The USPCoinbase × Solana

x402 on Solana, in production.

HTTP 402 was a placeholder in the spec for 30 years. Coinbase resurrected it as the protocol for AI-agent commerce. GapSmith is one of the first end-to-end implementations on Solana — Phantom for humans, agent wallets for machines, the same payment rail.

Takeaway

Every /api/v1/* endpoint is wrapped in a server-side x402 handler. A client probes without payment → gets a 402 with payment requirements → signs a Solana SPL transferChecked with a memo bound to the request → resubmits with X-Payment → gets 200. Idempotent by tx hash. Same code path for human Phantom payments and autonomous agent wallets.

Why we picked x402 (and Solana)

Three things have to be true for AI agents to transact at machine speed:

  • Per-request settlement — no API keys, no monthly invoices, no credit. The agent should pay exactly when it consumes.
  • Sub-5-second finality— humans tolerate 30s redirects, agents don't. Solana's ~400ms slot time + ~3s confirmation is the floor.
  • Stable unit of account — fees in volatile assets are unworkable for cost accounting. USDC on Solana solves this.

x402 is the protocol layer that makes per-request HTTP payments standard instead of bespoke. Coinbase formalized it; the wider ecosystem (Phantom, Solana Foundation, independent agent frameworks) is converging on it. We bet early.

The full flow

1

Probe

Client hits the endpoint without an X-Payment header:

bash
curl https://gapsmith.draftlabs.org/api/v1/scout/gaps?sector=ai-ml

Server responds 402 Payment Required with the payment requirements:

json
{
  "x402Version": 1,
  "accepts": [{
    "scheme": "exact",
    "network": "solana",
    "asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",   // USDC mainnet
    "payTo": "<merchant-pubkey>",
    "maxAmountRequired": "100000",                                // 0.10 USDC (atomic, 6 decimals)
    "memo": "scout-gaps:ai-ml:1730000000",                        // bound to path + sector + timestamp
    "resource": "https://gapsmith.draftlabs.org/api/v1/scout/gaps?sector=ai-ml"
  }],
  "error": "Payment required"
}
2

Sign + send on Solana

Client builds a Solana SPL transferChecked instruction with the given amount, attaches the memovia the Memo program, and broadcasts. For humans, this is one click in Phantom. For agents, it's ~80 lines of solders + solana Python (see examples/agent_demo.py).

3

Resubmit with X-Payment header

Once the tx confirms, the client encodes the tx signature into the X-Payment header and re-hits the original endpoint:

bash
curl https://gapsmith.draftlabs.org/api/v1/scout/gaps?sector=ai-ml \
  -H "X-Payment: <tx-signature>"
4

Server-side verification

The server-side withX402Payment wrapper does five checks:

  • Tx is finalized on the matching network (mainnet vs devnet)
  • Source ATA → destination matches the merchant address
  • Amount ≥ maxAmountRequired in atomic USDC
  • Memo matches the resource fingerprint (path + critical query params + a 1h window) — prevents replay against a different endpoint
  • tx_hash not previously used (UNIQUE constraint on x402_pending_payments.tx_hash) — prevents replay against the same endpoint
5

200 with the resource

All five checks pass: server runs the handler, returns the JSON resource. The wrapper does this in ~150ms server-side (the on-chain check uses Helius RPC with origin-pinning so we can prove the result came from a node we trust).

Idempotency by tx hash

A naive implementation would let an agent replay the same X-Payment to drain compute. Our table:

sql
CREATE TABLE x402_pending_payments (
  id           uuid PRIMARY KEY DEFAULT gen_random_uuid(),
  tx_hash      text NOT NULL UNIQUE,        -- ← idempotency key
  payer        text NOT NULL,                -- agent wallet pubkey
  amount       bigint NOT NULL,              -- atomic USDC
  resource     text NOT NULL,                -- request fingerprint
  user_id      uuid REFERENCES auth.users,   -- nullable for pure-agent calls
  consumed_at  timestamptz NOT NULL DEFAULT now()
);

First successful resolution inserts. Second attempt with the same tx hash hits the unique constraint, returns 409 Conflict. For async Compute API endpoints we additionally key the agent_jobs row on tx_hash UNIQUE — same payment, same job, replay returns cached result instead of re-running the 30-min Forge pipeline.

Memo binding (anti-replay against other endpoints)

The 402 response includes a memo string the client must attach to the on-chain tx. We compute it server-side as:

typescript
const memo = [
  sku,                                         // "scout-gaps"
  criticalQueryParam,                          // sector or sku-specific
  Math.floor(Date.now() / (60 * 60 * 1000))    // 1h window bucket
].join(":");

On verification we re-derive the memo and assert it matches the on-chain memo instruction. An agent can't pay $0.10 for /scout/gaps and reuse that tx for a $25 /forge/ideate call — different SKU, different memo, verification fails.

Two clients, one rail

Humans (Phantom)

On /pricing, Pay with USDC opens Phantom with a pre-built transferChecked tx. User taps approve, the tx confirms in ~3s, the page polls /api/checkout/x402/verify which runs the same five checks and marks the purchase complete.

Pre-flight: the client checks SOL/USDC balance via Helius RPC (NEXT_PUBLIC_SOLANA_RPC_URL, with allowed-origin restriction so a rogue site can't steal credits). If the user's ATA doesn't exist yet, we attachcreateAssociatedTokenAccountIdempotentInstruction to the same tx — first-time buyers pay ~0.00204 SOL extra for ATA rent.

Agents (any wallet)

Same protocol, no signup, no API key. Agents:

  • GET / POST the endpoint without payment → receive 402 with payment requirements
  • Sign a SPL transfer programmatically using their wallet's secret key
  • Resubmit with X-Payment: <tx-sig> → 200 with JSON

Reference implementation in examples/agent_demo.py — ~80 lines, hits all 5 Data API endpoints + (optionally) the async Compute API in one run. Same code works on devnet (free testing, ~$0.55 per full run) and mainnet (real USDC).

Why search routes through Tavily for some providers

A subtle ecosystem note that bit us during integration. Most LLM providers expose a Chat Completions endpoint (/v1/chat/completions) — that's what LiteLLM uses. Native web search tools, however, vary:

  • Gemini: googleSearchRetrieval tool works on Chat Completions ✓
  • OpenAI: web_search_preview tool only on the Responses API (/v1/responses) ✗
  • xAI / Grok: web_search tool only on the Responses API ✗
  • Anthropic / DeepSeek / MiniMax: no native search tool ✗

So we route Gemini through native search, and everyone else through Tavily— search-first results injected into the prompt. The outcome is the same (the model gets fresh web context) but the wiring differs. This kept us from over-indexing on any one provider's tool format.

See it on-chain

Every mainnet purchase is a real USDC transfer. The first one is 5xxx...rT9w on Solscan — check the Memo instruction (scout:1730000000) and the atomic-USDC amount. Devnet test runs (~$0.55 per pass) follow the same pattern with devnet USDC — see the API reference for endpoint-by-endpoint test transactions.

Reusing this

The wrapper is one file — src/lib/x402-server.ts— and ~150 lines including the on-chain verification helpers. If you're building x402 endpoints on Solana, copy it. The only tunable: which RPC you use (we use Helius for production, with an allowed-origins restriction). Everything else is protocol-conformant.

Want the source for your own integration? Reach out via /contact — we're happy to share and fold in upstream improvements as the x402 spec evolves.

Built on Solana · Powered by x402 · Phantom + Helius integrated