Quick Start for Buyers
This section explains how buyers (wallets, AI agents, or applications) can generate valid payment headers that authorize token transfers and authenticate payment when accessing protected resources. For the seller-side implementation, see Quick Start for Sellers.
Prerequisites
Cronos-compatible wallet with private key or browser wallet extension
USDX token balance (see Network Constants for contract addresses)
Integration Steps
Receive 402 Response - Buyer requests resource and receives payment requirements
Generate Payment Header - Create EIP-3009 signature authorizing token transfer
Retry with Payment - Send request with
X-PAYMENTcontaining authorizationReceive Content - Seller settles payment and returns protected resource
Installation
npm install ethersComplete Payment Flow
const axios = require('axios');
const { ethers } = require('ethers');
const { createPaymentHeader } = require('./payment-header-generator');
async function payForResource(resourceUrl, wallet) {
try {
// Step 1: Initial request to resource (expect 402)
const initialResponse = await axios.get(resourceUrl).catch(err => err.response);
// Step 2: Check for 402 Payment Required
if (initialResponse.status !== 402) {
return initialResponse.data;
}
const { paymentRequirements } = initialResponse.data;
// Step 3: Generate payment header (sign EIP-3009 authorization)
const paymentHeaderBase64 = await createPaymentHeader({
wallet: wallet,
paymentRequirements: paymentRequirements,
network: paymentRequirements.network,
});
// Step 4: Retry request with payment header
const paidResponse = await axios.get(resourceUrl, {
headers: { 'X-PAYMENT': paymentHeaderBase64 },
});
return paidResponse.data;
} catch (error) {
throw error;
}
}
// Example usage
async function main() {
const privateKey = process.env.PRIVATE_KEY;
const provider = new ethers.JsonRpcProvider('https://evm-t3.cronos.org');
const wallet = new ethers.Wallet(privateKey, provider);
const resourceUrl = 'https://seller-api.example.com/api/premium-data';
const result = await payForResource(resourceUrl, wallet);
return result;
}
main();Generating Payment Header
const { ethers } = require('ethers');
/**
* Generates a random 32-byte nonce for EIP-3009 authorization
*/
function generateNonce() {
return ethers.hexlify(ethers.randomBytes(32));
}
/**
* Creates a signed payment header for x402 payments
*/
async function createPaymentHeader({ wallet, paymentRequirements, network }) {
const { payTo, asset, maxAmountRequired, maxTimeoutSeconds, scheme } = paymentRequirements;
// Generate unique nonce
const nonce = generateNonce();
// Calculate validity window
const validAfter = 0; // Valid immediately
const validBefore = Math.floor(Date.now() / 1000) + maxTimeoutSeconds;
// Set up EIP-712 domain
const domain = {
name: "USDX Coin",
version: "1",
chainId: "338",
verifyingContract: asset,
};
// Define EIP-712 typed data structure
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' },
],
};
// Create the message to sign
const message = {
from: wallet.address,
to: payTo,
value: maxAmountRequired,
validAfter: validAfter,
validBefore: validBefore,
nonce: nonce,
};
// Sign using EIP-712
const signature = await wallet.signTypedData(domain, types, message);
// Construct payment header
const paymentHeader = {
x402Version: 1,
scheme: scheme,
network: network,
payload: {
from: wallet.address,
to: payTo,
value: maxAmountRequired,
validAfter: validAfter,
validBefore: validBefore,
nonce: nonce,
signature: signature,
asset: asset,
},
};
// Base64-encode
return Buffer.from(JSON.stringify(paymentHeader)).toString('base64');
}
module.exports = { createPaymentHeader, generateNonce, NETWORKS, TOKEN_NAME, TOKEN_VERSION };Common Pitfalls
Common mistakes when implementing the payment header generation:
Wrong Token Metadata
// WRONG - will fail signature verification
const domain = { name: 'USDX', version: '2' };
// CORRECT - must be exact
const domain = { name: 'USDX Coin', version: '1' };Invalid Nonce Format
// WRONG
const nonce = ethers.hexlify(ethers.randomBytes(16)); // Too short
const nonce = '1234567890'; // Not hex
// CORRECT
const nonce = ethers.hexlify(ethers.randomBytes(32)); // 32 bytesDecimal in Amount
// WRONG
const value = '1.5'; // Has decimal
const value = 1500000; // Number type (precision loss)
// CORRECT
const value = '1500000'; // Base units as stringTimestamp in Milliseconds
// WRONG
const validBefore = Date.now() + 300000; // Milliseconds
// CORRECT
const validBefore = Math.floor(Date.now() / 1000) + 300; // SecondsLast updated
Was this helpful?