06 · Quickstart: integrate in a day
Give your agent revocable, on-chain-enforced guardrails in about ten lines. Every
snippet below uses the real @ballast/sdk API and the live testnet TESTNET
preset, and typechecks against the published types.
Install
pnpm add @ballast/sdk @mysten/sui
@ballast/sdk is the full surface (client + PTB builders, on @mysten/sui v1).
If your app is on a different @mysten/sui major (e.g. a v2 dashboard), import the
dependency-free subset from @ballast/sdk/parse instead. It has the same types,
presets, parsers, and MOVE_TARGETS, with no client.
The ten-line gated trade
import { BallastClient, TESTNET, SUI } from '@ballast/sdk';
import { Ed25519Keypair } from '@mysten/sui/keypairs/ed25519';
const signer = Ed25519Keypair.fromSecretKey(process.env.AGENT_SECRET_KEY!);
const ballast = new BallastClient({
packageId: TESTNET.packageId,
deepbookPackageId: TESTNET.deepbookPackageId,
network: 'testnet',
signer, // the agent's own key — no authority beyond the mandate
});
// 1. The principal issues a mandate (a revocable, scoped BallastCap).
const { capId } = await ballast.issueCap({
identityId: TESTNET.identityId,
spendLimit: 50n * SUI, // hard ceiling, base units
maxLeverage: 3, // 3.00x
allowedMarkets: [TESTNET.poolId], // only this market
expiryMs: Date.now() + 60 * 60 * 1000, // 1 hour
});
// 2. Fund the agent's BalanceManager with SUI (zero-DEEP fee path).
await ballast.depositSui(TESTNET.balanceManagerId, 2n * SUI);
// 3. Act THROUGH the capability. A violation is a value, never a throw.
const result = await ballast.executeGated({
identityId: TESTNET.identityId,
capId,
poolId: TESTNET.poolId,
balanceManagerId: TESTNET.balanceManagerId,
amount: 1n * SUI,
isBid: false, // SELL SUI against resting bids
payWithDeep: false,
});
if (result.ok) {
console.log(`filled ${result.filled} — reputation now ${result.reputation}`);
} else {
console.log(`blocked by the chain: ${result.abortReason} (code ${result.abortCode})`);
}
That’s the whole loop: issue, fund, act. The cap enforcement, attestation, PTB construction, abort-code parsing, and fill parsing all live in the SDK.
Watch it get blocked
executeGated never throws on a capability violation. It returns
{ ok: false, abortCode, abortReason, digest? }. So proving the guardrails is just
reading the code:
const tooBig = await ballast.executeGated({
identityId: TESTNET.identityId,
capId,
poolId: TESTNET.poolId,
balanceManagerId: TESTNET.balanceManagerId,
amount: 5000n * SUI, // far over the 50 SUI cap
isBid: true,
});
// tooBig.ok === false, tooBig.abortCode === 5 (ESpendExceeded)
Revoke in one transaction
await ballast.revokeCap(capId); // owner-only; every later enforce() now aborts (code 1)
Upgrade to real TEE attestation (no call-site change)
Swap the attestation provider; nothing else changes. Supplying a provider with an
enclaveId routes executeGated to trading::execute_trade_attested
automatically.
import { BallastClient, TESTNET, NautilusAttestationProvider } from '@ballast/sdk';
const ballast = new BallastClient({
packageId: TESTNET.packageId,
deepbookPackageId: TESTNET.deepbookPackageId,
network: 'testnet',
signer,
attestation: new NautilusAttestationProvider({
enclaveUrl: process.env.ENCLAVE_URL!, // http://<nitro-ec2>:3000
enclaveId: '0xc2a179083847cc72b18cd357292a1482f87800393c9d60eb827ae51076351ba1',
}),
});
// executeGated(...) now requires a valid enclave signature on-chain (verify_nautilus).
Wallet-signing apps (dashboards)
If the principal signs from a wallet (e.g. dapp-kit), build the PTBs yourself and sign with the wallet instead of the client. The targets are stable strings:
import { MOVE_TARGETS, TESTNET } from '@ballast/sdk/parse';
import { Transaction } from '@mysten/sui/transactions';
const tx = new Transaction();
tx.moveCall({
target: MOVE_TARGETS.capabilityRevoke(TESTNET.packageId),
arguments: [tx.object(capId)],
});
// hand `tx` to the wallet to sign & execute
Point it at your own deployment
The TESTNET preset is just the reference deployment. Ballast is
deployment-agnostic. Pass your own packageId, identityId, poolId, and the
rest to BallastClient or the builders, and everything works against your package.