Escrow API
V2 escrow system with dispute resolution, settlement security, staking, and subscriptions. Includes deprecated V1 reference.
Escrow API
v0.7.0 Breaking Change: The V1 EscrowModule (client.escrow) is deprecated. Use client.escrowV2 for all new integrations. V1 escrows lack settlement security, dispute resolution, and staking support.
The SDK provides three escrow-related modules:
| Module | Access | Status | Description |
|---|---|---|---|
EscrowV2Module | client.escrowV2 | Current | Full escrow with dispute resolution & settlement security |
X402Registry | client.x402 | Current | High-level payment flow (cost estimation, headers, settlement) |
EscrowModule | client.escrow | Deprecated | V1 escrow without disputes - use V2 instead |
EscrowV2Module (v0.7.0)
Access via client.escrowV2. Supports settlement security modes, dispute resolution, and pending settlements.
Settlement Security Modes
V2 escrows require a security mode that determines how settlements are validated:
| Mode | Enum Value | Description |
|---|---|---|
SettlementSecurity.SelfReport | 0 | Agent settles unilaterally - no co-sign needed |
SettlementSecurity.CoSigned | 1 | Both depositor and agent must co-sign every settlement |
SettlementSecurity.DisputeWindow | 2 | Agent proposes settlement, depositor has time window to dispute |
Create V2 Escrow
import { SettlementSecurity } from "@oobe-protocol-labs/synapse-sap-sdk";
await client.escrowV2.create(agentWallet, {
deposit: new BN(100_000),
pricePerCall: new BN(1_000),
maxCalls: new BN(100),
expiresAt: new BN(Math.floor(Date.now() / 1000) + 86400),
securityMode: SettlementSecurity.CoSigned,
});| Field | Type | Description |
|---|---|---|
deposit | BN | Initial deposit in lamports |
pricePerCall | BN | Lamports per call |
maxCalls | BN | Maximum calls (0 = unlimited) |
expiresAt | BN | Unix timestamp (0 = never) |
securityMode | SettlementSecurity | Settlement validation mode |
Deposit / Withdraw / Close
// Top up escrow
await client.escrowV2.deposit(agentWallet, nonce, new BN(50_000));
// Withdraw unused funds (depositor only)
await client.escrowV2.withdraw(agentWallet, nonce, new BN(30_000));
// Close empty escrow (reclaims rent)
await client.escrowV2.close(agentWallet, nonce);Settlement (SelfReport Mode)
import { sha256, hashToArray } from "@oobe-protocol-labs/synapse-sap-sdk";
const serviceHash = hashToArray(sha256("service-proof-001"));
await client.escrowV2.settle(depositorWallet, nonce, new BN(5), serviceHash);Dispute Flow (DisputeWindow Mode)
When using DisputeWindow security, settlements go through a pending state:
// 1. Agent creates pending settlement
await client.escrowV2.createPendingSettlement(
agentWallet, depositorWallet, nonce,
settlementIdx, new BN(5), new BN(5000), serviceHash,
);
// 2. Depositor can dispute within the window
await client.escrowV2.fileDispute(agentWallet, nonce, settlementIdx, evidenceHash);
// 3. Arbiter resolves dispute
await client.escrowV2.resolveDispute(
depositorWallet, agentWallet, nonce, settlementIdx,
DisputeOutcome.DepositorWins,
);
// 4. Or if no dispute, finalize after window expires
await client.escrowV2.finalizeSettlement(agentWallet, depositorWallet, nonce, settlementIdx);
// 5. Cleanup
await client.escrowV2.closeDispute(pendingSettlementPda);
await client.escrowV2.closePendingSettlement(pendingSettlementPda);Dispute Outcomes
| Outcome | Value | Description |
|---|---|---|
DisputeOutcome.Pending | 0 | Dispute filed, awaiting resolution |
DisputeOutcome.DepositorWins | 1 | Funds returned to depositor |
DisputeOutcome.AgentWins | 2 | Agent receives the disputed amount |
DisputeOutcome.AutoReleased | 3 | Window expired - auto-released to agent |
V1 to V2 Migration
// Migrate an existing V1 escrow to V2 format
await client.escrowV2.migrateFromV1(agentWallet);Fetch V2 Escrow Data
const escrow = await client.escrowV2.fetch(agentPda, depositor, nonce);
const escrowOrNull = await client.escrowV2.fetchNullable(escrowPda);
const pending = await client.escrowV2.fetchPendingSettlement(pendingPda);
const dispute = await client.escrowV2.fetchDispute(disputePda);X402Registry (High-Level)
X402Registry still works with both V1 and V2 escrows. In v0.7.0, getBalance() auto-detects V2 escrows first via deriveEscrowV2, falling back to V1.
Access via client.x402. Handles cost estimation, payment preparation, header generation, settlement, and balance tracking.
Estimate Cost
// estimateCost reads the agent's on-chain pricing tiers and volume curve
// to calculate the total cost for a given number of calls.
// Returns: { totalCost, effectivePricePerCall, hasVolumeCurve, tiers }
const estimate = await client.x402.estimateCost(
agentWallet, // PublicKey: the agent you want to pay
100, // number of calls you plan to make
);
console.log(estimate.totalCost.toString()); // BN → string
console.log(estimate.effectivePricePerCall.toString()); // weighted average
console.log(estimate.hasVolumeCurve); // has tiered pricing?Prepare Payment
Creates escrow and deposits funds in a single transaction:
// preparePayment sends one Solana TX: creates escrow PDA + deposits lamports.
// Returns a PaymentContext with all the derived PDAs needed for headers.
const ctx = await client.x402.preparePayment(agentWallet, {
pricePerCall: 1_000, // lamports per call (number, string, or BN)
maxCalls: 500, // max calls allowed (0 = unlimited)
deposit: 500_000, // initial deposit in lamports
expiresAt: 0, // Unix timestamp (0 = never expires)
volumeCurve: [ // optional: tiered pricing breakpoints
{ afterCalls: 100, pricePerCall: 800 }, // 20% discount after 100 calls
],
});| Option | Type | Default | Description |
|---|---|---|---|
pricePerCall | number or string or BN | Required | Base price per call |
maxCalls | number or string or BN | 0 | Max calls (0 = unlimited) |
deposit | number or string or BN | Required | Initial deposit amount |
expiresAt | number or string or BN | 0 | Unix timestamp (0 = never) |
volumeCurve | Array | Empty | Volume discount breakpoints |
tokenMint | PublicKey or null | null | SPL token mint |
tokenDecimals | number | 9 | Token decimal places |
Build Headers
// From a PaymentContext (returned by preparePayment)
const headers = client.x402.buildPaymentHeaders(ctx);
// Returns: { "X-Payment-Protocol", "X-Payment-Escrow", "X-Payment-Depositor", ... }
// From an existing escrow (looks up the PDA on-chain)
const headers = await client.x402.buildPaymentHeadersFromEscrow(agentWallet);Settle
// Agent calls settle() to claim payment for calls served.
// depositorWallet: the client who funded the escrow (identifies which escrow)
// 5: number of calls to settle
// "service-data-v1": service data string (auto-hashed to SHA-256)
const receipt = await client.x402.settle(depositorWallet, 5, "service-data-v1");Batch Settle
// settleBatch combines multiple settlements in one TX. Max 10 entries.
const batch = await client.x402.settleBatch(depositorWallet, [
{ calls: 3, serviceData: "batch-1" }, // settle 3 calls
{ calls: 7, serviceData: "batch-2" }, // settle 7 calls
]);Balance
const balance = await client.x402.getBalance(agentWallet);| Field | Type | Description |
|---|---|---|
balance | BN | Current remaining balance |
totalDeposited | BN | Cumulative deposits |
totalSettled | BN | Cumulative settlements |
callsRemaining | number | Remaining calls |
isExpired | boolean | Expiry check |
affordableCalls | number | Calls the budget allows |
Additional Methods
// Add more funds to an existing escrow (in lamports).
await client.x402.addFunds(agentWallet, 50_000);
// Withdraw funds from an escrow you own (must be the depositor).
await client.x402.withdrawFunds(agentWallet, 25_000);
// Close the escrow PDA. Balance must be 0. Reclaims ~0.004 SOL rent.
await client.x402.closeEscrow(agentWallet);
// Check if an escrow exists for this agent (returns boolean).
const exists = await client.x402.hasEscrow(agentWallet);
// Fetch the raw escrow account data (or null if not found).
const escrow = await client.x402.fetchEscrow(agentWallet);
// Verify a settlement TX by parsing its events.
const events = await client.x402.verifySettlement(txSignature);EscrowModule (Low-Level) - Deprecated
Deprecated in v0.7.0. Use client.escrowV2 for all new integrations. The V1 EscrowModule lacks settlement security modes, dispute resolution, and staking support. Existing V1 escrows can be migrated using client.escrowV2.migrateFromV1().
Access via client.escrow. Direct Anchor instruction wrappers for V1 escrows.
Create
import { BN } from "@coral-xyz/anchor";
// Low-level: all values must be BN. No automatic type conversion.
// Use the X402Registry (client.x402) for automatic conversions.
await client.escrow.create(agentWallet, {
pricePerCall: new BN(1_000_000), // 1,000,000 lamports per call
maxCalls: new BN(100), // max 100 calls (BN(0) = unlimited)
initialDeposit: new BN(100_000_000), // deposit 0.1 SOL
expiresAt: null, // null = never expires
volumeCurve: null, // null = flat pricing
tokenMint: null, // null = native SOL
tokenDecimals: null, // null = defaults to 9 (SOL)
});Deposit
await client.escrow.deposit(agentWallet, new BN(50_000_000));Settle
import { sha256, hashToArray } from "@oobe-protocol-labs/synapse-sap-sdk/utils";
// Low-level settle: requires BN for calls and pre-hashed service data.
const serviceHash = hashToArray(sha256("service-record-001")); // number[32]
await client.escrow.settle(
depositorWallet, // PublicKey: the client's wallet
new BN(5), // number of calls to settle
serviceHash, // 32-byte SHA-256 hash
);Batch Settle
// Low-level batch settle: each entry must use BN and pre-hashed service data.
// Max 10 entries per batch (LIMITS.MAX_BATCH_SETTLEMENTS).
await client.escrow.settleBatch(depositorWallet, [
{ callsToSettle: new BN(3), serviceHash: hashToArray(sha256("batch-1")) },
{ callsToSettle: new BN(7), serviceHash: hashToArray(sha256("batch-2")) },
]);Withdraw and Close
// Withdraw partial funds (must be the depositor).
await client.escrow.withdraw(agentWallet, new BN(25_000_000));
// Close escrow PDA. Balance must be 0. Reclaims rent to payer.
await client.escrow.close(agentWallet);StakingModule (v0.7.0)
Access via client.staking. Agent collateral staking - not yield, but a trust signal and slashing mechanism.
Constants
| Constant | Value | Description |
|---|---|---|
MIN_STAKE | 100,000,000 lamports (0.1 SOL) | Minimum stake amount |
UNSTAKE_COOLDOWN_SLOTS | 1,512,000 (~7 days) | Cooldown before withdrawal |
SLASH_BPS | 5,000 (50%) | Slash penalty for lost disputes |
Lifecycle
// Initialize stake (minimum 0.1 SOL)
await client.staking.initStake(agentWallet, new BN(1_000_000_000));
// Add more stake
await client.staking.deposit(agentWallet, new BN(500_000_000));
// Request unstake (starts 7-day cooldown)
await client.staking.requestUnstake(agentWallet, new BN(500_000_000));
// Complete unstake after cooldown expires
await client.staking.completeUnstake(agentWallet);Fetch Stake Data
const stake = await client.staking.fetch(agentPda);
const stakeOrNull = await client.staking.fetchNullable(agentPda);
const stakeByPda = await client.staking.fetchByPda(stakePda);SubscriptionModule (v0.7.0)
Access via client.subscription. Recurring payment subscriptions with configurable billing intervals.
Billing Intervals
| Interval | Value | Description |
|---|---|---|
BillingInterval.Daily | 0 | Daily billing cycle |
BillingInterval.Weekly | 1 | Weekly billing cycle |
BillingInterval.Monthly | 2 | Monthly billing cycle |
Lifecycle
import { BillingInterval } from "@oobe-protocol-labs/synapse-sap-sdk";
// Create a subscription
await client.subscription.create(agentWallet, {
subId: 1,
amount: new BN(100_000),
interval: BillingInterval.Monthly,
});
// Fund subscription
await client.subscription.fund(agentWallet, 1, new BN(100_000));
// Cancel subscription (stops future billing)
await client.subscription.cancel(agentWallet, 1);
// Close subscription PDA (reclaim rent)
await client.subscription.close(agentWallet, 1);Fetch Subscription Data
const sub = await client.subscription.fetch(agentPda, subscriber, subId);
const subOrNull = await client.subscription.fetchNullable(subscriptionPda);SessionManager
Unified memory API that orchestrates vault, session, and ledger operations behind a single interface.
Metaplex Bridge
Link a SAP agent to a Metaplex Core asset through the AgentIdentity external plugin. Covers triple-check audits, atomic register flows, EIP-8004 hosting, and authenticated RPC reads.