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
| Method | Who signs | Effect |
|---|---|---|
stakeAsAuditor(kp, amount) | auditor | Stake into AuditorStaking. At or above the minimum, you are registered. |
depositReserve(kp, amount) | operator | Lock USDC in the ReserveVault until the certificate expires. |
depositFee(kp, amount, auditor) | operator | Escrow the audit fee, released on attestation. |
publishCertificate(kp, params) | operator | Create a Pending certificate. Returns the certificate id. |
attestCertificate(kp, certId) | auditor | Move it to Verified — and lock the auditor's stake to it. |
executePayment(kp, amount, to) | agent | Direct USDC transfer. See Payments. |
challengeCertificate(kp, certId, proofType, victim) | challenger | Post a bond and open a challenge. |
resolveChallenge(challengeId) | anyone | Settle it. Permissionless for InsufficientReserve. |
mintUsdc(kp, amount) | any | Testnet convenience for funding demo accounts. |
The issuing path, end to end
// 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:
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.