SDK reference

@aap/sdk-ts

TypeScript SDK for agents and merchants. It includes the Anchor program client, the hosted SettleProof API client, PDA helpers, canonical proof hashing, and off-chain Ed25519 signing.

Install

# inside the SettleProof / AAP monorepo
bun add @aap/sdk-ts@workspace:*

# externally (when published)
npm i @aap/sdk-ts @coral-xyz/anchor @solana/web3.js @solana/spl-token

AapClient

One client per Anchor provider.

import { AapClient } from "@aap/sdk-ts";
import { AnchorProvider, Wallet } from "@coral-xyz/anchor";
import { Connection } from "@solana/web3.js";

const conn = new Connection("https://api.devnet.solana.com", "confirmed");
const provider = new AnchorProvider(conn, new Wallet(payer), {
  commitment: "confirmed",
});

const client = new AapClient({ provider });

// Optional overrides for non-devnet deployments:
new AapClient({
  provider,
  custodyProgramId: new PublicKey("..."),
  attestorProgramId: new PublicKey("..."),
});

Hosted API + local signing

The public API can prepare deterministic escrow inputs, but the agent still signs locally with @aap/sdk-ts. This keeps private keys out of SettleProof infrastructure.

import { SettleProofApiClient, hexToBytes } from "@aap/sdk-ts";

const api = new SettleProofApiClient();

const quote = await api.prepareEscrow({
  agent_owner: agentOwner.toBase58(),
  merchant: merchantPubkey.toBase58(),
  mint: mint.toBase58(),
  amount_stroops: "1000000",
  ttl_seconds: 3600,
  task_intent: "GET https://merchant.example/api/report?id=abc",
});

const { escrowPda } = await client.createEscrow({
  agentOwner,
  merchantPubkey,
  mint,
  amountStroops: 1_000_000n,
  taskHash: hexToBytes(quote.data.inputs.task_hash),
  ttlSeconds: 3600,
});

API/data proof helpers

The first SettleProof wedge is API/data commerce. The SDK exports canonical evidence helpers so merchants and agents hash the same payload before submitting proof_hash on-chain.

import {
  buildApiDeliveryEvidence,
  proofHashForEvidence,
  proofHashHexForEvidence,
} from "@aap/sdk-ts";

const evidence = await buildApiDeliveryEvidence({
  requestUrl: "https://merchant.example/api/report?id=abc",
  statusCode: 200,
  responseBody: { score: 91, verdict: "delivered" },
  taskHash: quote.data.inputs.task_hash,
  requestId: "req_123",
});

const proofHash = await proofHashForEvidence(evidence);     // Uint8Array(32)
const proofHashHex = await proofHashHexForEvidence(evidence); // 64 hex chars

Methods

MethodDescription
new SettleProofApiClient()Headless client for /v1/status, /v1/escrows/prepare, registry, templates, evidence, and relay.
buildApiDeliveryEvidence(...)Build canonical API/data delivery evidence for the api-delivered-v1 template.
proofHashForEvidence(evidence)SHA-256 hash of canonical evidence payload for on-chain proof_hash.
initializeRegistry({admin})Initialize the merchant registry singleton (admin only, runs once).
registerMerchant({signer, displayName, attestorPubkey})Register a merchant. Returns its derived MerchantAccount PDA.
updateAttestorPubkey({signer, newAttestorPubkey})Rotate the off-chain Ed25519 key (HSM-friendly).
createEscrow({agentOwner, merchantPubkey, mint, amountStroops, taskHash, ttlSeconds})Lock USDC into a per-escrow PDA vault.
signAttestation({attestorKeypair, escrowPda, proofHash})Off-chain only. Returns SignedAttestation { ix, proofHash, timestamp }.
releaseEscrow({escrowPda, merchantPubkey, mint, signedAttestation})Submit a transaction with the Ed25519 ix + release_escrow CPI.
claimRefund({caller, escrowPda, agentOwner, mint})After TTL: anyone can refund USDC to the agent owner.
getEscrow(escrowPda)Fetch the deserialized EscrowAccount or null.
getMerchant(merchantPubkey)Fetch the deserialized MerchantAccount or null.

Full happy-path example

// 1. Setup
await client.initializeRegistry({ admin: payer });

const { merchantPda } = await client.registerMerchant({
  merchantSigner,
  displayName: "Acme API",
  attestorPubkey: attestor.publicKey,
});

// 2. Agent locks 1 USDC
const taskHash = sha256(Buffer.from("scrape https://example.com"));

const { escrowPda } = await client.createEscrow({
  agentOwner: agent,
  merchantPubkey: merchantSigner.publicKey,
  mint,
  amountStroops: 1_000_000n,
  taskHash,
  ttlSeconds: 3600,
});

// 3. Merchant signs delivery proof off-chain
const proofHash = sha256(Buffer.from(deliveredHtml));
const signed = client.signAttestation({
  attestorKeypair: attestor,
  escrowPda,
  proofHash,
});

// 4. Anyone submits the release tx (paid in SOL)
const txSig = await client.releaseEscrow({
  escrowPda,
  merchantPubkey: merchantSigner.publicKey,
  mint,
  signedAttestation: signed,
});

console.log("released:", txSig);

PDA helpers (standalone exports)

import {
  deriveEscrowPda,
  deriveMerchantPda,
  deriveRegistryPda,
} from "@aap/sdk-ts";

const { pda: escrowPda } = deriveEscrowPda(
  custodyProgramId,
  agentPubkey,
  taskHashBytes,
);

const { pda: merchantPda } = deriveMerchantPda(
  attestorProgramId,
  merchantPubkey,
);

Off-chain message helpers

The 72-byte attestation payload is escrow_pda ‖ proof_hash ‖ timestamp_le. These helpers make sure your client matches the on-chain verifier byte-for-byte.

import {
  buildAttestationMessage,
  signAttestation,
  buildEd25519AttestationIx,
  signAndBuildAttestationIx,
} from "@aap/sdk-ts";

// just the bytes (72 B)
const message = buildAttestationMessage(escrowPda, proofHash, timestamp);

// raw signature (64 B) + bytes
const { message, signature } = signAttestation({
  attestorKeypair, escrowPda, proofHash, timestamp,
});

// ready-to-use Ed25519Program TransactionInstruction
const ix = buildEd25519AttestationIx({ attestorPubkey, message, signature });

// one-shot helper
const { ix, message, signature, timestamp } = signAndBuildAttestationIx({
  attestorKeypair, escrowPda, proofHash, timestamp,
});