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-tokenAapClient
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
| Method | Description |
|---|---|
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,
});