This document describes how to enable and use the x402 v2 payment protocol in the Solana MCP Server.
The x402 v2 payment protocol enables monetization of MCP tool calls and resources. When enabled, the server can require payment before executing certain operations, with payments verified and settled through a facilitator service.
The implementation follows the canonical x402 v2 specification: https://github.com/coinbase/x402/blob/ce5085245c55c1a76416e445403cc3e10169b2e4/specs/x402-specification-v2.md
This section helps Web3 developers understand how x402 integrates blockchain payments with MCP (Model Context Protocol).
x402 is a protocol for monetizing API access using blockchain payments. Think of it as "HTTP 402 Payment Required" but for Web3:
- Traditional Web2: HTTP 402 status code (rarely used) - client needs to pay via credit card/PayPal
- Web3 x402: Client pays with crypto (USDC, SOL, etc.) via blockchain transactions
- Verification: Payment is verified on-chain before the API executes the request
CAIP-2 is a standard for identifying blockchain networks in a human-readable format:
<namespace>:<reference>
Examples:
- Solana Mainnet:
solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp - Solana Devnet:
solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1 - Ethereum Mainnet:
eip155:1 - Polygon:
eip155:137
Why it matters: Your server can support multiple chains, and clients specify which chain they're paying on.
On Solana, payments use SPL tokens (like USDC):
- Token Mint Address: The on-chain program that controls the token (e.g., USDC:
EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v) - Token Account: Each wallet has an Associated Token Account (ATA) for each token type
- Transfer: Payment moves tokens from payer's ATA to your ATA
Decimals Matter:
- USDC has 6 decimals:
1000000units = 1 USDC - SOL has 9 decimals:
1000000000lamports = 1 SOL - Always specify amounts in the smallest unit (like wei in Ethereum)
Solana uses Compute Units instead of gas:
- Compute Unit Limit: Max compute budget for the transaction (like gas limit)
- Compute Unit Price: Price per compute unit in microlamports (like gas price)
- Total Fee:
compute_units_used × compute_unit_pricemicrolamports
Why this matters for x402:
- Prevents clients from setting extremely high prices (gas griefing)
- You configure min/max compute unit price bounds
- Example:
min: 1000, max: 50000microlamports per CU
The facilitator is a trusted service that:
- Verifies payment authorizations (simulates transaction without broadcasting)
- Settles payments (broadcasts transaction to blockchain)
- Returns transaction signatures and settlement receipts
Why use a facilitator?
- Your API server doesn't need to manage private keys
- Facilitator handles transaction complexity
- Separates payment logic from business logic
- Can batch multiple payments for efficiency
Facilitator Endpoints:
POST /verify- Check if payment is valid (no blockchain interaction)POST /settle- Execute payment on blockchainGET /supported- List supported networks and payment schemes
Step 1: Client Request (No Payment)
// Client makes request without payment
const response = await fetch('https://api.example.com/mcp', {
method: 'POST',
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'getBalance',
arguments: { pubkey: 'Gh9Z...' }
}
})
});Step 2: Server Returns Payment Required
{
"error": {
"code": -40200,
"message": "Payment required",
"data": {
"x402Version": 2,
"accepts": [{
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"amount": "10000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "FeeRecipient...",
"maxTimeoutSeconds": 60
}]
}
}
}Step 3: Client Creates Payment Transaction
// Using @solana/web3.js
import { Connection, Transaction, SystemProgram } from '@solana/web3.js';
import { createTransferCheckedInstruction } from '@solana/spl-token';
// 1. Create transaction with required instructions
const transaction = new Transaction();
// Add compute budget instructions
transaction.add(
ComputeBudgetProgram.setComputeUnitLimit({ units: 200000 }),
ComputeBudgetProgram.setComputeUnitPrice({ microLamports: 5000 })
);
// Add token transfer instruction
transaction.add(
createTransferCheckedInstruction(
sourceATA, // Your token account
tokenMint, // USDC mint
destATA, // Server's token account
wallet.publicKey, // Your wallet
10000, // Amount (0.01 USDC)
6 // Decimals
)
);
// 2. Sign transaction
transaction.feePayer = wallet.publicKey;
transaction.recentBlockhash = (await connection.getLatestBlockhash()).blockhash;
await wallet.signTransaction(transaction);
// 3. Serialize transaction
const serializedTx = transaction.serialize().toString('base64');Step 4: Client Retries with Payment
const retryResponse = await fetch('https://api.example.com/mcp', {
method: 'POST',
body: JSON.stringify({
jsonrpc: '2.0',
id: 1,
method: 'tools/call',
params: {
name: 'getBalance',
arguments: { pubkey: 'Gh9Z...' },
_meta: {
payment: {
x402Version: 2,
accepted: {
scheme: 'exact',
network: 'solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp',
amount: '10000',
asset: 'EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v',
payTo: 'FeeRecipient...',
maxTimeoutSeconds: 60
},
payload: {
transaction: serializedTx,
signature: transaction.signatures[0].signature.toString('base64')
}
}
}
}
})
});Step 5: Server Verifies and Settles
- Server sends payment to facilitator
/verifyendpoint - Facilitator simulates transaction (checks balance, signature, etc.)
- If valid, facilitator broadcasts to blockchain via
/settle - Server executes the paid operation
- Returns result with settlement receipt
Transaction Validation:
- ✅ Signature verification: Proves the wallet owner authorized payment
- ✅ Amount validation: Ensures payment matches requirements exactly
- ✅ Destination validation: Confirms payment goes to correct address
- ✅ Timeout validation: Prevents replay attacks with old transactions
- ✅ Compute unit bounds: Prevents excessive gas price attacks
Fee Payer Rules:
- The facilitator pays blockchain fees (gas)
- Facilitator's wallet must NOT be the source of payment tokens
- This prevents the facilitator from paying itself
- Enforced in SVM exact scheme validation
Replay Protection:
- Use
maxTimeoutSecondsto limit transaction validity - Recent blockhash ensures transaction is current
- Signature is unique per transaction
Pattern 1: Wallet Integration
// Using Phantom wallet (Solana)
const connectWallet = async () => {
if (window.solana) {
await window.solana.connect();
return window.solana;
}
throw new Error('Phantom wallet not installed');
};Pattern 2: Multi-Wallet Support
// Support multiple wallets
const wallets = {
phantom: window.solana,
solflare: window.solflare,
// etc.
};Pattern 3: Token Account Creation
// Create ATA if it doesn't exist
import { getAssociatedTokenAddress, createAssociatedTokenAccountInstruction } from '@solana/spl-token';
const ata = await getAssociatedTokenAddress(mint, owner);
const ataInfo = await connection.getAccountInfo(ata);
if (!ataInfo) {
// Add ATA creation instruction before transfer
transaction.add(
createAssociatedTokenAccountInstruction(
payer, ata, owner, mint
)
);
}Pattern 4: Transaction Status Monitoring
// Monitor transaction confirmation
const signature = await connection.sendRawTransaction(transaction.serialize());
await connection.confirmTransaction(signature, 'confirmed');
// Check if transaction succeeded
const status = await connection.getSignatureStatus(signature);
if (status.value?.confirmationStatus === 'confirmed') {
console.log('Payment confirmed!');
}Local Development:
# 1. Use Solana devnet
# 2. Get devnet SOL from faucet
solana airdrop 2 <your-wallet-address> --url devnet
# 3. Use devnet USDC or create test token
# 4. Configure server for devnet:
{
"x402": {
"networks": {
"solana-devnet": {
"network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
...
}
}
}
}Mock Facilitator:
// For testing without blockchain
// Mock facilitator always returns success
const mockFacilitator = {
verify: () => ({ isValid: true, payer: 'MockWallet' }),
settle: () => ({
success: true,
transaction: 'MockTxHash',
network: 'solana:devnet'
})
};Common Issues:
-
"Insufficient funds"
- Check wallet balance:
solana balance <address> - Ensure enough tokens + SOL for fees
- Check wallet balance:
-
"Invalid signature"
- Verify wallet signed the transaction
- Check transaction serialization is correct
-
"Timeout exceeded"
- Transaction took too long to create
- Generate fresh recent blockhash
- Reduce
maxTimeoutSecondsif too long
-
"Compute unit price out of bounds"
- Check server's min/max compute unit price settings
- Adjust your transaction's compute unit price
-
"Settlement failed"
- Check blockchain explorer for transaction
- Verify facilitator has SOL for fees
- Ensure ATA exists for destination
Useful Tools:
- Solana Explorer: https://explorer.solana.com/
- Solscan: https://solscan.io/
- Web3.js Console:
nodeREPL with @solana/web3.js
Before going to production:
- Test on devnet with real transactions
- Configure mainnet network IDs correctly
- Set appropriate compute unit price bounds
- Implement proper error handling in client
- Add transaction confirmation waiting
- Set up monitoring/alerting for failed payments
- Document pricing for users
- Test with multiple wallets (Phantom, Solflare, etc.)
- Implement graceful fallback for payment failures
- Consider offering free tier or trial credits
The x402 protocol support is behind a feature flag and disabled by default. To enable it:
cargo build --features x402
cargo run --features x402Add the following configuration to your config.json:
{
"rpc_url": "https://api.mainnet-beta.solana.com",
"commitment": "confirmed",
"protocol_version": "2025-06-18",
"x402": {
"enabled": true,
"facilitator_base_url": "https://facilitator.example.com",
"request_timeout_seconds": 30,
"max_retries": 3,
"networks": {
"solana-mainnet": {
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"assets": [
{
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"name": "USDC",
"decimals": 6
}
],
"pay_to": "YourFeeRecipientAddress",
"min_compute_unit_price": 1000,
"max_compute_unit_price": 100000
}
}
}
}| Field | Type | Required | Description |
|---|---|---|---|
enabled |
boolean | Yes | Enable/disable x402 protocol (default: false) |
facilitator_base_url |
string | Yes* | Base URL of the facilitator service (*required when enabled) |
request_timeout_seconds |
number | No | HTTP request timeout (default: 30) |
max_retries |
number | No | Maximum retry attempts (default: 3) |
networks |
object | Yes* | Supported networks and assets (*required when enabled) |
| Field | Type | Required | Description |
|---|---|---|---|
network |
string | Yes | CAIP-2 network identifier (e.g., "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp") |
assets |
array | Yes | List of supported assets on this network |
pay_to |
string | Yes | Payment recipient address |
min_compute_unit_price |
number | No | Minimum compute unit price (SVM only) |
max_compute_unit_price |
number | No | Maximum compute unit price (SVM only) |
| Field | Type | Required | Description |
|---|---|---|---|
address |
string | Yes | Asset identifier (e.g., token mint address) |
name |
string | Yes | Human-readable asset name |
decimals |
number | Yes | Number of decimal places |
When a client makes a request to a protected tool without payment:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "getBalance",
"arguments": {
"pubkey": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr"
}
}
}The server returns a Payment Required error (code -40200) with payment requirements:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -40200,
"message": "Payment required to call tool 'getBalance'",
"data": {
"x402Version": 2,
"error": "Payment required to call tool 'getBalance'",
"resource": {
"url": "mcp://tool/getBalance",
"description": "MCP tool call: getBalance",
"mimeType": "application/json"
},
"accepts": [
{
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"amount": "1000000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "YourFeeRecipientAddress",
"maxTimeoutSeconds": 60
}
]
}
}
}The client retries the request with payment information in the _meta field:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "getBalance",
"arguments": {
"pubkey": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr"
},
"_meta": {
"payment": {
"x402Version": 2,
"accepted": {
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"amount": "1000000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "YourFeeRecipientAddress",
"maxTimeoutSeconds": 60
},
"payload": {
"transaction": "base64_encoded_solana_transaction",
"signature": "transaction_signature"
}
}
}
}
}If payment is valid, the server executes the tool and includes settlement information:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Balance: 1.5 SOL"
}
],
"_meta": {
"settlement": {
"success": true,
"transaction": "5vR...abc",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"payer": "ClientWalletAddress"
}
}
}
}If payment is invalid, the server returns an Invalid Payment error (code -40201):
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -40201,
"message": "Invalid payment: insufficient funds"
}
}This section provides 10+ detailed use cases demonstrating how to use x402 payment protocol in various scenarios.
Scenario: A data provider wants to charge 0.01 USDC per balance check request.
Setup:
{
"x402": {
"enabled": true,
"facilitator_base_url": "https://facilitator.example.com",
"networks": {
"solana-mainnet": {
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"assets": [{
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"name": "USDC",
"decimals": 6
}],
"pay_to": "DataProviderWalletAddress",
"min_compute_unit_price": 1000,
"max_compute_unit_price": 50000
}
}
}
}Usage:
- Client calls
getBalancewithout payment - Server returns Payment Required with amount "10000" (0.01 USDC with 6 decimals)
- Client creates signed transaction and includes in
_meta.payment - Server verifies and settles payment
- Server executes getBalance and returns result with settlement receipt
Scenario: Allow 100 free requests per hour, then require payment for additional requests.
Implementation Strategy:
- Track request counts per client (not implemented in current version)
- After limit exceeded, return Payment Required error
- Configure small payment amount (e.g., 0.001 USDC per request)
Configuration:
{
"x402": {
"enabled": true,
"networks": {
"solana-mainnet": {
"assets": [{
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"name": "USDC",
"decimals": 6
}],
"pay_to": "ServiceProviderAddress"
}
}
}
}Payment Required Response:
{
"error": {
"code": -40200,
"data": {
"x402Version": 2,
"error": "Rate limit exceeded. Payment required for additional requests.",
"resource": {
"url": "mcp://tool/getBalance"
},
"accepts": [{
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"amount": "1000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "ServiceProviderAddress",
"maxTimeoutSeconds": 60
}]
}
}
}Scenario: Support payments on both Solana mainnet and devnet for testing.
Configuration:
{
"x402": {
"enabled": true,
"facilitator_base_url": "https://facilitator.example.com",
"networks": {
"solana-mainnet": {
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"assets": [{
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"name": "USDC",
"decimals": 6
}],
"pay_to": "MainnetRecipient",
"min_compute_unit_price": 1000,
"max_compute_unit_price": 100000
},
"solana-devnet": {
"network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
"assets": [{
"address": "Gh9ZwEmdLJ8DscKNTkTqPbNwLNNBjuSzaG9Vp2KGtKJr",
"name": "DevUSDC",
"decimals": 6
}],
"pay_to": "DevnetRecipient",
"min_compute_unit_price": 100,
"max_compute_unit_price": 10000
}
}
}
}Usage:
- Clients can choose which network to pay on
- Devnet for testing with lower compute unit prices
- Mainnet for production with real tokens
Scenario: Accept payments in USDC, USDT, or SOL (wrapped).
Configuration:
{
"x402": {
"networks": {
"solana-mainnet": {
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"assets": [
{
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"name": "USDC",
"decimals": 6
},
{
"address": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
"name": "USDT",
"decimals": 6
},
{
"address": "So11111111111111111111111111111111111111112",
"name": "Wrapped SOL",
"decimals": 9
}
],
"pay_to": "MultiTokenRecipient"
}
}
}
}Payment Required Response: Server returns all accepted payment methods, client chooses one:
{
"accepts": [
{
"scheme": "exact",
"amount": "10000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"extra": {"name": "USDC"}
},
{
"scheme": "exact",
"amount": "10000",
"asset": "Es9vMFrzaCERmJfrF4H2FYD4KCoNkY11McCe8BenwNYB",
"extra": {"name": "USDT"}
},
{
"scheme": "exact",
"amount": "100000000",
"asset": "So11111111111111111111111111111111111111112",
"extra": {"name": "Wrapped SOL"}
}
]
}Scenario: Different tools have different prices.
Strategy:
- Configure base payment amounts per tool (implementation-dependent)
- Return appropriate amount in Payment Required response
Example Pricing:
getBalance: 0.001 USDC (1000 units)getTransaction: 0.005 USDC (5000 units)getProgramAccounts: 0.01 USDC (10000 units)
Payment Required for Expensive Operation:
{
"error": {
"code": -40200,
"data": {
"x402Version": 2,
"error": "Payment required for getProgramAccounts",
"resource": {
"url": "mcp://tool/getProgramAccounts",
"description": "Expensive operation requiring 0.01 USDC"
},
"accepts": [{
"scheme": "exact",
"amount": "10000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payTo": "ProviderAddress",
"maxTimeoutSeconds": 60
}]
}
}
}Scenario: Test payment integration without real blockchain transactions.
Setup Mock Facilitator:
# Run local mock facilitator for testing
npm install -g @x402/mock-facilitator
x402-mock-facilitator --port 3001Configuration:
{
"x402": {
"enabled": true,
"facilitator_base_url": "http://localhost:3001",
"request_timeout_seconds": 10,
"max_retries": 1,
"networks": {
"solana-devnet": {
"network": "solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1",
"assets": [{
"address": "TestTokenMint",
"name": "TestUSDC",
"decimals": 6
}],
"pay_to": "TestRecipient"
}
}
}
}Testing Flow:
- Mock facilitator always returns
isValid: truefor /verify - Mock facilitator returns mock transaction hash for /settle
- Test payment flow without actual blockchain interaction
- Verify error handling and retry logic
Scenario: Client submits payment but verification fails.
Common Failure Reasons:
- Insufficient balance
- Invalid signature
- Expired authorization
- Amount mismatch
Example Invalid Payment Response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -40201,
"message": "Invalid payment: insufficient balance"
}
}Client Recovery Strategy:
- Check wallet balance
- Ensure sufficient funds for payment + gas
- Generate new payment authorization
- Retry request with updated payment
Scenario: Prevent clients from submitting transactions with excessive compute unit prices.
Configuration:
{
"x402": {
"networks": {
"solana-mainnet": {
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"assets": [{
"address": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"name": "USDC",
"decimals": 6
}],
"pay_to": "RecipientAddress",
"min_compute_unit_price": 1000,
"max_compute_unit_price": 50000
}
}
}
}Validation:
- Server validates compute unit price is within [1000, 50000] microlamports
- Rejects transactions with prices outside bounds
- Returns Invalid Payment error with reason
Example Rejection:
{
"error": {
"code": -40201,
"message": "Invalid payment: Compute unit price 100000 out of bounds [1000, 50000]"
}
}Scenario: Track payment requests across verification, settlement, and tool execution.
How It Works:
- Each payment request gets a unique UUID trace ID
- Trace ID logged at all stages: verify, settle, execute
- Use trace ID to correlate logs across services
Example Log Output:
INFO [trace_id=550e8400-e29b-41d4-a716-446655440000] Verifying payment authorization
INFO [trace_id=550e8400-e29b-41d4-a716-446655440000] Payment verified successfully
INFO [trace_id=550e8400-e29b-41d4-a716-446655440000] Settling payment
INFO [trace_id=550e8400-e29b-41d4-a716-446655440000] Payment settled: tx=5vR...abc
INFO [trace_id=550e8400-e29b-41d4-a716-446655440000] Executing tool: getBalance
Usage:
# Filter logs by trace ID
grep "550e8400-e29b-41d4-a716-446655440000" server.log
# Track payment flow end-to-end
# Useful for debugging failed paymentsScenario: Facilitator temporarily unavailable or network issues.
Configuration:
{
"x402": {
"facilitator_base_url": "https://facilitator.example.com",
"request_timeout_seconds": 30,
"max_retries": 3
}
}How It Works:
- First request fails (network timeout)
- Wait 100ms + random jitter
- Retry request
- If fails again, wait 200ms + jitter
- Retry request
- If fails again, wait 400ms + jitter
- Final retry
- If all retries exhausted, return error to client
Retry Timing:
- Retry 1: 100ms + random(0-100ms)
- Retry 2: 200ms + random(0-100ms)
- Retry 3: 400ms + random(0-100ms)
Example Error After Exhausted Retries:
{
"error": {
"code": -32603,
"message": "Facilitator request failed after 3 attempts"
}
}Scenario: Gradually introduce payments without breaking existing clients.
Phase 1 - Preparation:
{
"x402": {
"enabled": false // Feature disabled, prepare infrastructure
}
}Phase 2 - Testing:
{
"x402": {
"enabled": true,
"facilitator_base_url": "https://staging-facilitator.example.com"
// Test with staging facilitator
}
}Phase 3 - Production:
{
"x402": {
"enabled": true,
"facilitator_base_url": "https://facilitator.example.com"
// Switch to production facilitator
}
}Communication Strategy:
- Announce payment requirement with 30-day notice
- Provide documentation and examples
- Offer free tier or credits for transition period
Scenario: Payment verification succeeds but settlement fails.
Possible Causes:
- Network congestion
- Transaction simulation fails
- Blockchain state changed between verify and settle
Example Settlement Failure Response:
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32603,
"message": "Payment settlement failed: transaction simulation failed"
}
}Client Recovery:
- Check blockchain state
- Verify account balances unchanged
- Generate new payment authorization
- Retry entire payment flow
Server Behavior:
- Log settlement failure with trace ID
- Do NOT execute paid operation
- Return error to client immediately
- No partial charges
Scenario: Notify external systems when payments are received.
Note: Webhook support not in current implementation, but shows integration pattern.
Future Configuration:
{
"x402": {
"webhooks": {
"payment_verified": "https://your-system.com/webhooks/payment-verified",
"payment_settled": "https://your-system.com/webhooks/payment-settled",
"payment_failed": "https://your-system.com/webhooks/payment-failed"
}
}
}Webhook Payload Example:
{
"event": "payment_settled",
"timestamp": "2025-12-12T15:00:00Z",
"trace_id": "550e8400-e29b-41d4-a716-446655440000",
"payment": {
"amount": "10000",
"asset": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"payer": "ClientWalletAddress",
"transaction": "5vR...abc",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
},
"tool": "getBalance"
}The facilitator service must implement these HTTP endpoints:
Verifies a payment authorization without executing blockchain transaction.
Request:
{
"paymentPayload": { /* PaymentPayload object */ },
"paymentRequirements": { /* PaymentRequirements object */ }
}Response:
{
"isValid": true,
"payer": "ClientWalletAddress"
}Executes payment settlement on the blockchain.
Request: Same as /verify
Response:
{
"success": true,
"transaction": "5vR...abc",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp",
"payer": "ClientWalletAddress"
}Returns supported payment schemes and networks.
Response:
{
"kinds": [
{
"x402Version": 2,
"scheme": "exact",
"network": "solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp"
}
],
"extensions": [],
"signers": {
"solana:*": ["FacilitatorSignerAddress"]
}
}For Solana (SVM) networks using the "exact" scheme, the server validates:
-
Strict Instruction Layout
- ComputeBudgetInstruction::SetComputeUnitLimit
- ComputeBudgetInstruction::SetComputeUnitPrice
- (Optional) AssociatedTokenAccount::Create
- Token::TransferChecked
-
Facilitator Fee Payer Constraints
- Fee payer must not appear in instruction accounts
- Fee payer must not be transfer authority or source
-
Compute Unit Price Bounds
- Must be within configured min/max range
-
Destination Account Validation
- Destination ATA must match payTo/asset
-
Transfer Amount
- Must exactly equal required amount
- HTTPS Required: All facilitator URLs must use HTTPS
- Timeout Bounds: Timeout must be between 1 and 300 seconds
- Retry with Jitter: Exponential backoff with random jitter
- Structured Logging: All operations logged with trace ID
- Input Validation: All payment data validated before processing
| Code | Name | Description |
|---|---|---|
| -40200 | Payment Required | Payment is required to access the resource |
| -40201 | Invalid Payment | Payment payload is invalid or verification failed |
- Verify feature flag is enabled:
cargo build --features x402 - Check
x402.enabledistruein config.json - Verify facilitator URL is accessible
- Check logs for detailed error messages
- Verify payment payload format matches specification
- Check compute unit price is within bounds (SVM)
- Verify asset and network are configured
- Review facilitator logs
- Verify payer has sufficient balance
- Check transaction instruction layout (SVM)
- Verify fee payer constraints
- Review blockchain explorer for transaction details
See tests/x402_integration.rs for complete integration test examples demonstrating:
- Payment Required response
- Valid payment processing
- Invalid payment handling
- Settlement verification