SDK reference

@aap/sdk-ts

Thin TypeScript wrapper over the Anchor program clients. Handles PDA derivation, off-chain Ed25519 signing, and the multi-account release transaction. ESM, ~51 KB minified.

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.

const quote = await fetch(
  "https://api.settleproof.xyz/v1/escrows/prepare",
  {
    method: "POST",
    headers: { "content-type": "application/json" },
    body: JSON.stringify({
      agent_owner: agentOwner.toBase58(),
      merchant: merchantPubkey.toBase58(),
      mint: mint.toBase58(),
      amount_stroops: "1000000",
      ttl_seconds: 86400,
      task_intent: "book hotel abc123",
    }),
  },
).then((res) => res.json());

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

Methods

MethodDescription
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,
});