Skip to main content
This example demonstrates how to make x402 payments on Base Sepolia testnet using the exact payment scheme. This uses EIP-3009 (Transfer with Authorization) for gasless USDC transfers. Source: GitHub › scripts/evm-example/base-sepolia-payment.ts

What is EIP-3009?

EIP-3009 enables gasless payments by allowing you to:
  1. Sign an authorization message off-chain (EIP-712)
  2. A facilitator submits the transaction and pays gas
  3. The USDC contract verifies your signature and executes the transfer
This is perfect for micro-payments where gas costs would exceed the payment amount.

Full Example

import "dotenv/config";
import { logger, logResponse } from "../logger";
import { createLocalWallet } from "@faremeter/wallet-evm";
import { createPaymentHandler } from "@faremeter/payment-evm/exact";
import { wrap as wrapFetch } from "@faremeter/fetch";
import { baseSepolia } from "viem/chains";

const { EVM_PRIVATE_KEY } = process.env;

if (!EVM_PRIVATE_KEY) {
  throw new Error("EVM_PRIVATE_KEY must be set in your environment");
}

// Parse command line arguments
const args = process.argv.slice(2);
const port = args[0] ?? "4021";
const endpoint = args[1] ?? "weather";
const url = `http://localhost:${port}/${endpoint}`;

logger.info("Creating wallet for Base Sepolia USDC payments...");
const wallet = await createLocalWallet(baseSepolia, EVM_PRIVATE_KEY);
logger.info(`Wallet address: ${wallet.address}`);

const fetchWithPayer = wrapFetch(fetch, {
  handlers: [createPaymentHandler(wallet)],
});

logger.info(`Making payment request to ${url}...`);
const req = await fetchWithPayer(url);
await logResponse(req);

Step-by-Step Breakdown

1. Load Private Key

const { EVM_PRIVATE_KEY } = process.env;

if (!EVM_PRIVATE_KEY) {
  throw new Error("EVM_PRIVATE_KEY must be set in your environment");
}
Your private key must be:
  • 64-character hex string
  • Prefixed with 0x
  • Example: 0x1234567890abcdef...

2. Create Wallet

const wallet = await createLocalWallet(baseSepolia, EVM_PRIVATE_KEY);
This creates a wallet interface that:
  • Uses your private key for signing
  • Connects to Base Sepolia RPC
  • Implements EIP-712 typed data signing (required for EIP-3009)

3. Create Payment Handler

const fetchWithPayer = wrapFetch(fetch, {
  handlers: [createPaymentHandler(wallet)],
});
The payment handler:
  • Defaults to USDC on the wallet’s chain
  • Signs EIP-3009 authorization messages
  • Uses EIP-712 typed data for secure signing

4. Make Payment

const req = await fetchWithPayer(url);
When a 402 response is received:
  1. The handler creates an EIP-3009 authorization
  2. Signs it using EIP-712 typed data
  3. Returns the authorization + signature
  4. The facilitator submits the transaction

Payment Flow

Custom Asset Selection

By default, the handler uses USDC. To specify a different asset:
const wallet = await createLocalWallet(baseSepolia, EVM_PRIVATE_KEY);

const fetchWithPayer = wrapFetch(fetch, {
  handlers: [
    createPaymentHandler(wallet, {
      asset: "USDC", // or custom contract info
    }),
  ],
});
Supported assets come from @faremeter/info/evm. You can also provide custom contract information.

Command Line Arguments

  • First argument: Server port (default: 4021)
  • Second argument: Endpoint path (default: weather)
Example:
tsx base-sepolia-payment.ts 4021 weather

EIP-712 Signing Details

The authorization is signed using EIP-712 typed data: Domain:
{
  name: "USD Coin",
  version: "2",
  chainId: 84532,  // Base Sepolia
  verifyingContract: "0x..." // USDC contract
}
Types:
{
  TransferWithAuthorization: [
    { name: "from", type: "address" },
    { name: "to", type: "address" },
    { name: "value", type: "uint256" },
    { name: "validAfter", type: "uint256" },
    { name: "validBefore", type: "uint256" },
    { name: "nonce", type: "bytes32" }
  ]
}
Message:
{
  from: "0x...",        // Your wallet address
  to: "0x...",          // Merchant address
  value: 1000000n,      // Amount (6 decimals, so 1 USDC = 1000000)
  validAfter: 1234567890n,
  validBefore: 1234568190n,
  nonce: "0x..."        // Random 32-byte nonce
}

Environment Variables

  • EVM_PRIVATE_KEY: Your EVM private key (0x-prefixed hex string)

Funding Your Wallet

Make sure your wallet has:
  • USDC: Sufficient USDC for payments (Base Sepolia USDC from faucet)
  • ETH: For initial setup (optional if using facilitator for gas)
You can get Base Sepolia ETH from: