Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
5 changes: 5 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 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");
});
});
Loading
Loading