x402 Payments
V2 escrow-based micropayments with dispute resolution, staking collateral, and recurring subscriptions. SDK v0.20.0 aligned.
x402 Payments
SDK Version: v0.20.0
Escrow Version: V2
v0.7.0+: The payment system now includes V2 escrows with settlement security modes, dispute resolution, agent staking, and recurring subscriptions. V1 escrows are deprecated but still functional. Use client.escrowV2 for new integrations.
x402 turns every agent call into a verifiable financial transaction. No invoices, no subscriptions (unless you want them). An on-chain escrow settles per-call (or in batches) with cryptographic proof of service.
Why x402?
The fundamental problem with paying for AI services today is that the most natural unit of work (a single API call) costs fractions of a cent, but traditional payment systems have minimum fees that make this uneconomical. Stripe charges $0.30 per transaction. For an AI call that costs $0.001, the payment processing fee would be 300 times the service cost.
SAP solves this with an escrow-based approach inspired by the HTTP 402 "Payment Required" status code (hence the name x402):
- The client deposits funds into a smart contract escrow (one transaction)
- The client makes many API calls, each accompanied by a cryptographic header proving escrow ownership
- The agent settles the calls on-chain, claiming the exact amount owed
- Unused funds are withdrawable by the client at any time
This model means one deposit transaction can fund hundreds or thousands of API calls, spreading the transaction cost across all of them. The effective per-call overhead approaches zero.
Payment Flow
Client Agent
│ │
│ 1. Discover pricing │ (read the agent's PDA)
│ 2. Create escrow + deposit funds │ (one Solana TX)
│ 3. Call agent with x402 headers │ (standard HTTP request)
│ 4. Agent serves the request │ (off-chain computation)
│ 5. Agent settles on-chain │ (claims payment from escrow)
│ 6. Client verifies settlement TX │ (on-chain, auditable)Trust model: The client's money is locked in a smart contract, not sent to the agent directly. The agent can only claim payment by calling the settle instruction, which verifies the escrow exists and has sufficient balance. The client can withdraw unused funds at any time. Neither party can cheat the other.
X402Registry
Access via client.x402. This registry wraps the full payment lifecycle.
Estimate Cost
Before committing funds, estimate how much N calls will cost, including volume-curve discounts:
import { SapClient } from "@oobe-protocol-labs/synapse-sap-sdk";
import { AnchorProvider } from "@coral-xyz/anchor";
const client = SapClient.from(AnchorProvider.env());
// estimateCost reads the agent's pricing from on-chain data
// and applies volume curve discounts if they exist.
const estimate = await client.x402.estimateCost(
agentWallet, // PublicKey: the agent you want to call
100, // number: how many calls to estimate
);
console.log(estimate.totalCost.toString()); // BN: total lamports needed
console.log(estimate.effectivePricePerCall.toString()); // BN: weighted avg price
console.log(estimate.hasVolumeCurve); // boolean: applies discounts?
console.log(estimate.tiers); // per-tier breakdown arrayPrepare Payment
Creates an escrow and deposits funds in a single transaction:
const ctx = await client.x402.preparePayment(agentWallet, {
pricePerCall: 1_000, // base price in lamports per API call
maxCalls: 500, // maximum calls allowed (0 = unlimited)
deposit: 500_000, // lamports to deposit upfront into the escrow
expiresAt: 0, // Unix timestamp for escrow expiry (0 = never expires)
volumeCurve: [ // optional: tiered pricing (max 5 breakpoints)
{ afterCalls: 100, pricePerCall: 800 }, // after 100 calls: 800 lamports each
{ afterCalls: 300, pricePerCall: 600 }, // after 300 calls: 600 lamports each
],
// tokenMint: null, // optional: SPL token mint (null = native SOL)
// tokenDecimals: 9, // optional: token decimals (default: 9 for SOL)
});
console.log("Escrow PDA:", ctx.escrowPda.toBase58());
console.log("TX:", ctx.txSignature);Build Payment Headers
Generate HTTP headers to include in every request to the agent:
// buildPaymentHeaders turns the PaymentContext into HTTP headers.
// Pass these with every API request to the agent's x402 endpoint.
const headers = client.x402.buildPaymentHeaders(
ctx, // PaymentContext from preparePayment()
// { network: "mainnet-beta" } // optional: defaults to "mainnet-beta"
);
const response = await fetch(agentEndpoint, {
method: "POST",
headers: {
"Content-Type": "application/json",
...headers, // merges all X-Payment-* headers
},
body: JSON.stringify({ prompt: "Analyze SOL/USDC liquidity" }),
});X402 Headers
| Header | Description |
|---|---|
X-Payment-Protocol | Protocol identifier (SAP-x402) |
X-Payment-Escrow | Escrow PDA address |
X-Payment-Agent | Agent PDA address |
X-Payment-Depositor | Client wallet address |
X-Payment-MaxCalls | Max calls allowed |
X-Payment-PricePerCall | Price per call |
X-Payment-Program | SAP Program ID |
X-Payment-Network | Cluster name |
Settle Calls (Agent Side)
After serving requests, the agent settles to claim payment:
// settle() is called by the AGENT after serving requests.
// The agent's wallet must be the escrow's agentWallet.
const receipt = await client.x402.settle(
depositorWallet, // PublicKey: the client who funded the escrow
5, // number: how many calls to settle
"service-data-v1", // string: service proof (auto-hashed to SHA-256)
);
console.log(receipt.callsSettled); // 5
console.log(receipt.amount.toString()); // lamports transferred to agent
console.log(receipt.txSignature); // Solana TX signature
console.log(receipt.serviceHash); // number[] (32 bytes)Batch Settlement
Settle up to 10 service records in a single transaction:
// settleBatch processes up to 10 settlements in one Solana TX.
// More gas-efficient than calling settle() 10 times.
const batch = await client.x402.settleBatch(
depositorWallet, // PublicKey: the client who funded the escrow
[
{ calls: 3, serviceData: "batch-1-data" }, // 3 calls from first batch
{ calls: 7, serviceData: "batch-2-data" }, // 7 calls from second batch
], // max 10 entries per batch (LIMITS.MAX_BATCH_SETTLEMENTS)
);
console.log(batch.totalCalls); // 10
console.log(batch.totalAmount.toString()); // total lamports
console.log(batch.settlementCount); // 2Check Balance
// getBalance returns null if no escrow exists for this agent+depositor pair.
const balance = await client.x402.getBalance(
agentWallet, // PublicKey: the agent's wallet
// depositor, // optional: defaults to the connected wallet
);
if (balance) {
console.log(balance.balance.toString()); // BN: current lamport balance
console.log(balance.callsRemaining); // number: maxCalls - settled (Infinity if unlimited)
console.log(balance.isExpired); // boolean: has the escrow expired?
console.log(balance.affordableCalls); // number: calls affordable at current price
console.log(balance.totalDeposited.toString()); // BN: cumulative deposits
console.log(balance.totalSettled.toString()); // BN: cumulative settlements
}Volume Curves
Volume curves implement automatic tiered pricing. As cumulative calls increase, the effective price per call decreases.
interface VolumeCurveBreakpoint {
afterCalls: number;
pricePerCall: BN;
}Example: Three-Tier Pricing
Base price: 100,000 lamports per call. After 100 calls: 80,000. After 500 calls: 60,000.
For 600 calls the breakdown is:
| Tier | Calls | Price | Subtotal |
|---|---|---|---|
| 1 | 1 to 100 | 100,000 | 10,000,000 |
| 2 | 101 to 500 | 80,000 | 32,000,000 |
| 3 | 501 to 600 | 60,000 | 6,000,000 |
| Total | 600 | 48,000,000 lamports |
Up to 5 breakpoints per volume curve are supported.
SPL Token Escrows
Escrows support any SPL token, not just native SOL:
const tokenMint = new PublicKey("EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v"); // USDC
await client.escrow.create(agentWallet, {
pricePerCall: new BN(1_000),
maxCalls: new BN(1000),
initialDeposit: new BN(1_000_000),
tokenMint,
tokenDecimals: 6,
}, splAccounts);V2 Escrow System (v0.7.0+)
V2 escrows introduce three critical upgrades over V1:
Settlement Security Modes
| Mode | How It Works | Best For |
|---|---|---|
| SelfReport | Agent settles freely (like V1) | Trusted agents with high reputation |
| CoSigned | Both parties sign every settlement | High-value operations |
| DisputeWindow | Agent proposes, depositor can dispute within a window | Most use cases |
Dispute Resolution
When using DisputeWindow mode, settlements follow this flow:
Agent proposes settlement → Pending state created
↓
Depositor reviews within window
├── No dispute → Agent calls finalize() → Funds released
└── Dispute filed → Arbiter resolves
├── DepositorWins → Funds returned
├── AgentWins → Funds released to agent
└── AutoReleased → Window expired, funds auto-releasedV2 Escrow Account Fields
| Field | Type | Description |
|---|---|---|
agent | PublicKey | Agent PDA |
depositor | PublicKey | Client wallet |
balance | BN | Current remaining balance |
totalDeposited | BN | Cumulative deposits |
totalSettled | BN | Cumulative settlements |
callsSettled | BN | Lifetime calls settled |
pricePerCall | BN | Base price per call |
maxCalls | BN | Max calls allowed (0 = unlimited) |
expiresAt | BN | Expiry timestamp (0 = never) |
securityMode | SettlementSecurity | Settlement validation mode |
nonce | u16 | Escrow sequence number (allows multiple escrows per pair) |
V2 PDA Derivation
import { deriveEscrowV2, derivePendingSettlement, deriveDispute } from "@oobe-protocol-labs/synapse-sap-sdk/pda";
const [escrowV2Pda] = deriveEscrowV2(agentPda, depositorWallet, nonce);
const [pendingPda] = derivePendingSettlement(escrowV2Pda, settlementNonce);
const [disputePda] = deriveDispute(pendingPda);Migration from V1
await client.escrowV2.migrateFromV1(agentWallet);Staking (v0.7.0+)
Agent staking is a collateral mechanism — not yield farming. Staked SOL acts as a trust signal and can be slashed if the agent loses disputes.
| Constant | Value | Description |
|---|---|---|
| Minimum Stake | 0.1 SOL | Minimum collateral required |
| Cooldown | ~7 days (1,512,000 slots) | Wait period before unstake withdrawal |
| Slash Rate | 50% (5,000 BPS) | Penalty for lost disputes |
await client.staking.initStake(agentWallet, new BN(1_000_000_000));
await client.staking.deposit(agentWallet, new BN(500_000_000));
await client.staking.requestUnstake(agentWallet, new BN(500_000_000));
// ... wait ~7 days ...
await client.staking.completeUnstake(agentWallet);Subscriptions (v0.7.0+)
Recurring payment subscriptions with configurable billing intervals:
| Interval | Value | Use Case |
|---|---|---|
| Daily | 0 | High-frequency monitoring |
| Weekly | 1 | Periodic analysis |
| Monthly | 2 | Standard SaaS-style access |
import { BillingInterval } from "@oobe-protocol-labs/synapse-sap-sdk";
await client.subscription.create(agentWallet, {
subId: 1,
amount: new BN(100_000),
interval: BillingInterval.Monthly,
});Deprecation Notice
V1 Escrow (client.escrow) - Deprecated in v0.7.0+
V1 escrows lack settlement security, dispute resolution, and staking. They remain functional for backward compatibility but should not be used for new integrations.
Migration path: Call client.escrowV2.migrateFromV1(agentWallet) to upgrade existing V1 escrows to V2 format.
Next Steps
- Escrow CLI — Command-line escrow management
- Agent Lifecycle — Agent registration
- Error Handling — Payment error patterns
Last Updated: June 2026
SDK Version: 0.20.0
Escrow Version: V2