Bound
SDK

Writing transactions

Methods that sign and submit — staking, depositing, publishing, attesting, challenging.

Every write takes a Keypair from @stellar/stellar-sdk as its first argument, signs locally, and submits the transaction.

Server-side only

These calls require the caller's secret key. Keep them behind your own backend. A secret key in a browser bundle is a compromised account, not a deployment detail.

import { Keypair } from "@stellar/stellar-sdk";
import { bound } from "@bound/sdk";

const operator = Keypair.fromSecret(process.env.OPERATOR_SECRET!);

The methods

MethodWho signsEffect
stakeAsAuditor(kp, amount)auditorStake into AuditorStaking. At or above the minimum, you are registered.
depositReserve(kp, amount)operatorLock USDC in the ReserveVault until the certificate expires.
depositFee(kp, amount, auditor)operatorEscrow the audit fee, released on attestation.
publishCertificate(kp, params)operatorCreate a Pending certificate. Returns the certificate id.
attestCertificate(kp, certId)auditorMove it to Verified — and lock the auditor's stake to it.
executePayment(kp, amount, to)agentDirect USDC transfer. See Payments.
challengeCertificate(kp, certId, proofType, victim)challengerPost a bond and open a challenge.
resolveChallenge(challengeId)anyoneSettle it. Permissionless for InsufficientReserve.
mintUsdc(kp, amount)anyTestnet convenience for funding demo accounts.

The issuing path, end to end

issue.ts
// 1. The auditor puts their own money at risk.
await bound.stakeAsAuditor(auditor, 500_0000000n);          // $500

// 2. The operator locks the reserve — this is the pre-funded payout.
await bound.depositReserve(operator, 1_500_0000000n);       // $1,500

// 3. And escrows the audit fee.
await bound.depositFee(operator, 25_0000000n, auditor.publicKey());

// 4. The certificate is published, still Pending.
const certId = await bound.publishCertificate(operator, {
  agent: agentAddress,
  bound: 1_500_0000000n,
  reserveAmount: 1_500_0000000n,
  expiresAt: Math.floor(Date.now() / 1000) + 30 * 24 * 3600,
});

// 5. The auditor attests. Their stake locks to the certificate in the same tx.
await bound.attestCertificate(auditor, certId);

After step 5, verifyCertificate(agent).valid is true, and the auditor cannot withdraw their stake until expiresAt.

Challenging a certificate

If the vault holds less than the certificate claims, that is provable without trusting anyone:

challenge.ts
const challengeId = await bound.challengeCertificate(
  challenger,
  certId,
  "InsufficientReserve",
  victimAddress,
);

await bound.resolveChallenge(challengeId);

resolveChallenge for InsufficientReserve is permissionless — the contract reads the claimed reserve from the Registry and the actual balance from the vault and compares them. On fraud it slashes the auditor 80% to the victim and 20% to the challenger, drains the remaining reserve to the victim, invalidates the certificate, and returns the challenger's bond. If there is no fraud, the challenger forfeits their bond.

The other proof types (BoundExceeded, FakeSignature) are subjective and settle through resolve_by_arbiter instead. Trust model explains why that distinction is drawn where it is, rather than papered over.

On this page