Skip to main content
A runnable agent that acts as itself — its own key, its own AgentCap — and swaps SUI on DeepBook through the gated adapter. One swap within the cap is allowed. One over the cap is denied on-chain. After the owner revokes, the same swap halts.

Source on GitHub

altheia-sui-demo — the demo agent repo.

What this shows

StepActionExpected
1Agent swaps 0.05 SUI (within the per-tx cap)allowed, transaction lands
2Agent swaps 0.2 SUI (over the per-tx cap)denied over_per_tx_cap
3Owner revokes in the dashboard, agent retries step 1denied policy_revoked
Step 1 is a policy decision, not a guaranteed fill. The agent submits a gated DeepBook order; on a thin testnet book it can fill zero. Allowed means the policy permitted the action and the transaction landed — it does not assert the order filled.

Setup

1. Provision the agent

In the dashboard, provision an agent with:
FieldValue
Budget assetSUI
Per-tx cap0.5 SUI
Per-day cap2 SUI
Allowed actionsswap
Scopethe DeepBook pool
Save the agent private key.

2. Configure

npm i @altheia-xyz/sui
npm i -D tsx
AGENT_PRIVKEY=suiprivkey1...
ALTHEIA_API=https://api.altheia.xyz   # for recording denials
TOKEN=<operator token>
AGENT_ID=<dashboard agent id>

The loop

agentRefs derives the agent’s objects from its key. guardedSubmit dry-runs each swap: an allowed swap submits and the on-chain AllowedAction event indexes it; a denied swap is recorded to the Chronicle without spending gas.
import {
  SuiJsonRpcClient, getJsonRpcFullnodeUrl, Ed25519Keypair, decodeSuiPrivateKey,
  agentRefs, buildSwap, keypairExecutor, guardedSubmit,
} from "@altheia-xyz/sui";

const client = new SuiJsonRpcClient({ url: getJsonRpcFullnodeUrl("testnet"), network: "testnet" });
const kp = Ed25519Keypair.fromSecretKey(decodeSuiPrivateKey(process.env.AGENT_PRIVKEY!).secretKey);
const agentAddr = kp.getPublicKey().toSuiAddress();
const refs = await agentRefs(client, agentAddr);

const audit = {
  baseUrl: process.env.ALTHEIA_API!, token: process.env.TOKEN!,
  agent_id: process.env.AGENT_ID!, action_type: "deepbook_swap", asset: "SUI",
};

async function swap(sui: number) {
  const amount = BigInt(Math.round(sui * 1e9));
  const r = await guardedSubmit(client, keypairExecutor(client, kp), agentAddr,
    buildSwap(refs, amount), { ...audit, amount: sui });
  if (r.ok) console.log(`allowed — DeepBook order submitted: ${r.digest}`);
  else console.log(`denied on-chain (logged to Chronicle): ${r.reason_code}`);
}

await swap(0.05);  // STEP 1 — within the per-tx cap
await swap(0.2);   // STEP 2 — over the per-tx cap → over_per_tx_cap
// STEP 3 — revoke in the dashboard, then:
await swap(0.05);  // → policy_revoked

What the run shows

Step 1 submits a real gated DeepBook order; the digest is on a Sui explorer. Step 2 aborts inside check_and_consume before any coin leaves the vault, and the denial is recorded. After the owner revokes (via the dashboard, signed with the OwnerCap), step 3 aborts policy_revoked — the same action that worked now halts.

Owner side

Revoke and the kill-and-drain are owner operations. In the dashboard the owner clicks Revoke (or Withdraw to wallet, which revokes and sweeps the vault back). Programmatically, with owner refs:
const agent = new AltheiaSui(client, ownerRefs, keypairExecutor(client, ownerKp));
await agent.revoke();
await agent.killAndDrain(ownerAddr);  // revoke + sweep SUI + acquired DEEP to the owner

Verify

Open the agent in the dashboard. The Chronicle has the allowed swap (settled, with a digest), the denied attempt (over_per_tx_cap), and the post-revoke halt.
Chronicle: allowed swap, denied over-cap, revoke

SDK reference

AltheiaSui, agentRefs, builders, reads.