Skip to content

wgopar/a2a-x402-agent-template

Repository files navigation

A2A x402 Agent Template

License: MIT Node.js 22+ Tests

A production-ready template for building AI agents that accept on-chain payments. Combines Hono (web framework), x402 (HTTP 402 payment protocol), A2A (Agent-to-Agent protocol), and ERC-8004 (on-chain agent identity) into a serverless agent that deploys to AWS Lambda in one command.

Pre-configured for Base Sepolia (testnet) — switch to Base Mainnet by changing two env vars (see Network Switching). Designed to be consumed by AI agents: ships with AGENT.md so tools like OpenClaw can index and interact with this repo out of the box.

Architecture Overview

flowchart LR
    Client -->|HTTPS| FnURL["Lambda Function URL"]

    subgraph AWS["AWS (Terraform-managed)"]
        FnURL --> Lambda["Lambda<br/>(arm64, Docker)"]
        Lambda --> SM["Secrets Manager<br/>(private key)"]
        Lambda -.-> CW["CloudWatch Logs"]
        ECR["ECR"] -.->|image source| Lambda
    end

    subgraph Hono["Hono App"]
        Free["/health<br/>/.well-known/agent-card.json<br/>/.well-known/agent-registration.json"]
        Paid["/api/* &ensp; /a2a"]
    end

    Lambda --> Hono

    Paid -->|verify payment| Facilitator["x402 Facilitator"]
    Facilitator -->|settle| Blockchain["Base (USDC)"]
Loading

Terraform manages all AWS infrastructure: Lambda function, ECR image repository, Secrets Manager (private key), IAM roles, CloudWatch log group, and the Function URL endpoint. Endpoints split into free (/health, agent card, read-only A2A methods) and paid (/api/*, A2A message/send and message/stream) — see How Payments Work for the full x402 sequence.

What's Inside

Library Package What it does
Hono hono Ultrafast web framework. Runs on Node.js, AWS Lambda, Deno, Bun, Cloudflare Workers — same code everywhere.
x402 @x402/hono @x402/core @x402/evm @coinbase/x402 HTTP 402 payment protocol. Uses Coinbase CDP facilitator for payment verification and settlement.
A2A @a2a-js/sdk Google's Agent-to-Agent protocol. Standardized JSON-RPC interface so agents can discover and talk to each other.
ERC-8004 agent0-sdk erc-8004-js On-chain agent identity. Mints an NFT with IPFS metadata pointing to your agent's live endpoint. Supports update-or-create registration flow.
Zod zod TypeScript-first schema validation. Used to validate API route inputs.

Prerequisites

  • Node.js 22+ and npm
  • AWS CLI configured (aws configure)
  • Terraform (for Lambda deployment)
  • Docker (for building the Lambda container image)
  • Pinata account (free tier works — needed only for ERC-8004 identity registration)

Quick Start

1. Clone and install

git clone https://github.com/wgopar/a2a-x402-agent-template.git
cd a2a-x402-agent-template
npm install

2. Create a wallet

npm run create-wallet -- my-agent

This generates a new Ethereum wallet and writes the address + private key to both .env (local dev) and infra/terraform.tfvars (Lambda deploy). It also sets function_name in tfvars, which names all AWS resources (Lambda, ECR, IAM roles). Use a unique name per agent to prevent resource collisions. Both files are gitignored.

Next: Fund the wallet with testnet ETH from a Base Sepolia faucet.

3. Configure environment

The wallet script pre-fills most values. Review .env and adjust if needed:

# .env (auto-generated, gitignored)
WALLET_ADDRESS=0x...          # from create-wallet
PRIVATE_KEY=0x...             # from create-wallet
NETWORK=eip155:84532          # Base Sepolia (testnet)
RPC_URL=https://sepolia.base.org
BYPASS_PAYMENTS=true          # skip x402 for local dev (auto-disabled in production)
AGENT_NAME=Hello Agent
AGENT_URL=http://localhost:3000
PORT=3000

# CDP Facilitator (required when BYPASS_PAYMENTS=false)
CDP_API_KEY_ID=your-cdp-key-id
CDP_API_KEY_SECRET=your-cdp-key-secret

See Configuration Reference for all available variables.

4. Run locally

npm run dev

5. Test endpoints

# Health check (always free)
curl http://localhost:3000/health

# Agent card with structured entrypoints (always free)
curl http://localhost:3000/.well-known/agent-card.json

# Agent registration linkage (always free)
curl http://localhost:3000/.well-known/agent-registration.json

# API endpoint — returns 200 with BYPASS_PAYMENTS=true, or 402 with payment terms
curl http://localhost:3000/api/hello

With BYPASS_PAYMENTS=true (default for local dev), all endpoints return responses directly. When payments are enabled, unpaid requests return 402 Payment Required with payment terms in the x-payment-required header.

Deploy to AWS Lambda

First-time setup

Make sure you've run npm run create-wallet -- <name> first — it sets function_name in terraform.tfvars, which is required and names all AWS resources.

cd infra && terraform init
terraform apply -target=aws_ecr_repository.agent   # create ECR repo
cd ..

Deploy

npm run deploy

This builds a Docker image, pushes to ECR, and runs terraform apply — your agent is live. Terraform stores your private key in AWS Secrets Manager (not as a Lambda environment variable), so it's encrypted at rest and accessed via IAM at runtime.

flowchart LR
    Build["docker build<br/>(esbuild bundle)"] --> Push["docker push<br/>(ECR)"]
    Push --> Apply["terraform apply<br/>(Lambda + IAM + Secrets)"]
    Apply --> Live["Function URL<br/>(public HTTPS)"]
Loading

Verify

# Get your Function URL from terraform output
cd infra && terraform output function_url

# Test it
curl https://<your-function-url>/health
curl https://<your-function-url>/.well-known/agent-card.json

Register On-Chain Identity (Optional)

ERC-8004 registration mints an NFT that points to your agent's live endpoint via IPFS metadata. This enables on-chain agent discovery. If assets/icon.png exists, it's automatically uploaded to IPFS and included in the metadata.

The register script uses an update-or-create flow: it auto-detects if the wallet already owns an agent on the registry and updates its metadata instead of minting a new token. You can also set AGENT_ID in .env to target a specific token.

1. Get a Pinata JWT

Sign up at pinata.cloud, create an API key, and add the JWT to .env:

PINATA_JWT=your-pinata-jwt

2. Set your agent URL

Point AGENT_URL in .env to your deployed Lambda Function URL (not localhost):

AGENT_URL=https://<your-function-url>

3. Add an icon (optional)

Place a PNG image at assets/icon.png. It will be uploaded to IPFS and included in the on-chain metadata automatically. To use a different path, set AGENT_IMAGE_PATH in .env.

4. Register

npm run register
flowchart LR
    Icon["Upload icon<br/>(assets/icon.png)"] --> Meta["Build metadata<br/>(name, skills, endpoint, image)"]
    Meta --> IPFS["Upload to IPFS<br/>(via Pinata)"]
    IPFS --> Check{"Agent exists?"}
    Check -->|No| Mint["Mint ERC-8004 NFT<br/>(on-chain)"]
    Check -->|Yes| Update["Update token URI<br/>(on-chain)"]
Loading

5. Verify

Anyone can now discover your agent on-chain:

tokenURI(tokenId) → ipfs://... → metadata JSON → services[0].endpoint → your agent

How Payments Work

The x402 protocol uses gasless payments. Clients never submit blockchain transactions — they sign an off-chain EIP-3009 transferWithAuthorization, and the facilitator settles on-chain on their behalf.

sequenceDiagram
    participant Client
    participant Agent
    participant Facilitator
    participant Blockchain

    Client->>Agent: Request (no payment)
    Agent-->>Client: 402 + payment terms

    Note over Client: Sign EIP-3009<br/>transferWithAuthorization<br/>(gasless, off-chain)

    Client->>Agent: Retry + signed authorization
    Agent->>Facilitator: Verify + settle
    Facilitator->>Blockchain: Submit transferWithAuthorization
    Facilitator-->>Agent: Settlement proof

    Agent-->>Client: Response + tx hash
Loading
Endpoint Payment
GET /health Free
GET /.well-known/agent-card.json Free
GET /.well-known/agent-registration.json Free
POST /a2atasks/get, tasks/cancel Free
POST /a2amessage/send, message/stream $0.01 USDC
GET /api/hello $0.01 USDC
POST /api/hello $0.01 USDC

Read-only A2A methods are always free. Only work-producing methods (message/send, message/stream) require payment.

Customize Your Agent

Three files to edit:

src/agent/skills.ts — Define skills

export const skills: AgentSkill[] = [
  {
    id: "my-skill",
    name: "My Skill",
    description: "What this skill does",
    tags: ["tag1", "tag2"],
    examples: ["Do the thing", "Another example"],
  },
];

src/agent/executor.ts — Implement logic

export class MyExecutor implements AgentExecutor {
  async execute(
    requestContext: RequestContext,
    eventBus: ExecutionEventBus,
  ): Promise<void> {
    // Your agent logic here
    const task: Task = {
      kind: "task",
      id: requestContext.taskId,
      contextId: requestContext.contextId,
      status: {
        state: "completed",
        message: {
          kind: "message",
          messageId: uuidv4(),
          role: "agent",
          parts: [{ kind: "text", text: "Your response" }],
          contextId: requestContext.contextId,
        },
      },
    };
    eventBus.publish(task);
    eventBus.publish({
      kind: "status-update",
      taskId: requestContext.taskId,
      contextId: requestContext.contextId,
      status: task.status,
      final: true,
    });
    eventBus.finished();
  }
}

src/routes/api.ts — Add HTTP endpoints

api.get("/my-endpoint", (c) => {
  return c.json({ data: "your response" });
});

New routes under /api/* are automatically payment-gated. Update the price in src/app.ts if needed.

Project Structure

├── src/
│   ├── app.ts              # Composition root — middleware chain + routes
│   ├── config.ts           # Environment config (async for Secrets Manager)
│   ├── server.ts           # Node.js entrypoint (local dev)
│   ├── lambda.ts           # AWS Lambda entrypoint (lazy init)
│   ├── agent/
│   │   ├── card.ts         # AgentCard builder
│   │   ├── entrypoints.ts  # ★ Structured entrypoints (customize this)
│   │   ├── executor.ts     # ★ Task execution logic (customize this)
│   │   └── skills.ts       # ★ Skill definitions (customize this)
│   ├── a2a/
│   │   └── handler.ts      # A2A JSON-RPC handler + agent card endpoint
│   ├── payments/
│   │   └── x402.ts         # x402 payment middleware factory
│   ├── routes/
│   │   ├── api.ts          # ★ Paid HTTP routes (customize this)
│   │   └── health.ts       # Health check
│   └── identity/
│       └── erc8004.ts      # ERC-8004 registration (agent0-sdk)
├── scripts/
│   ├── create-wallet.ts    # Generate wallet + set function_name → .env + terraform.tfvars
│   ├── register-identity.ts# Register on-chain identity
│   └── deploy.sh           # Build + push + terraform apply
├── infra/
│   ├── main.tf             # Lambda, ECR, IAM, Secrets Manager
│   ├── variables.tf        # Terraform variable definitions
│   ├── outputs.tf          # Terraform outputs (function URL, etc.)
│   └── terraform.tfvars.example
├── assets/
│   └── icon.png            # Agent icon (uploaded to IPFS during registration)
├── test/                   # Vitest tests
├── .env.example            # Environment template
├── Dockerfile              # Multi-stage (lambda default, server for ECS)
└── tsconfig.json

Files marked with are the ones you customize.

Configuration Reference

Variable Required Default Description
WALLET_ADDRESS Yes Ethereum wallet address
PRIVATE_KEY Yes (local dev) Private key for signing. Not needed on Lambda if PRIVATE_KEY_SECRET_ARN is set.
PRIVATE_KEY_SECRET_ARN Yes (Lambda) AWS Secrets Manager ARN. Lambda uses this instead of PRIVATE_KEY.
NETWORK No eip155:84532 Chain identifier (Base Sepolia)
RPC_URL No https://sepolia.base.org JSON-RPC endpoint
CDP_API_KEY_ID When payments enabled CDP API key ID for x402 facilitator authentication
CDP_API_KEY_SECRET When payments enabled CDP API key secret for x402 facilitator authentication
BYPASS_PAYMENTS No false Skip x402 payment enforcement. Throws if true in production (NODE_ENV=production).
AGENT_NAME No Hello Agent Agent display name (shown in agent card)
AGENT_DESCRIPTION No A simple Hello World agent Agent description (shown in agent card)
AGENT_URL No http://localhost:3000 Public URL. Set to Function URL when deploying to Lambda.
PORT No 3000 Local dev server port
AGENT_ID No ERC-8004 agent token ID. Set after first registration. Used by register script for updates.
PINATA_JWT For registration Pinata API JWT. Required only for npm run register (ERC-8004).
AGENT_IMAGE_PATH No assets/icon.png Path to agent icon. Uploaded to IPFS during registration if present.
AGENT_PROVIDER_NAME No Provider org name. Requires AGENT_PROVIDER_URL to also be set.
AGENT_PROVIDER_URL No Provider org URL
AGENT_DOCS_URL No Documentation URL (shown in agent card)
AGENT_ICON_URL No Icon URL (shown in agent card)

Network Switching

The same wallet works on both testnet and mainnet. Switch by editing .env and infra/terraform.tfvars:

Base Sepolia (testnet) Base Mainnet
NETWORK eip155:84532 eip155:8453
RPC_URL https://sepolia.base.org https://mainnet.base.org

The CDP facilitator (@coinbase/x402) handles both networks — no URL switching needed. Just set CDP_API_KEY_ID and CDP_API_KEY_SECRET.

After switching, redeploy (npm run deploy) and re-register identity (npm run register) on the new network.

Testing

npm test        # Run all tests
npm run lint    # TypeScript type check

25 tests cover app composition (including JSON-RPC error handling and Zod validation), agent card generation, executor event publishing, config loading (including bypass-payments safety), and Lambda handler setup.

License

MIT

About

Production-ready AI agent template with x402 payments, A2A protocol, and ERC-8004 on-chain identity. Deploys to AWS Lambda via Terraform.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors