Skip to content

x402 Payment Protocol

The x402 protocol enables micropayments for API operations. This guide explains how payments work in Grapevine.

What is x402?

x402 is a payment protocol that allows:

  • Micropayments - Pay only for what you use
  • On-chain settlements - Payments on Base blockchain
  • USDC payments - Stablecoin for predictable costs
  • Automatic processing - Seamless integration

When Payments Are Required

Payments are required for:

  • Creating feeds
  • Creating entries
  • Some premium operations

Read operations (GET) are always free.

Payment Flow

1. Initial Request Returns 402

const response = await fetch(`${API_URL}/v1/feeds`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    ...authHeaders
  },
  body: JSON.stringify({ name: 'My Feed' })
});
 
// Response status: 402 Payment Required

2. Parse Payment Requirements

if (response.status === 402) {
  const paymentData = await response.json();
  
  // Payment data structure:
  // {
  //   "x402Version": "0",
  //   "accepts": [
  //     {
  //       "scheme": "exact",
  //       "asset": "0x036CbD53842c5426634e7929541eC2318f3dCF7e",
  //       "network": "base-sepolia",
  //       "maxAmountRequired": "1000000"
  //     }
  //   ]
  // }
}

3. Create Payment Header

import { createPaymentHeader, selectPaymentRequirements } from 'x402/client';
import { PaymentRequirementsSchema } from 'x402/types';
import { facilitator } from '@coinbase/x402';
 
// Parse and select requirements
const requirements = paymentData.accepts.map(x => 
  PaymentRequirementsSchema.parse(x)
);
 
const selected = selectPaymentRequirements(
  requirements,
  ['base-sepolia'], // or ['base'] for mainnet
  'exact'
);
 
// Create payment header
const paymentHeader = await createPaymentHeader(
  walletClient,
  parseInt(paymentData.x402Version),
  selected,
  facilitator
);

4. Retry with Payment

const paidResponse = await fetch(`${API_URL}/v1/feeds`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    ...authHeaders,
    'X-PAYMENT': paymentHeader
  },
  body: JSON.stringify({ name: 'My Feed' })
});
 
// Check for payment confirmation
if (paidResponse.headers.get('X-PAYMENT-RESPONSE')) {
  console.log('Payment processed successfully');
}
 
const feed = await paidResponse.json();

Complete Helper Function

async function makeRequestWithPayment(
  url: string, 
  options: RequestInit
): Promise<Response> {
  const response = await fetch(url, options);
  
  if (response.status === 402) {
    const paymentData = await response.json();
    const x402Version = paymentData.x402Version;
    const accepts = paymentData.accepts;
    
    // Parse requirements
    const requirements = accepts.map(x => 
      PaymentRequirementsSchema.parse(x)
    );
    
    // Select matching network
    const selected = selectPaymentRequirements(
      requirements,
      [network], // 'base' or 'base-sepolia'
      'exact'
    );
    
    if (!selected) {
      throw new Error(`No payment requirements match network: ${network}`);
    }
    
    // Create payment
    const paymentHeader = await createPaymentHeader(
      walletClient,
      parseInt(x402Version),
      selected,
      facilitator
    );
    
    // Retry with payment
    return fetch(url, {
      ...options,
      headers: {
        ...options.headers,
        'X-PAYMENT': paymentHeader
      }
    });
  }
  
  return response;
}

Usage Example

// Use the helper for any request that might require payment
const feed = await makeRequestWithPayment(
  `${API_URL}/v1/feeds`,
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      ...authHeaders
    },
    body: JSON.stringify({
      name: 'My Feed',
      description: 'Test feed'
    })
  }
);

Payment Costs

Feed Creation

  • Testnet: Varies (test USDC)
  • Mainnet: Varies (real USDC)

Entry Creation

  • Testnet: Varies (test USDC)
  • Mainnet: Varies (real USDC)

Costs are returned in the 402 response under maxAmountRequired.

Supported Networks

Base Sepolia (Testnet)

  • Network: base-sepolia
  • Chain ID: 84532
  • Currency: Test USDC
  • API: https://api.grapevine.markets

Base (Mainnet)

  • Network: base
  • Chain ID: 8453
  • Currency: USDC
  • API: https://api.grapevine.fyi

Getting USDC

Testnet (Base Sepolia)

  1. Get ETH from Alchemy Faucet
  2. Bridge USDC from Ethereum Sepolia
  3. Or contact Grapevine support for test tokens

Mainnet (Base)

  1. Buy USDC on an exchange
  2. Bridge to Base network
  3. Send to your wallet address

Error Handling

No Matching Network

const selected = selectPaymentRequirements(
  requirements,
  [network],
  'exact'
);
 
if (!selected) {
  throw new Error(
    `Payment requires ${requirements[0].network} but wallet is on ${network}`
  );
}

Insufficient Balance

try {
  const paymentHeader = await createPaymentHeader(...);
} catch (error) {
  if (error.message.includes('insufficient')) {
    console.error('Insufficient USDC balance');
    // Prompt user to add funds
  }
}

Payment Failed

const response = await fetch(url, {
  headers: { 'X-PAYMENT': paymentHeader }
});
 
if (!response.ok) {
  const error = await response.json();
  console.error('Payment failed:', error);
}

Testing Payments

Use the test script to verify payment flow:

# Run on testnet
API_URL=https://api.grapevine.markets bun test-grapevine.ts

The script will:

  1. Create a feed (requires payment)
  2. Create entries (requires payment)
  3. Log payment details
  4. Verify successful transactions

Next Steps