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
This guide follows the patterns from the GitHub Repo Analyzer reference agent. All code examples are from that real, working implementation.

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-agent

Install the A2X ecosystem packages:

npm install @a2a-js/sdk @google/adk @google/genai x402 zod
PackagePurpose
@a2a-js/sdkA2A protocol server — JSON-RPC transport, task store, request handling
@google/adkGoogle Agent Development Kit — LLM agent framework
@google/genaiGoogle Generative AI — model interface
x402X402 payment verification and on-chain settlement
zodRuntime 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

The 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

The 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

The lazy singleton pattern prevents build-time failures when environment variables are not yet set. The agent is only instantiated on first use.

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 dev

Verify the Agent Card

curl http://localhost:3000/.well-known/agent.json | jq

You 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 deploy

Verify your deployed agent:

curl https://my-a2x-agent.fly.dev/.well-known/agent.json | jq

Step 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?

Reference

The complete working implementation is available in the GitHub Repo Analyzer reference agent.