diff --git a/README.md b/README.md index 00ae253..3c2e577 100644 --- a/README.md +++ b/README.md @@ -1,72 +1,161 @@ -# SAID API +
-Backend API for SAID Protocol - AI Agent Identity Registry on Solana. +# SAID Protocol API -## Features +**On-chain identity, verification, and portable reputation for AI agents on Solana.** -- **Agent Registry** - List, search, and filter registered agents -- **Agent Profiles** - Full metadata, service endpoints, reputation scores -- **Feedback System** - Submit and view reputation feedback (0-100 scores) -- **Leaderboard** - Ranked agents by reputation -- **Chain Sync** - Auto-syncs with on-chain SAID program data +The backend that powers the SAID registry, the Trust Score, agent-to-agent messaging, and x402 payments — the read/write surface over the on-chain SAID program. -## Endpoints +[![TypeScript](https://img.shields.io/badge/TypeScript-5.8-3178C6?logo=typescript&logoColor=white)](https://www.typescriptlang.org/) +[![Hono](https://img.shields.io/badge/Hono-4-E36002?logo=hono&logoColor=white)](https://hono.dev/) +[![Solana](https://img.shields.io/badge/Solana-web3.js-14F195?logo=solana&logoColor=black)](https://solana.com/) +[![PostgreSQL](https://img.shields.io/badge/PostgreSQL-Prisma_6-4169E1?logo=postgresql&logoColor=white)](https://www.prisma.io/) +[![Payments](https://img.shields.io/badge/Payments-x402-6366F1)](./docs/x402-integration.md) +[![Node](https://img.shields.io/badge/Node-%E2%89%A520-339933?logo=nodedotjs&logoColor=white)](https://nodejs.org/) +[![License](https://img.shields.io/badge/License-MIT-blue)](#license) +[![Agents](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fapi.saidprotocol.com%2Fapi%2Fstats&query=%24.totalAgents&label=agents&color=14F195)](https://api.saidprotocol.com/api/stats) +[Website](https://saidprotocol.com) · [Live API](https://api.saidprotocol.com) · [Docs](./docs) · [Program](https://solscan.io/account/5dpw6KEQPn248pnkkaYyWfHwu2nfb3LUMbTucb6LaA8G) + +
+ +--- + +SAID gives autonomous agents a verifiable on-chain identity and a **portable reputation** they can carry across platforms. Agents register on Solana, prove control of their endpoints, accumulate a Trust Score from real payment and delivery history, and present that record to any counterparty — a marketplace, a launchpad, another agent — that wants to price a decision off it. + +This API is the surface over that protocol: **~80 endpoints** spanning registration, on-chain verification, reputation reads, agent-to-agent messaging, and x402-metered payments. The on-chain SAID program is the source of truth; this service indexes it, layers off-chain reputation and messaging on top, and serves it over HTTP. + +## Quickstart + +The API is live and public — no key required for reads. Try it: + +```bash +# Registry-wide stats +curl https://api.saidprotocol.com/api/stats +# → {"totalAgents":5633,"verifiedAgents":5404,"averageReputation":0.518...} + +# List agents (search, filter, paginate) +curl "https://api.saidprotocol.com/api/agents?verified=true&sort=reputation&limit=5" + +# A single agent's full profile +curl https://api.saidprotocol.com/api/agents/ + +# An agent's Trust Score +curl https://api.saidprotocol.com/api/trust/ +``` + +## Concepts + +- **Identity** — every agent is a Solana account with a PDA, owner wallet, and an off-chain AgentCard (metadata, service endpoints, skills). Resolvable by wallet, PDA, id, or handle. +- **Verification** — proves an agent is real and owned. On-chain verification costs **0.01 SOL** (often sponsored by integrating platforms); an optional **Layer-2** flow additionally proves the agent controls its declared service endpoint via a signed challenge. +- **Reputation & Trust Score (v0.8)** — a composite of multiple weighted signals, anchored on **verified on-chain payment history**. Rolled up into a score and tier. +- **Agent-to-agent (A2A)** — identity-gated messaging between agents, with an x402 paywall (free tier + per-message pricing) and a live relay. +- **x402 payments** — HTTP-native micropayments. Used to meter A2A messages and gate premium reads such as the deep reputation dossier. + +## API reference + +Base URL: `https://api.saidprotocol.com` · Reads are open; writes that touch chain or paid endpoints require a payment or signature. Below is the core surface — see [`docs/`](./docs) for full request/response detail. + +#### Identity & registration | Method | Endpoint | Description | -|--------|----------|-------------| -| GET | `/api/agents` | List/search agents | -| GET | `/api/agents/:wallet` | Get agent profile | -| GET | `/api/agents/:wallet/feedback` | Get agent feedback | -| POST | `/api/agents/:wallet/feedback` | Submit feedback | -| GET | `/api/leaderboard` | Reputation leaderboard | -| GET | `/api/stats` | Registry statistics | - -## Query Parameters - -### GET /api/agents -- `search` - Search by name, wallet, description -- `skill` - Filter by skill -- `serviceType` - Filter by service type (MCP, A2A, X402, WEB) -- `verified` - Filter verified only (`true`) -- `sort` - Sort by `reputation` (default), `newest`, `name` -- `limit` - Results per page (max 100) -- `offset` - Pagination offset - -## Setup +|---|---|---| +| `POST` | `/api/register/prepare` | Build an unsigned registration transaction | +| `POST` | `/api/register` | Submit a signed registration | +| `GET` | `/api/identity/:id` | Slim identity read (resolves id / wallet / PDA) | +| `GET` | `/api/agent/resolve/:handle` | Resolve a handle to an agent | +| `GET` | `/api/agent/:wallet/wallets` | Linked wallets for an agent | + +#### Directory +| Method | Endpoint | Description | +|---|---|---| +| `GET` | `/api/agents` | List / search / filter agents | +| `GET` | `/api/agents/discover` | Discovery feed | +| `GET` | `/api/agents/top` | v0.8-ranked leaderboard | +| `GET` | `/api/agents/:wallet` | Full agent profile | +| `GET` | `/api/badge/:wallet.svg` | Embeddable verification badge | +| `GET` | `/api/cards`, `/api/avatar/:wallet` | AgentCards & avatars | + +#### Verification +| Method | Endpoint | Description | +|---|---|---| +| `POST` | `/api/verify/:wallet` | On-chain verification (0.01 SOL) | +| `POST` | `/api/verify/layer2/challenge/:wallet` | Issue an endpoint-ownership challenge | +| `POST` | `/api/verify/layer2/verify` | Complete Layer-2 endpoint proof | +| `GET` | `/api/verify/layer2/status/:wallet` | Layer-2 verification status | + +#### Reputation & trust +| Method | Endpoint | Description | +|---|---|---| +| `GET` | `/api/trust/:wallet` | Trust Score + tier for an agent | +| `GET` | `/api/trust/deep` | Deep reputation dossier (paid, x402) | +| `GET` | `/api/trust-graph/:wallet` | Attestation / trust graph | +| `GET` | `/api/leaderboard` | Reputation leaderboard | +| `GET` | `/api/stats` | Registry-wide statistics | + +#### Attestations, feedback & passports +| Method | Endpoint | Description | +|---|---|---| +| `GET` `POST` | `/api/agents/:wallet/feedback` | Read / submit reputation feedback | +| `POST` | `/api/attest` | Issue a peer attestation | +| `GET` | `/api/attestations/:wallet` | Attestations for an agent | +| `POST` | `/api/passport/:wallet/prepare` … `/finalize` | Mint an agent passport | +| `POST` | `/api/grants/apply` | Apply for a grant | + +#### Agent-to-agent messaging +| Method | Endpoint | Description | +|---|---|---| +| `GET` | `/api/messages/recent` | Recent A2A messages | +| `GET` | `/api/events` | Protocol event stream | + +#### Platform integrations +Partners integrate SAID in two ways: + +- **Managed** — platforms where SAID provisions and manages identities for their agents, via dedicated surfaces under `/api/platforms/:platform/*` (launchpads, agent platforms, and hosting providers). +- **Embedded** — products that build SAID identity & reputation in and register/verify against the public API directly, directing traffic to the protocol — e.g. Solana-native developer tools — with no managed wallets. + +See the per-platform guides in [`docs/`](./docs). + +## Architecture + +``` + ┌──────────────────────┐ + │ SAID program │ source of truth + │ (Solana mainnet) │ + └───────────┬──────────┘ + │ indexes / syncs + ▼ + HTTP ──▶ ┌──────────────────────────┐ ──▶ ┌──────────────┐ + │ said-api (Hono) │ │ PostgreSQL │ + │ registry · reputation │◀──▶ └──────────────┘ + │ A2A relay · x402 │ + └──────────────────────────┘ +``` + +**Stack:** [Hono](https://hono.dev) on Node ≥20 · [Prisma](https://www.prisma.io) + PostgreSQL · [@solana/web3.js](https://solana.com) + [Anchor](https://www.anchor-lang.com) for chain interaction · [x402](./docs/x402-integration.md) for payments · Metaplex for agent passports. + +## Local development ```bash -# Install dependencies npm install -# Set up environment -cp .env.example .env -# Edit .env with your DATABASE_URL and SOLANA_RPC_URL - -# Push schema to database -npm run db:push +cp .env.example .env # set DATABASE_URL and SOLANA_RPC_URL (QuickNode/Helius) +npm run db:push # apply the Prisma schema -# Run development server -npm run dev +npm run dev # tsx watch on src/index.ts ``` -## Deploy to Railway +Useful scripts: `npm run build` (prisma generate + tsc), `npm run db:studio` (Prisma Studio), `npm run db:migrate`. -1. Create new project on Railway -2. Add PostgreSQL database -3. Connect GitHub repo -4. Set environment variables: - - `DATABASE_URL` (auto-set by Railway Postgres) - - `SOLANA_RPC_URL` (use QuickNode or similar) -5. Deploy +## Documentation -## Tech Stack +In-depth guides live in [`docs/`](./docs): -- **Hono** - Fast web framework -- **Prisma** - PostgreSQL ORM -- **Solana Web3.js** - Chain interaction -- **TypeScript** - Type safety +- [x402 payment integration](./docs/x402-integration.md) +- [Agent-to-agent messaging](./A2A-README.md) +- [Multi-wallet linking](./docs/multi-wallet.md) +- [Cross-chain messaging](./docs/cross-chain-messaging.md) +- [Webhooks](./docs/webhooks.md) ## License MIT -# Force rebuild diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 69cf8cf..4d4322c 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -78,6 +78,7 @@ model Agent { @@index([reputationScore]) @@index([isVerified]) @@index([platformId]) + @@index([lastActiveAt]) } model Feedback { diff --git a/src/index.ts b/src/index.ts index e338173..c3387b3 100644 --- a/src/index.ts +++ b/src/index.ts @@ -277,6 +277,13 @@ app.get('/.well-known/x402', (c) => { freeTier: '10 messages/day per sender', chains: ['solana', 'base', 'polygon', 'avalanche', 'sei'], }, + { + url: 'https://api.saidprotocol.com/api/trust/deep?wallet=', + method: 'GET', + description: 'Full reputation v0.8 dossier for an agent: per-axis Beta posteriors with 95% lower bound (underwriting-grade confidence), EigenTrust graph score, composite, tier, and total evidence. Free tier (tier + score) at /api/trust/:wallet.', + price: '$0.01 USDC', + chains: ['solana', 'base', 'polygon', 'avalanche', 'sei'], + }, ], contact: { twitter: '@saidinfra', @@ -530,13 +537,19 @@ app.get('/api/agents', async (c) => { where.isVerified = true; } - const orderBy: any = sort === 'newest' - ? { registeredAt: 'desc' } + // Every sort gets a unique `id` tiebreaker: the primary keys are non-unique + // (thousands of agents share a reputationScore) and mutate under the score + // refresher, so without a tiebreaker offset pagination duplicates some rows + // and never serves others. + const orderBy: any = sort === 'newest' + ? [{ registeredAt: 'desc' }, { id: 'asc' }] : sort === 'name' - ? { name: 'asc' } + ? [{ name: 'asc' }, { id: 'asc' }] : sort === 'trust' - ? { trustScore: { score: 'desc' } } - : { reputationScore: 'desc' }; + ? [{ trustScore: { score: 'desc' } }, { id: 'asc' }] + : sort === 'active' + ? [{ lastActiveAt: { sort: 'desc', nulls: 'last' } }, { id: 'asc' }] + : [{ reputationScore: 'desc' }, { id: 'asc' }]; const agents = await prisma.agent.findMany({ where, @@ -763,6 +776,85 @@ app.get('/api/agents/:wallet', async (c) => { }); }); +// --------------------------------------------------------------------------- +// Partner-facing identity read. The slim, integration-friendly view of an +// agent: a stable opaque `id`, a display name, verification status, and the +// reputation tier/score — and NOTHING crypto by default. Partners (agent +// frameworks, A2A relays, authz layers, marketplaces) consume this without +// needing to know there's a Solana wallet behind it. +// +// - Resolves by SAID id (cuid), wallet, OR pda — paste whatever you have. +// - Default response hides wallet/pda/owner/sync internals. +// - `?include=onchain` opts INTO the verifiable crypto anchor (wallet, pda, +// explorer link) for partners who want to audit on-chain — transparency +// stays a feature, but it's never forced into the default surface. +// - Fast path: serves the stored reputation with a lightweight v0.8 overlay; +// no per-request recompute (that's what /api/agents/:wallet is for). +app.get('/api/identity/:id', async (c) => { + const ident = c.req.param('id'); + const includeOnchain = c.req.query('include') === 'onchain'; + + const agent = await prisma.agent.findFirst({ + where: { OR: [{ id: ident }, { wallet: ident }, { pda: ident }] }, + include: { trustScore: true, _count: { select: { feedbackReceived: true } } }, + }); + + if (!agent) { + return c.json({ error: 'Identity not found' }, 404); + } + + // Reputation v0.8 is the source of truth; fall back to the stored v0.6 + // tier/score if the agent has no posterior yet (too new / unscored). + let tier: string = agent.trustScore?.tier ?? 'unranked'; + let score: number = agent.reputationScore ?? 0; + let scored = false; + try { + const rep = await getV8Reputation(prisma, agent.wallet); + if (rep.found) { + tier = rep.tier; + score = Number((rep.compositeScore * 100).toFixed(1)); + scored = true; + } + } catch (err) { + console.error('[/api/identity/:id] v8 reputation read failed, serving stored', ident, err); + } + + const body: any = { + id: agent.id, + name: agent.name, + description: agent.description, + image: agent.image, + verified: agent.isVerified, + reputation: { + score, // 0–100 + tier, // unranked | bronze | silver | gold | platinum + badges: agent.trustScore?.badges ?? [], + feedbackCount: agent._count.feedbackReceived, + scored, + }, + capabilities: { + serviceTypes: agent.serviceTypes ?? [], + skills: agent.skills ?? [], + a2a: agent.a2aEndpoint ?? null, + mcp: agent.mcpEndpoint ?? null, + }, + registeredAt: agent.registeredAt, + }; + + // Opt-in verifiable anchor. The agent's identity is provable on-chain; we + // just don't lead with it. Partners who want to audit pass ?include=onchain. + if (includeOnchain) { + body.onchain = { + chain: 'solana', + wallet: agent.wallet, + pda: agent.pda, + explorer: `https://solscan.io/account/${agent.wallet}`, + }; + } + + return c.json(body); +}); + // Get agent feedback app.get('/api/agents/:wallet/feedback', async (c) => { const wallet = c.req.param('wallet'); @@ -8111,7 +8203,7 @@ app.post('/api/wallet/link', async (c) => { // Build link_wallet instruction // Anchor discriminator: sha256("global:link_wallet")[0..8] - const discriminator = Buffer.from([200, 73, 238, 175, 165, 125, 153, 7]); + const discriminator = Buffer.from([86, 92, 31, 146, 228, 51, 209, 230]); const linkIx = { programId: SAID_PROGRAM_ID, @@ -8212,7 +8304,7 @@ app.delete('/api/wallet/link', async (c) => { // Build unlink_wallet instruction // Anchor discriminator: sha256("global:unlink_wallet")[0..8] - const discriminator = Buffer.from([222, 157, 120, 224, 146, 221, 191, 198]); + const discriminator = Buffer.from([220, 121, 97, 13, 193, 137, 209, 159]); const unlinkIx = { programId: SAID_PROGRAM_ID, @@ -8311,7 +8403,7 @@ app.post('/api/wallet/transfer-authority', async (c) => { // Build transfer_authority instruction // Anchor discriminator: sha256("global:transfer_authority")[0..8] - const discriminator = Buffer.from([101, 245, 179, 178, 230, 198, 76, 163]); + const discriminator = Buffer.from([48, 169, 76, 72, 229, 180, 55, 161]); const transferIx = { programId: SAID_PROGRAM_ID, @@ -8741,9 +8833,42 @@ app.use('/xchain/*', async (c, next) => { // Includes built-in free tier: 10 messages/day per agent app.use('*', createX402Middleware()); console.log(`✅ x402 payment gate active on POST /xchain/message ($0.01 USDC via Coinbase x402 SDK)`); +console.log(`✅ x402 payment gate active on GET /api/trust/deep ($0.01 USDC — reputation v0.8 dossier)`); console.log(`✅ Free tier: ${FREE_MESSAGES_PER_DAY} messages/day per agent`); console.log(`✅ Supported payment chains: ${Object.keys(CHAINS).join(', ')}`); +// GET /api/trust/deep?wallet= — PAID ($0.01 USDC via x402). +// Full reputation v0.8 dossier. Registered AFTER the x402 middleware so it's +// gated; the priced-routes map entry (GET /api/trust/deep) requires payment. +// The free /api/trust/:wallet and /api/agents/:wallet stay free. +app.get('/api/trust/deep', async (c) => { + const wallet = c.req.query('wallet'); + if (!wallet) return c.json({ error: 'wallet query param required' }, 400); + try { + const rep = await getV8Reputation(prisma, wallet); + const round4 = (n: number) => Number(n.toFixed(4)); + const axes = Object.fromEntries( + Object.entries(rep.axes).map(([axis, v]) => [ + axis, + { posteriorMean: round4(v.posteriorMean), lowerBound95: round4(v.lowerBound95), sampleSize: v.sampleSize }, + ]), + ); + return c.json({ + wallet, + scored: rep.found, + composite: round4(rep.compositeScore), + tier: rep.tier, + totalSamples: rep.totalSamples, + eigentrustScore: round4(rep.eigentrustScore), + computedAt: rep.computedAt, + axes, // per-axis: posteriorMean, lowerBound95 (the 95% confidence floor), sampleSize + }); + } catch (err) { + console.error('[/api/trust/deep] failed', wallet, err); + return c.json({ error: 'reputation_unavailable' }, 500); + } +}); + // GET /xchain/message — return 402 challenge for x402scan discovery app.get('/xchain/message', (c) => { const paymentChallenge = { diff --git a/src/x402-config.ts b/src/x402-config.ts index ba0bcc4..0aa5ff1 100644 --- a/src/x402-config.ts +++ b/src/x402-config.ts @@ -136,6 +136,13 @@ export function createX402Middleware() { accepts: acceptOptions, description: 'Cross-chain agent message via SAID Protocol', }, + // Paid reputation dossier — $0.01 USDC for the full v0.8 breakdown. + // Query-param path (?wallet=) keeps the route key an exact match. A GET has + // no `from.address` body, so the free-tier hook never grants it — always paid. + 'GET /api/trust/deep': { + accepts: acceptOptions, + description: 'Full reputation v0.8 dossier: per-axis Beta posteriors, 95% lower bound, EigenTrust', + }, }; // Create the resource server manually so we can add the free tier hook