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.