What it demonstrates
- Mounting
key0Routeron an Express app to handle the full A2A payment flow - Defining multiple pricing plans with variable access durations
- Issuing JWTs via
AccessTokenIssuerafter on-chain payment verification - Protecting routes with
validateAccessTokenmiddleware - Using Redis for challenge storage and double-spend prevention
Architecture
Prerequisites
- Node.js 18+ or Bun runtime
- Redis running locally or accessible via URL
- A wallet address on Base to receive USDC payments
Directory structure
Environment variables
Code walkthrough
1. Imports and configuration
server.ts
@key0ai/key0/express subpath export provides the Express-specific router and middleware.
2. Payment adapter and storage
server.ts
X402Adapter— verifies ERC-20 Transfer events on Base by reading on-chain transaction receipts via viem.RedisChallengeStore— manages the challenge state machine (PENDING, PAID, DELIVERED, EXPIRED) with atomic Lua-script transitions.RedisSeenTxStore— prevents double-spend by tracking used transaction hashes with atomicSET NX.
3. Seller config and pricing plans
server.ts
plans array defines your pricing. Each plan has a unique planId, a unitAmount in USD, and a human-readable description. These are exposed in the auto-generated agent card so AI agents can discover what you sell and how much it costs.
challengeTTLSeconds controls how long an agent has to complete payment before the challenge expires (15 minutes here).
4. Credential issuance callback
server.ts
fetchResourceCredentials and returns whatever credential you issue. Here, the callback mints a JWT with plan-dependent expiry:
- single-photo: 1-hour token
- full-album: 24-hour token
fetchResourceCredentials can return any string — a JWT, an API key, an OAuth token, or a signed URL. Key0 passes it through to the agent as-is.5. Payment lifecycle hook
server.ts
onPaymentReceived fires after payment verification and credential issuance succeed. Use it for logging, analytics, webhooks, or notifying downstream systems.
resourceEndpointTemplate tells agents where to use their token. The {resourceId} placeholder is replaced with the actual resource identifier in the AccessGrant response.
6. Protecting routes
server.ts
/api/* routes behind JWT validation. This is decoupled from the payment flow — it only verifies the token signature and expiry. Your route handlers stay clean and focused on business logic.
Running the example
1
Clone and install
2
Configure environment
.env and set your wallet address and a strong token secret:.env
3
Start Redis
4
Start the server
Expected output
Verify the agent card
SellerConfig. It describes the seller’s capabilities, pricing plans, and A2A endpoint URL — everything an agent needs to initiate a purchase.
Complete the payment flow
To run a full end-to-end payment, start this seller and point an agent at it. The agent will discover the seller, select a plan, pay USDC on Base Sepolia, and retrieve the protected photo data.Source code
View
examples/express-seller on GitHub
