Quick Start: Deploy Your First A2X Agent
Build and deploy a paid AI agent in 30 minutes using the A2A protocol and X402 micropayments.
By the end of this guide, you'll have a working agent that:
- Exposes an AI skill via the A2A protocol
- Charges USDC micropayments via X402
- Is discoverable through the Agent Registry
Prerequisites
- Node.js 20+
- A Google AI Studio API key (for Gemini) — Get one here
- A GitHub OAuth App with Device Flow enabled — Create one here
- A crypto wallet address on Base Sepolia to receive payments
Step 1: Project Setup
Create a new Next.js project and install dependencies:
npx create-next-app@latest my-a2x-agent --typescript --app
cd my-a2x-agentInstall the A2X ecosystem packages:
npm install @a2a-js/sdk @google/adk @google/genai x402 zod| Package | Purpose |
|---|---|
@a2a-js/sdk | A2A protocol server — JSON-RPC transport, task store, request handling |
@google/adk | Google Agent Development Kit — LLM agent framework |
@google/genai | Google Generative AI — model interface |
x402 | X402 payment verification and on-chain settlement |
zod | Runtime validation for inputs |
Step 2: Environment Configuration
Create a .env file in your project root:
# LLM
GEMINI_API_KEY=your_gemini_api_key
# GitHub OAuth (for user authentication)
GITHUB_CLIENT_ID=your_github_client_id
# Server
NEXT_PUBLIC_BASE_URL=http://localhost:3000
APP_NAME=my-a2x-agent
# X402 Payment Configuration (JSON array)
# amount is in USDC minimum units: 1000 = 0.001 USDC
X402_BASE_FEE=[{"network":"base-sepolia","asset":"0x036CbD53842c5426634e7929541eC2318f3dCF7e","payTo":"YOUR_WALLET_ADDRESS","amount":"1000","eip712Name":"USDC","eip712Version":"2"}]Tip
amount field uses 6 decimal places (USDC standard). 1000 = 0.001 USDC, 1000000 = 1.00 USDC.Step 3: Agent Card Configuration
The Agent Card is your agent's identity — it tells clients what your agent does, how to authenticate, and what payment is required.
Create src/lib/a2a/agent-card.json:
{
"name": "My A2X Agent",
"description": "A paid AI agent that does amazing things.",
"protocolVersion": "0.3.0",
"version": "1.0.0",
"url": "http://localhost:3000/api/a2a",
"securitySchemes": {
"github_oauth": {
"type": "oauth2",
"description": "GitHub OAuth2 Device Flow authentication.",
"flows": {
"deviceCode": {
"deviceAuthorizationUrl": "http://localhost:3000/auth/device",
"tokenUrl": "http://localhost:3000/auth/token"
}
}
}
},
"security": [{ "github_oauth": [] }],
"skills": [
{
"id": "my-skill",
"name": "My Skill",
"description": "Describe what this skill does clearly — clients use this to decide whether to call your agent.",
"tags": ["example"],
"inputModes": ["text/plain"],
"outputModes": ["text/plain"]
}
],
"capabilities": {
"streaming": true,
"extensions": [
{
"uri": "https://github.com/google-agentic-commerce/a2a-x402/blob/main/spec/v0.2",
"required": true
}
]
},
"defaultInputModes": ["text/plain"],
"defaultOutputModes": ["text/plain"]
}Key Point
extensions array declares X402 support. Setting required: true means clients must implement X402 to use your agent.Step 4: X402 Payment Module
Payment Types (src/lib/x402/types.ts)
Define the data structures for the X402 payment lifecycle:
export interface PaymentRequirements {
scheme: 'exact';
network: string;
maxAmountRequired: string;
asset: string;
payTo: string;
resource: string;
mimeType: string;
maxTimeoutSeconds: number;
extra: {
name: string;
version: string;
};
}
export interface X402PaymentRequiredResponse {
x402Version: 1;
accepts: PaymentRequirements[];
}
export interface PaymentPayload {
x402Version: 1;
network: string;
scheme: string;
payload: {
signature: string;
authorization: {
from: string;
to: string;
value: string;
validAfter: string;
validBefore: string;
nonce: string;
};
};
}
export interface X402SettleResponse {
success: boolean;
transaction: string;
network: string;
errorReason?: string;
}
export const X402_METADATA_KEYS = {
STATUS: 'x402.payment.status',
REQUIRED: 'x402.payment.required',
PAYLOAD: 'x402.payment.payload',
RECEIPTS: 'x402.payment.receipts',
ERROR: 'x402.payment.error',
} as const;Payment Config (src/lib/x402/config.ts)
Parse the X402_BASE_FEE environment variable:
export interface BaseFeeEntry {
network: string;
asset: string;
payTo: string;
amount: string;
eip712Name: string;
eip712Version: string;
}
export interface X402Config {
baseFees: BaseFeeEntry[];
}
export function getX402Config(): X402Config {
const raw = process.env.X402_BASE_FEE;
if (!raw) throw new Error('X402_BASE_FEE environment variable is required');
const entries = JSON.parse(raw);
if (!Array.isArray(entries) || entries.length === 0) {
throw new Error('X402_BASE_FEE must be a non-empty JSON array');
}
return {
baseFees: entries.map((e) => ({
network: e.network,
asset: e.asset,
payTo: e.payTo,
amount: e.amount,
eip712Name: e.eip712Name ?? 'USDC',
eip712Version: e.eip712Version ?? '2',
})),
};
}Step 5: AI Agent Definition
Define your LLM agent using Google ADK. This is where your agent's intelligence lives.
Create src/lib/a2a/agent.ts:
import { Gemini, InMemorySessionService, LlmAgent, Runner } from '@google/adk';
export const APP_NAME = 'my-a2x-agent';
let _sessionService: InMemorySessionService | null = null;
let _runner: Runner | null = null;
export function getSessionService(): InMemorySessionService {
if (!_sessionService) {
_sessionService = new InMemorySessionService();
}
return _sessionService;
}
export function getRunner(): Runner {
if (!_runner) {
const agent = new LlmAgent({
name: APP_NAME,
model: new Gemini({ model: 'gemini-2.5-pro' }),
description: 'Describe your agent here',
instruction: `Your agent's system prompt goes here.
Define how it should behave and what tools to use.`,
tools: [/* your tools here */],
});
_runner = new Runner({
appName: APP_NAME,
agent,
sessionService: getSessionService(),
});
}
return _runner;
}Note
Step 6: A2A Handler Implementation
The Executor (src/lib/a2a/a2a-executor.ts)
The executor is the core of your agent. It handles the 2-stage flow: request payment then execute agent.
import { isFinalResponse } from '@google/adk';
import { createUserContent, createPartFromText } from '@google/genai';
import type { Message, Task, TaskStatusUpdateEvent } from '@a2a-js/sdk';
import type { AgentExecutor, ExecutionEventBus, RequestContext } from '@a2a-js/sdk/server';
import { useFacilitator } from 'x402/verify';
import { APP_NAME, getRunner, getSessionService } from './agent';
import { getX402Config } from '../x402/config';
import { X402_METADATA_KEYS } from '../x402/types';
const { verify: verifyPayment, settle: settlePayment } = useFacilitator();
export class AdkAgentExecutor implements AgentExecutor {
async execute(requestContext: RequestContext, eventBus: ExecutionEventBus): Promise<void> {
const { userMessage, taskId, contextId } = requestContext;
const metadata = (userMessage.metadata ?? {}) as Record<string, unknown>;
const paymentStatus = metadata[X402_METADATA_KEYS.STATUS] as string | undefined;
// Stage 1: First request — ask for payment
// Stage 2: Payment submitted — verify, settle, then execute
if (paymentStatus === 'payment-submitted') {
await this.handlePaymentSubmission(requestContext, eventBus, metadata);
} else {
this.requestPayment(taskId, contextId, eventBus);
}
}
}The key insight: the executor checks x402.payment.statusin the incoming message metadata to determine which stage of the flow it's in.
The Handler (src/lib/a2a/a2a-handler.ts)
Wire up the A2A SDK components:
import type { AgentCard } from '@a2a-js/sdk';
import {
DefaultRequestHandler,
InMemoryTaskStore,
JsonRpcTransportHandler,
} from '@a2a-js/sdk/server';
import { AdkAgentExecutor } from './a2a-executor';
import agentCardJson from './agent-card.json';
const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL ?? 'http://localhost:3000';
export const agentCard: AgentCard = {
...(agentCardJson as Omit<AgentCard, 'url'>),
url: `${BASE_URL}/api/a2a`,
};
const taskStore = new InMemoryTaskStore();
const executor = new AdkAgentExecutor();
const requestHandler = new DefaultRequestHandler(agentCard, taskStore, executor);
export const transportHandler = new JsonRpcTransportHandler(requestHandler);The Route (src/app/api/a2a/route.ts)
Expose the A2A endpoint as a Next.js API route:
import { NextRequest, NextResponse } from 'next/server';
import { transportHandler } from '@/lib/a2a/a2a-handler';
export const dynamic = 'force-dynamic';
export async function POST(req: NextRequest) {
const body = await req.json();
const result = await transportHandler.handle(body);
// Handle streaming responses (SSE)
if (result && Symbol.asyncIterator in Object(result)) {
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
for await (const chunk of result as AsyncGenerator) {
controller.enqueue(encoder.encode(`data: ${JSON.stringify(chunk)}\n\n`));
}
controller.close();
},
});
return new Response(stream, {
headers: { 'Content-Type': 'text/event-stream' },
});
}
return NextResponse.json(result);
}Serve the Agent Card (src/app/.well-known/agent.json/route.ts)
import { NextResponse } from 'next/server';
import { agentCard } from '@/lib/a2a/a2a-handler';
export async function GET() {
return NextResponse.json(agentCard);
}Step 7: Local Testing
Start the development server:
npm run devVerify the Agent Card
curl http://localhost:3000/.well-known/agent.json | jqYou should see your agent card with skills, capabilities, and the X402 extension declared.
Send a Task
curl -X POST http://localhost:3000/api/a2a \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_GITHUB_TOKEN" \
-d '{
"jsonrpc": "2.0",
"method": "message/send",
"id": "test-001",
"params": {
"message": {
"role": "user",
"parts": [{"kind": "text", "text": "Hello agent!"}]
}
}
}'The response should include x402.payment.status: "payment-required"with payment requirements — confirming the payment gate is working.
Step 8: Deploy to Fly.io
Create a Dockerfile in your project root:
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build
FROM node:20-alpine AS runner
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
ENV PORT=3000
EXPOSE 3000
CMD ["node", "server.js"]Update next.config.ts for standalone output:
const nextConfig = {
output: 'standalone',
};
export default nextConfig;Deploy:
# Install Fly CLI if needed
curl -L https://fly.io/install.sh | sh
# Launch your app
fly launch --name my-a2x-agent
# Set environment variables
fly secrets set GEMINI_API_KEY=your_key
fly secrets set GITHUB_CLIENT_ID=your_client_id
fly secrets set NEXT_PUBLIC_BASE_URL=https://my-a2x-agent.fly.dev
fly secrets set X402_BASE_FEE='[{"network":"base-sepolia","asset":"0x036CbD53842c5426634e7929541eC2318f3dCF7e","payTo":"YOUR_WALLET","amount":"1000","eip712Name":"USDC","eip712Version":"2"}]'
# Deploy
fly deployVerify your deployed agent:
curl https://my-a2x-agent.fly.dev/.well-known/agent.json | jqStep 9: Register with Agent Registry
Register your agent so it can be discovered by Personal Agents:
curl -X POST https://a2a-agent-registry.fly.dev/agents \
-H "Content-Type: application/json" \
-d '{
"name": "My A2X Agent",
"description": "A paid AI agent that does amazing things.",
"endpoint": "https://my-a2x-agent.fly.dev/api/a2a",
"agentCardUrl": "https://my-a2x-agent.fly.dev/.well-known/agent.json"
}'Your agent is now live and discoverable in the A2X ecosystem.
What's Next?
Architecture
Understand the 5-layer agent architecture.
A2A Protocol
Deep dive into the protocol details.
X402 Payments
Learn the payment state machine.
Agent Card Guide
Advanced agent card configuration.
Reference
The complete working implementation is available in the GitHub Repo Analyzer reference agent.