SAP DOCv0.9.3
SDK Reference

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:

ModuleAccessStatusDescription
EscrowV2Moduleclient.escrowV2CurrentFull escrow with dispute resolution & settlement security
X402Registryclient.x402CurrentHigh-level payment flow (cost estimation, headers, settlement)
EscrowModuleclient.escrowDeprecatedV1 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:

ModeEnum ValueDescription
SettlementSecurity.SelfReport0Agent settles unilaterally - no co-sign needed
SettlementSecurity.CoSigned1Both depositor and agent must co-sign every settlement
SettlementSecurity.DisputeWindow2Agent 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,
});
FieldTypeDescription
depositBNInitial deposit in lamports
pricePerCallBNLamports per call
maxCallsBNMaximum calls (0 = unlimited)
expiresAtBNUnix timestamp (0 = never)
securityModeSettlementSecuritySettlement 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

OutcomeValueDescription
DisputeOutcome.Pending0Dispute filed, awaiting resolution
DisputeOutcome.DepositorWins1Funds returned to depositor
DisputeOutcome.AgentWins2Agent receives the disputed amount
DisputeOutcome.AutoReleased3Window 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
  ],
});
OptionTypeDefaultDescription
pricePerCallnumber or string or BNRequiredBase price per call
maxCallsnumber or string or BN0Max calls (0 = unlimited)
depositnumber or string or BNRequiredInitial deposit amount
expiresAtnumber or string or BN0Unix timestamp (0 = never)
volumeCurveArrayEmptyVolume discount breakpoints
tokenMintPublicKey or nullnullSPL token mint
tokenDecimalsnumber9Token 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);
FieldTypeDescription
balanceBNCurrent remaining balance
totalDepositedBNCumulative deposits
totalSettledBNCumulative settlements
callsRemainingnumberRemaining calls
isExpiredbooleanExpiry check
affordableCallsnumberCalls 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

ConstantValueDescription
MIN_STAKE100,000,000 lamports (0.1 SOL)Minimum stake amount
UNSTAKE_COOLDOWN_SLOTS1,512,000 (~7 days)Cooldown before withdrawal
SLASH_BPS5,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

IntervalValueDescription
BillingInterval.Daily0Daily billing cycle
BillingInterval.Weekly1Weekly billing cycle
BillingInterval.Monthly2Monthly 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);