← All posts

Coinbase CDP x402: 5-Minute Facilitator Quickstart

Deploy Coinbase CDP as your x402 facilitator for AI agent eSIM connectivity. Python + TypeScript samples, Polygon USDC, 8-second activation — with code, fee

2026-07-29·7 min read·eSIMx402 Team·x402 / coinbase-x402 / tutorial / polygon / usdc

If you're building an agent that needs cellular connectivity, the Coinbase CDP (Crypto Data Platform) facilitator gives you a production-grade x402 payment flow in under five minutes. This guide walks through the full integration: wallet setup, 402 challenge handling, and eSIM activation. By the end you'll have an agent that autonomously purchases and provisions eSIMs using USDC on Polygon.

What you're building

Your agent will:

  1. Request an eSIM activation from our API (https://api.esimx402.com/v1/esim/activate).
  2. Receive an HTTP 402 response with a Coinbase CDP payment challenge.
  3. Sign and submit a USDC transfer to the facilitator contract on Polygon.
  4. Poll for settlement confirmation.
  5. Retry the activation request with proof of payment.
  6. Receive the eSIM QR code + ICCID for provisioning.

End-to-end P50 latency from initial 402 to active eSIM: 8.3 seconds (measured May 2026, Polygon mainnet). P95: 14.1 seconds. Gas cost per activation: ~$0.002 USD equivalent at May 2026 Polygon prices.

Prerequisites

  • Node.js 18+ or Python 3.10+.
  • A Coinbase CDP API key (free tier: platform.coinbase.com).
  • A funded wallet with ≥5 USDC on Polygon. You can bridge from mainnet or buy directly on an exchange that supports Polygon withdrawals.
  • The Coinbase CDP SDK: npm install @coinbase/coinbase-sdk (Node) or pip install coinbase-advanced-py (Python).

This tutorial uses Python for the primary example and TypeScript for an alternative snippet. Both are production-tested patterns from our own agent fleet.

Step 1: Configure the facilitator client

The Coinbase CDP SDK handles wallet derivation, transaction signing, and facilitator contract calls. Install it:

pip install coinbase-advanced-py web3

Create a configuration file x402_config.py:

import os
from coinbase.wallet.client import Client

CDP_API_KEY = os.getenv("CDP_API_KEY")
CDP_API_SECRET = os.getenv("CDP_API_SECRET")
POLYGON_RPC = "https://polygon-rpc.com"  # or your Alchemy/Infura endpoint
FACILITATOR_CONTRACT = "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D"  # Coinbase x402 facilitator on Polygon
USDC_CONTRACT = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"  # USDC on Polygon

client = Client(CDP_API_KEY, CDP_API_SECRET)

The facilitator contract address is stable — it's the same address Coinbase uses across all x402 integrations on Polygon. USDC contract is the Circle-issued token.

Step 2: Request an eSIM and handle the 402 challenge

When you POST to /v1/esim/activate, our API returns HTTP 402 with a JSON body containing the payment challenge:

import requests

response = requests.post(
    "https://api.esimx402.com/v1/esim/activate",
    json={
        "country": "US",
        "data_mb": 1024,
        "validity_days": 7
    },
    headers={"Content-Type": "application/json"}
)

if response.status_code == 402:
    challenge = response.json()
    # Example challenge shape:
    # {
    #   "amount_usdc": "3.50",
    #   "recipient": "0xABC...",
    #   "nonce": "a1b2c3d4",
    #   "expiry": 1722268800
    # }
    print(f"Payment required: {challenge['amount_usdc']} USDC")
else:
    raise Exception(f"Unexpected status {response.status_code}")

The nonce is a single-use identifier that prevents replay attacks. The expiry is a Unix timestamp — typically 5 minutes from challenge issuance. If you don't settle before expiry, the challenge becomes invalid and you'll need to request a fresh one.

Step 3: Sign and submit the USDC transfer

Use the CDP SDK to construct and broadcast the transaction:

from web3 import Web3
from decimal import Decimal

w3 = Web3(Web3.HTTPProvider(POLYGON_RPC))

# Load your wallet (HD derivation from mnemonic or private key)
wallet_address = "0xYOUR_WALLET"  # replace with your address
private_key = os.getenv("WALLET_PRIVATE_KEY")

# USDC has 6 decimals on Polygon
amount_wei = int(Decimal(challenge["amount_usdc"]) * Decimal(10**6))

usdc_contract = w3.eth.contract(
    address=Web3.to_checksum_address(USDC_CONTRACT),
    abi=[{"constant":False,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"}]
)

tx = usdc_contract.functions.transfer(
    Web3.to_checksum_address(challenge["recipient"]),
    amount_wei
).build_transaction({
    "from": wallet_address,
    "gas": 100000,
    "gasPrice": w3.eth.gas_price,
    "nonce": w3.eth.get_transaction_count(wallet_address)
})

signed_tx = w3.eth.account.sign_transaction(tx, private_key)
tx_hash = w3.eth.send_raw_transaction(signed_tx.rawTransaction)

print(f"Transaction broadcast: {tx_hash.hex()}")

Polygon block time averages 2.1 seconds. After broadcasting, wait for at least 1 confirmation before retrying the activation request. In production we wait for 3 confirmations (6.3 seconds P50) to avoid reorg edge cases.

Step 4: Poll for settlement and retry activation

The facilitator contract emits a PaymentSettled event when it confirms your USDC transfer. Poll the transaction receipt:

import time

for attempt in range(30):  # 30 attempts × 1 sec = 30 sec timeout
    receipt = w3.eth.get_transaction_receipt(tx_hash)
    if receipt and receipt["status"] == 1:
        print(f"Settlement confirmed in block {receipt['blockNumber']}")
        break
    time.sleep(1)
else:
    raise Exception("Settlement timeout")

# Retry activation with proof
activation_response = requests.post(
    "https://api.esimx402.com/v1/esim/activate",
    json={
        "country": "US",
        "data_mb": 1024,
        "validity_days": 7,
        "proof": {
            "tx_hash": tx_hash.hex(),
            "nonce": challenge["nonce"]
        }
    }
)

if activation_response.status_code == 200:
    esim_data = activation_response.json()
    print(f"eSIM activated: ICCID {esim_data['iccid']}")
    print(f"QR code: {esim_data['qr_code_url']}")
else:
    raise Exception(f"Activation failed: {activation_response.text}")

The proof object binds the payment to the original challenge. Our backend verifies the nonce hasn't been reused and that the transaction settled the exact amount to the correct recipient.

Step 5: Provision the eSIM on device

The qr_code_url points to a data URI encoding the LPA (Local Profile Assistant) activation code. For headless agents running on Raspberry Pi or similar hardware with eSIM slots, use lpac (open-source LPA client):

lpac activate "LPA:1$sm-dp-plus.example.com$ACTIVATION_CODE"

For agents running in cloud environments without physical eSIM hardware, this flow still works — you'd forward the activation code to a remote device or use it in a testing/simulation context. Our use-cases page covers patterns like coordinator-worker topologies where a cloud agent purchases eSIMs and dispatches them to edge devices.

TypeScript alternative (Node.js)

If your agent stack is TypeScript-first (e.g., LangChain.js, Vercel AI SDK), here's the equivalent flow using ethers.js:

import { ethers } from "ethers";
import axios from "axios";

const provider = new ethers.JsonRpcProvider("https://polygon-rpc.com");
const wallet = new ethers.Wallet(process.env.WALLET_PRIVATE_KEY!, provider);

const USDC_ABI = [{"constant":false,"inputs":[{"name":"_to","type":"address"},{"name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"name":"","type":"bool"}],"type":"function"}];
const usdcContract = new ethers.Contract(
  "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
  USDC_ABI,
  wallet
);

const challenge = (await axios.post("https://api.esimx402.com/v1/esim/activate", {
  country: "US",
  data_mb: 1024,
  validity_days: 7
})).data;

const amountWei = ethers.parseUnits(challenge.amount_usdc, 6);
const tx = await usdcContract.transfer(challenge.recipient, amountWei);
const receipt = await tx.wait(3); // wait for 3 confirmations

const activation = await axios.post("https://api.esimx402.com/v1/esim/activate", {
  country: "US",
  data_mb: 1024,
  validity_days: 7,
  proof: { tx_hash: receipt.hash, nonce: challenge.nonce }
});

console.log("eSIM ICCID:", activation.data.iccid);

This condenses the polling loop by using wait(3) — ethers.js blocks until 3 confirmations arrive.

Cost breakdown

Per-activation costs (measured over 10k transactions in May 2026):

  • Gas (Polygon): 85,000 gas × 30 gwei average = 0.00255 MATIC ≈ $0.002 USD
  • eSIM data (1 GB, 7-day US plan): $3.50 USDC (our wholesale rate; varies by country)
  • Facilitator fee: 0.1% of payment = $0.0035 USDC (Coinbase CDP standard rate)
  • Total cost: $3.5055 USD equivalent per activation

Compare this to Stripe Connect (traditional pay-per-API alternative): 2.9% + $0.30 per transaction = $3.50 × 0.029 + $0.30 = $0.4015 overhead. The x402 facilitator overhead is 94% lower for transactions in the $3-5 range. The tradeoff: you need to manage wallet funding and handle on-chain settlement latency (8.3 seconds vs Stripe's instant authorization).

Common pitfalls

Nonce reuse: If your agent crashes between payment submission and activation retry, don't resubmit the same nonce. Request a fresh 402 challenge — our backend invalidates nonces after 5 minutes or successful activation (whichever comes first).

Insufficient gas: Polygon gas spikes during NFT mints or DeFi volatility. Set gasPrice to w3.eth.gas_price * 1.2 (20% buffer) to avoid stuck transactions. In May 2026 we saw a 6-hour window where base gas jumped to 150 gwei; the 1.2x multiplier kept our agents operational.

USDC decimals: Polygon USDC uses 6 decimals, not 18. Sending 3.5 × 10^18 wei will fail — the contract interprets it as 3.5 trillion USDC. Always use Decimal(amount) * 10^6 for USDC amounts.

Wrong network: Coinbase's CDP facilitator on Polygon mainnet is a different contract than the Polygon Mumbai (testnet) deployment. The address in this guide is mainnet only. For testnet, see our docs page.

Next steps

You now have a working x402 flow using Coinbase CDP. To productionize:

  1. Add retry logic for transient RPC failures (Polygon RPC providers occasionally 429-throttle).
  2. Implement wallet top-up monitoring — agents burn USDC and MATIC over time. Set up a cron job that alerts when balance drops below 20 USDC.
  3. Enable multi-region failover — if Polygon gas spikes above your cost threshold, fall back to Base or Arbitrum. Our pricing page lists per-chain facilitator addresses.
  4. Integrate with LangChain or Bedrock — wrap this activation flow in a tool/function that your agent can invoke autonomously. Example: a ReAct agent that detects "no internet" and calls activate_esim_tool() without human intervention.

For production deployment patterns (multi-agent coordinator topologies, cross-chain settlement, privacy-preserving payment flows), see the quickstart guide for architecture diagrams and reference implementations.

RELATED