Skip to content

x402 Protocol

The x402 protocol enables HTTP 402 (Payment Required) responses with cryptocurrency payments. WasiAI uses x402 for pay-per-inference micropayments.


What is x402?

x402 is a payment protocol that:

  1. Extends HTTP: Uses the standard 402 status code
  2. Enables Micropayments: Payments as low as $0.001
  3. Is Gasless for Users: Facilitator pays blockchain gas
  4. Settles Instantly: ~1 second on Avalanche

Why x402?

Traditional payment systems have minimum transaction sizes ($0.50+) due to fees. x402 enables true micropayments by:

  • Using EIP-3009 (transferWithAuthorization) for gasless USDC transfers
  • Leveraging facilitators that batch and pay gas
  • Settling on Avalanche with sub-cent transaction costs

How It Works

High-Level Flow

1. Client requests protected resource
2. Server returns HTTP 402 + payment requirements
3. Client signs USDC authorization (no gas)
4. Client retries with X-PAYMENT header
5. Server verifies payment via facilitator
6. Facilitator executes USDC transfer on-chain
7. Server returns resource + X-PAYMENT-RESPONSE

Detailed Sequence

┌──────────┐          ┌──────────┐          ┌──────────┐          ┌──────────┐
│  Client  │          │  Server  │          │Facilitator│         │   USDC   │
└────┬─────┘          └────┬─────┘          └────┬─────┘          └────┬─────┘
     │                     │                     │                     │
     │ POST /api/inference │                     │                     │
     │────────────────────>│                     │                     │
     │                     │                     │                     │
     │   HTTP 402 + Req    │                     │                     │
     │<────────────────────│                     │                     │
     │                     │                     │                     │
     │ Sign authorization  │                     │                     │
     │ (wallet popup)      │                     │                     │
     │                     │                     │                     │
     │ POST + X-PAYMENT    │                     │                     │
     │────────────────────>│                     │                     │
     │                     │                     │                     │
     │                     │  POST /settle       │                     │
     │                     │────────────────────>│                     │
     │                     │                     │                     │
     │                     │                     │ transferWithAuth()  │
     │                     │                     │────────────────────>│
     │                     │                     │                     │
     │                     │                     │      txHash         │
     │                     │                     │<────────────────────│
     │                     │                     │                     │
     │                     │   { success, tx }   │                     │
     │                     │<────────────────────│                     │
     │                     │                     │                     │
     │  200 OK + Response  │                     │                     │
     │<────────────────────│                     │                     │
     │                     │                     │                     │

Payment Requirement

When a server requires payment, it returns HTTP 402 with a JSON body:

{
  "x402Version": 1,
  "accepts": [
    {
      "scheme": "exact",
      "network": "avalanche-fuji",
      "maxAmountRequired": "10000",
      "resource": "https://wasiai.io/api/inference/1",
      "description": "AI inference on Crypto Sentiment Analyzer",
      "payTo": "0x1234...5678",
      "asset": "0x5425890298aed601595a70AB815c96711a31Bc65",
      "maxTimeoutSeconds": 60
    }
  ],
  "error": "X-PAYMENT header is required"
}

Fields

Field Type Description
scheme string Payment scheme ("exact" = exact amount)
network string Blockchain network identifier
maxAmountRequired string Amount in token base units
resource string URL of the protected resource
description string Human-readable description
payTo string Recipient address
asset string Token contract address (USDC)
maxTimeoutSeconds number Payment validity window

Payment Payload

The client constructs a payment payload and sends it as a base64-encoded X-PAYMENT header:

{
  "x402Version": 1,
  "scheme": "exact",
  "network": "avalanche-fuji",
  "payload": {
    "signature": "0x...",
    "authorization": {
      "from": "0xUserWallet...",
      "to": "0xRecipient...",
      "value": "10000",
      "validAfter": "1702300000",
      "validBefore": "1702300060",
      "nonce": "0x..."
    }
  }
}

Authorization Fields

Field Type Description
from address Payer's wallet address
to address Recipient address (splitter)
value string Amount in base units
validAfter string Unix timestamp (payment valid after)
validBefore string Unix timestamp (payment expires)
nonce bytes32 Unique 32-byte nonce

EIP-3009: TransferWithAuthorization

x402 uses EIP-3009, which allows gasless USDC transfers via signed messages.

How It Works

  1. User signs an EIP-712 typed message authorizing a transfer
  2. Anyone (facilitator) can submit this signature to the USDC contract
  3. USDC contract verifies signature and executes transfer
  4. User pays no gas; facilitator pays gas

EIP-712 Domain

const domain = {
  name: 'USD Coin',
  version: '2',
  chainId: 43113,
  verifyingContract: '0x5425890298aed601595a70AB815c96711a31Bc65'
}

EIP-712 Types

const 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' }
  ]
}

Facilitators

A facilitator is a service that executes USDC transfers on behalf of users.

Ultravioleta DAO (Default)

  • Cost: Free
  • Gas: Covered by Ultravioleta
  • Networks: Avalanche only
  • Endpoint: https://facilitator.ultravioletadao.xyz
// Settlement request
POST https://facilitator.ultravioletadao.xyz/settle
{
  "x402Version": 1,
  "paymentPayload": { ... },
  "paymentRequirements": { ... }
}

Thirdweb

  • Cost: $99/month
  • Gas: Backend wallet pays
  • Networks: Multi-chain
// Direct contract call
const tx = await usdcContract.transferWithAuthorization(
  from, to, value, validAfter, validBefore, nonce, v, r, s
)

Configuration

# .env.local
X402_FACILITATOR_PROVIDER=ultravioleta  # or 'thirdweb'

# For Thirdweb
THIRDWEB_SECRET_KEY=your_key
PRIVATE_KEY=backend_wallet_key

Payment Response

After successful payment, the server returns the resource with an X-PAYMENT-RESPONSE header:

{
  "success": true,
  "transaction": "0xabc123...",
  "network": "avalanche-fuji",
  "payer": "0xUserWallet...",
  "errorReason": null
}

Security

Replay Protection

Each payment has a unique 32-byte nonce. Used nonces are tracked to prevent replay attacks.

const nonce = generateNonce() // 32 random bytes

Time Windows

Payments are only valid within a time window: - validAfter: Payment cannot be used before this time - validBefore: Payment expires after this time

Typical window: 60 seconds.

Amount Validation

Server verifies the payment amount matches the requirement:

if (paymentAmount < requiredAmount) {
  return { valid: false, error: 'Insufficient payment' }
}

Recipient Validation

Payment must go to the correct address:

if (paymentTo !== requirement.payTo) {
  return { valid: false, error: 'Wrong recipient' }
}


Error Handling

Error Cause Solution
Unsupported x402 version Wrong protocol version Update client
Wrong network Network mismatch Switch networks
Nonce already used Replay attempt Generate new nonce
Insufficient payment Amount too low Check pricing
Payment expired Time window passed Sign new authorization
Wrong recipient Address mismatch Check payTo field

Integration Example

Client-Side (TypeScript)

import { useSignTypedData } from 'wagmi'

async function payForInference(modelId: number, input: string) {
  // 1. Get payment requirements
  const res = await fetch(`/api/inference/${modelId}`, {
    method: 'POST',
    body: JSON.stringify({ input })
  })

  if (res.status !== 402) {
    return res.json() // No payment required
  }

  const { accepts } = await res.json()
  const requirement = accepts[0]

  // 2. Build authorization
  const now = Math.floor(Date.now() / 1000)
  const authorization = {
    from: userAddress,
    to: requirement.payTo,
    value: BigInt(requirement.maxAmountRequired),
    validAfter: BigInt(now - 60),
    validBefore: BigInt(now + 60),
    nonce: generateNonce()
  }

  // 3. Sign with wallet
  const signature = await signTypedDataAsync({
    domain: USDC_DOMAIN,
    types: TRANSFER_WITH_AUTHORIZATION_TYPES,
    primaryType: 'TransferWithAuthorization',
    message: authorization
  })

  // 4. Build X-PAYMENT header
  const payload = {
    x402Version: 1,
    scheme: 'exact',
    network: 'avalanche-fuji',
    payload: { signature, authorization }
  }
  const xPayment = btoa(JSON.stringify(payload))

  // 5. Retry with payment
  const paidRes = await fetch(`/api/inference/${modelId}`, {
    method: 'POST',
    headers: { 'X-PAYMENT': xPayment },
    body: JSON.stringify({ input })
  })

  return paidRes.json()
}

Resources