Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .env.local.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ STELLAR_NETWORK=testnet
# Only set to 'true' if using testnet
DEV_MODE=true

# Admin API Key
# Full root access to /admin/* and administrative operations.
# In production, this variable is REQUIRED. In dev, auto-generated on first boot if unset.
ADMIN_API_KEY=osk_admin_your_secret_key_here

# Anthropic API Key
# Get from: https://console.anthropic.com/
# Format: sk-ant-...
Expand All @@ -43,6 +48,29 @@ DEBUG_ROUTES=false
# customization -- equip/unequip of already-unlocked cosmetics still works.
STELLAR_TREASURY_ADDRESS=

# Optional: Trusted proxy count for X-Forwarded-For parsing.
# Set to the number of reverse-proxy hops your infrastructure adds
# (e.g. "1" on Vercel, "2" if behind a load balancer + Cloudflare).
# When unset, the best-effort rightmost non-private IP is used instead.
# TRUSTED_PROXY_COUNT=1

# Optional: Force synchronous key-store flush on every authenticated request.
# Defaults to async/debounced (200ms). Set to "true" when accurate per-request
# usage counters matter (e.g. billing) at the cost of latency on hot paths.
# FLUSH_USAGE_SYNC=false

# ─────────────────────────────────────────────────────────────────────────────
# API KEY STORE BACKEND (required for serverless / multi-instance deployments)
# ─────────────────────────────────────────────────────────────────────────────
# By default, API keys are stored in .data/api-keys.json on the local
# filesystem β€” fine for self-hosted/local dev but NOT shared across serverless
# invocations (e.g. Vercel). Set the two vars below to enable Upstash Redis
# (or Vercel KV, which uses the same protocol) for a shared, durable store.
#
# Get these from https://console.upstash.com or Vercel dashboard β†’ Storage β†’ KV.
# UPSTASH_REDIS_REST_URL=https://us1-your-db.upstash.io
# UPSTASH_REDIS_REST_TOKEN=AXxx...your-token

# Optional: Better Stack / Logtail structured API logs
# Leave empty locally to keep logging as a no-op.
LOGTAIL_SOURCE_TOKEN=
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,5 @@ node_modules
# Local file-backed stores
.data/
tsconfig.tsbuildinfo
test-results/
playwright-report/
6 changes: 6 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"sonarlint.connectedMode.project": {
"connectionId": "bitcoindefi",
"projectKey": "Bitcoindefi_Open-Stellar"
}
}
81 changes: 65 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,14 @@ Plataforma de infraestructura de pagos para agentes de IA, construida sobre Stel

## Stack

| Capa | TecnologΓ­a |
|------|------------|
| Framework | Next.js 16 (modo webpack β€” requerido por snarkjs) |
| UI | React 19, Tailwind v4, Radix UI, Framer Motion |
| Stellar | @stellar/stellar-sdk v16, @stellar/freighter-api, Soroban RPC |
| ZK | snarkjs 0.7.6, Groth16/BN254, circom (WASM artifacts) |
| EVM | wagmi, viem, WalletConnect |
| Deploy | Vercel (Next.js, auto-detect) |
| Capa | TecnologΓ­a |
| --------- | ------------------------------------------------------------- |
| Framework | Next.js 16 (modo webpack β€” requerido por snarkjs) |
| UI | React 19, Tailwind v4, Radix UI, Framer Motion |
| Stellar | @stellar/stellar-sdk v16, @stellar/freighter-api, Soroban RPC |
| ZK | snarkjs 0.7.6, Groth16/BN254, circom (WASM artifacts) |
| EVM | wagmi, viem, WalletConnect |
| Deploy | Vercel (Next.js, auto-detect) |

---

Expand Down Expand Up @@ -84,12 +84,14 @@ Archivos: [app/explorer/page.tsx](app/explorer/page.tsx), [components/explorer/r
Cada agente puede acuΓ±ar un **pasaporte Groth16** que prueba β€” sin revelar la identidad del dueΓ±o ni el saldo real β€” que estΓ‘ respaldado por un humano verificado y es solvente hasta su spend cap.

Las cuatro invariantes on-chain:

- Prueba Groth16 vΓ‘lida (verificada por CircomGroth16Verifier en Soroban)
- Nullifier anti-replay (un pasaporte, un uso)
- MembresΓ­a en el identity registry
- Proof-of-funds para el spend cap declarado

Flujo en el browser:

1. Se genera un keypair efΓ­mero (`privateKey`, `agentId`)
2. snarkjs calcula el witness y genera la prueba WASM local
3. La prueba se envΓ­a al validador Soroban para attestation on-chain
Expand Down Expand Up @@ -119,13 +121,53 @@ La ruta `GET /api/cron/health-check` esta pensada para Vercel Cron. Marca offlin

Archivos: [lib/agents/agent-health-store.ts](lib/agents/agent-health-store.ts), [app/api/agents/](app/api/agents/), [app/api/cron/health-check/](app/api/cron/health-check/)

### AutenticaciΓ³n y GestiΓ³n de API Keys (Zero-Trust)

Open Stellar implementa un modelo de autenticaciΓ³n y autorizaciΓ³n mΓ‘quina a mΓ‘quina cerrado por defecto (_closed-by-default_).

#### Niveles de Claves

- **Admin Key (`ADMIN_API_KEY`)**: Acceso total para la consola administrativa (`/admin/*`) y operaciones de escritura restringidas. Requerida en producciΓ³n al iniciar la aplicaciΓ³n.
- **Service Keys (`osk_live_...`)**: Claves emitidas con scopes especΓ­ficos (`x402:quote`, `x402:settle`, `agents:read`, `agents:write`, `webhooks:manage`, `quests:manage`).

#### AutenticaciΓ³n en Requests

Las solicitudes se autentican mediante header:

```http
Authorization: Bearer osk_live_...
```

O mediante query param para integraciones simples:

```http
GET /api/agents?apiKey=osk_live_...
```

#### Rate Limits por Tier (Sliding Window)

| Tier | Requests/min |
| ------ | ------------ |
| No key | 10 |
| Free | 60 |
| Pro | 600 |
| Admin | unlimited |

#### Seguridad y GarantΓ­as

- **Almacenamiento Hashed**: Las claves se guardan hasheadas con SHA-256 (`lib/auth/api-keys.ts`). Ninguna clave en texto plano queda almacenada.
- **ComparaciΓ³n en Tiempo Constante**: Se utiliza `timingSafeEqual` para prevenir ataques de canal lateral (_timing attacks_).
- **Visualización Única**: El secreto completo se muestra una única vez al emitirse o rotarse; posteriormente solo se expone el prefijo sanitizado (`osk_live_abc123...`).
- **RevocaciΓ³n Inmediata**: La revocaciΓ³n invalida el acceso instantΓ‘neamente en el middleware.
- **Consola de AdministraciΓ³n**: Interfaz grΓ‘fica en `/admin/keys` para emitir, inspeccionar, revocar y rotar credenciales.

### Escrow

| Contrato | Red | FunciΓ³n |
|----------|-----|---------|
| [EscrowMilestone.sol](contracts/evm/EscrowMilestone.sol) | EVM | Escrow por hitos (createDeal, release, refund, raiseDispute) |
| [X402ServicePaywall.sol](contracts/evm/X402ServicePaywall.sol) | EVM | Paywall x402 (settle402, hasPaid, withdraw) |
| [escrow/src/lib.rs](contracts/stellar/escrow/src/lib.rs) | Soroban | Base funcional (create, release, dispute, get) |
| Contrato | Red | FunciΓ³n |
| -------------------------------------------------------------- | ------- | ------------------------------------------------------------ |
| [EscrowMilestone.sol](contracts/evm/EscrowMilestone.sol) | EVM | Escrow por hitos (createDeal, release, refund, raiseDispute) |
| [X402ServicePaywall.sol](contracts/evm/X402ServicePaywall.sol) | EVM | Paywall x402 (settle402, hasPaid, withdraw) |
| [escrow/src/lib.rs](contracts/stellar/escrow/src/lib.rs) | Soroban | Base funcional (create, release, dispute, get) |

---

Expand All @@ -144,6 +186,7 @@ Vista operativa del stack como SaaS: squads de agentes por distrito, telemetrΓ­a
### Agent Passport (ZK)

Panel interactivo de 4 pasos:

1. **Mint** β€” genera prueba Groth16 en el browser
2. **Verify on-chain** β€” consulta attestation en Soroban testnet
3. **Authorize x402** β€” gate de spend cap contra el validador
Expand All @@ -154,6 +197,7 @@ Muestra contratos desplegados en testnet con links a stellar.expert.
### Private Deploy

Para desarrolladores que quieren su propio nodo Open Stellar:

- GuΓ­a de 3 pasos (Fork β†’ Configure β†’ Deploy)
- BotΓ³n "Deploy to Vercel" de un click
- Tabla completa de endpoints API con mΓ©todo y descripciΓ³n
Expand Down Expand Up @@ -214,10 +258,12 @@ Set `NEXT_PUBLIC_MOCK_MODE=true` in `.env.local` to run the local demo without l
Las rutas bajo `/api/protocol/*` y `/api/stellar/*` emiten logs estructurados mediante Better Stack / Logtail cuando `LOGTAIL_SOURCE_TOKEN` estΓ‘ configurado. La app tambien envuelve `next.config.mjs` con `withLogtail` para habilitar la integracion de Next.js. Si la variable no existe, el logger queda en modo no-op para desarrollo local.

Campos base incluidos en cada evento:

- `route`, `method`, `path`, `status`, `durationMs`
- `event`, `reason` y contexto de negocio como `paymentRef`, `agentId`, `chain`, `txHash`, `publicKey`

Alertas recomendadas en Better Stack:

- `event = x402.settle.failed` o `status >= 500`
- `event = x402.settle.passport_denied` para detectar rechazos del gate ZK
- `reason = friendbot_failed` o `reason = horizon_lookup_failed` para incidentes Stellar testnet
Expand Down Expand Up @@ -283,6 +329,7 @@ El repositorio incluye `vercel.json` que fuerza:
```

Pasos:

1. Fork en GitHub
2. Importar en [vercel.com/new](https://vercel.com/new)
3. Agregar `NEXT_PUBLIC_WALLETCONNECT_PROJECT_ID` en las variables de entorno del proyecto
Expand Down Expand Up @@ -318,22 +365,24 @@ Protecciones recomendadas para `main`:
El repositorio estΓ‘ integrado con SonarCloud para anΓ‘lisis estΓ‘tico de cΓ³digo y cobertura de tests. Los badges de calidad se muestran al inicio de este README.

ConfiguraciΓ³n:

- **Quality Gate**: Se ejecuta en cada push a `main` y en cada PR.
- **Exclusiones**: `scripts/templates/**` estΓ‘ excluido del anΓ‘lisis para evitar falsos positivos en archivos con placeholders intencionales.
- **Cobertura**: Se reporta desde `coverage/lcov.info` generado por Vitest.

Para que el quality gate aparezca como status check en PRs, asegΓΊrate de que la [configuraciΓ³n de SonarCloud](https://sonarcloud.io/project/settings?project=Bitcoindefi_Open-Stellar&id=Bitcoindefi_Open-Stellar) tenga activado:

- **Administration > General Settings > Pull Request** β†’ "Enable pull request decoration"
- **Administration > Quality Gate** β†’ Seleccionar el quality gate por defecto o uno custom

---

## Contratos desplegados (Stellar testnet)

| Contrato | ID |
|----------|----|
| Contrato | ID |
| ---------------------- | ---------------------------------------------------------- |
| AgentPassportValidator | `CDNSZUNEWFCGSPWLPDSWTENR2WPHKC34RGZQG7RJA54OPGTZGVVRFYBA` |
| CircomGroth16Verifier | `CCMKLYSRUH2HMA4UU6WLXWQXEY6KAH5AWB5BEVMJGNGC5GLGTVROLG4A` |
| CircomGroth16Verifier | `CCMKLYSRUH2HMA4UU6WLXWQXEY6KAH5AWB5BEVMJGNGC5GLGTVROLG4A` |

Explorar en [stellar.expert/explorer/testnet](https://stellar.expert/explorer/testnet).

Expand Down
62 changes: 42 additions & 20 deletions __tests__/agent-profile-page.test.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,40 @@
import { describe, expect, it, vi } from "vitest"
import AgentPage, { generateMetadata } from "@/app/agents/[id]/page"
import { registerAgent, resetAgentRegistryForTests } from "@/lib/agent-registry"
import { beforeEach, afterEach, describe, expect, it, vi } from "vitest";
import AgentPage, { generateMetadata } from "@/app/agents/[id]/page";
import {
registerAgent,
resetAgentRegistryForTests,
} from "@/lib/agent-registry";

vi.mock("next/navigation", () => ({
notFound: vi.fn(() => {
throw new Error("404")
})
}))
throw new Error("404");
}),
}));

describe("Agent Profile Page", () => {
beforeEach(() => {
vi.stubGlobal(
"fetch",
vi.fn().mockResolvedValue({
ok: false,
json: async () => ({}),
}),
);
});

afterEach(() => {
vi.unstubAllGlobals();
});

it("renders 404 when agent does not exist", async () => {
resetAgentRegistryForTests()
await expect(AgentPage({ params: Promise.resolve({ id: "non-existent" }) }))
.rejects.toThrow("404")
})
resetAgentRegistryForTests();
await expect(
AgentPage({ params: Promise.resolve({ id: "non-existent" }) }),
).rejects.toThrow("404");
});

it("renders dynamic agent page metadata and page element correctly", async () => {
resetAgentRegistryForTests()
resetAgentRegistryForTests();
registerAgent({
agentId: "agent-007",
model: "gpt-5-mini",
Expand All @@ -27,14 +45,18 @@ describe("Agent Profile Page", () => {
x402: { accepts: false },
registeredAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
})
});

const element = await AgentPage({
params: Promise.resolve({ id: "agent-007" }),
});
expect(element).toBeDefined();

const element = await AgentPage({ params: Promise.resolve({ id: "agent-007" }) })
expect(element).toBeDefined()

// Check metadata generation
const metadata = await generateMetadata({ params: Promise.resolve({ id: "agent-007" }) })
expect(metadata.title).toContain("agent-007")
expect(metadata.description).toContain("Defense Grid")
})
})
const metadata = await generateMetadata({
params: Promise.resolve({ id: "agent-007" }),
});
expect(metadata.title).toContain("agent-007");
expect(metadata.description).toContain("Defense Grid");
});
});
2 changes: 1 addition & 1 deletion __tests__/api/agents/positions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ describe("agent position store", () => {
})
})

it("keeps history bounded to the latest 1000 entries", { timeout: 20000 }, () => {
it("keeps history bounded to the latest 1000 entries", { timeout: 60000 }, () => {
for (let index = 0; index < 1005; index += 1) {
moveAgentPosition("bot-1", { dx: 1, dy: 0 })
}
Expand Down
Loading
Loading