SAP DOCv0.20.0
Core Protocol

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):

  1. The client deposits funds into a smart contract escrow (one transaction)
  2. The client makes many API calls, each accompanied by a cryptographic header proving escrow ownership
  3. The agent settles the calls on-chain, claiming the exact amount owed
  4. 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 array

Prepare 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

HeaderDescription
X-Payment-ProtocolProtocol identifier (SAP-x402)
X-Payment-EscrowEscrow PDA address
X-Payment-AgentAgent PDA address
X-Payment-DepositorClient wallet address
X-Payment-MaxCallsMax calls allowed
X-Payment-PricePerCallPrice per call
X-Payment-ProgramSAP Program ID
X-Payment-NetworkCluster 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);  // 2

Check 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:

TierCallsPriceSubtotal
11 to 100100,00010,000,000
2101 to 50080,00032,000,000
3501 to 60060,0006,000,000
Total60048,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

ModeHow It WorksBest For
SelfReportAgent settles freely (like V1)Trusted agents with high reputation
CoSignedBoth parties sign every settlementHigh-value operations
DisputeWindowAgent proposes, depositor can dispute within a windowMost 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-released

V2 Escrow Account Fields

FieldTypeDescription
agentPublicKeyAgent PDA
depositorPublicKeyClient wallet
balanceBNCurrent remaining balance
totalDepositedBNCumulative deposits
totalSettledBNCumulative settlements
callsSettledBNLifetime calls settled
pricePerCallBNBase price per call
maxCallsBNMax calls allowed (0 = unlimited)
expiresAtBNExpiry timestamp (0 = never)
securityModeSettlementSecuritySettlement validation mode
nonceu16Escrow 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.

ConstantValueDescription
Minimum Stake0.1 SOLMinimum collateral required
Cooldown~7 days (1,512,000 slots)Wait period before unstake withdrawal
Slash Rate50% (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:

IntervalValueUse Case
Daily0High-frequency monitoring
Weekly1Periodic analysis
Monthly2Standard 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


Last Updated: June 2026
SDK Version: 0.20.0
Escrow Version: V2