02 · Architecture
Ballast has three actors that sign in three different trust domains. Keeping them straight is the key to understanding the system.
- Wallet-signed: the principal (a DAO treasury or human owner) signs from their own wallet to mint identity, issue a mandate, fund the agent, or revoke.
- Agent-server-signed: the agent signs trade transactions with its own operational key. This key has no authority beyond what the mandate grants.
- TEE-signed: the enclave signs the trade decision with a key whose identity the chain has verified. This is data inside the agent’s transaction, not a transaction signer.
Diagram 1: System architecture
flowchart TB
subgraph principal["Principal domain (wallet-signed)"]
treasury["Treasury / owner wallet"]
dash["Dashboard (web/, dapp-kit)"]
end
subgraph agentdomain["Agent domain (agent-server-signed)"]
agent["Agent service (agent/)"]
sdk["@ballast/sdk"]
end
subgraph tee["TEE domain (TEE-signed)"]
enclave["AWS Nitro enclave (enclave/)"]
end
subgraph chain["Sui testnet"]
subgraph pkg["Ballast Move package (v3)"]
identity["identity"]
capability["capability"]
attestation["attestation"]
trading["trading"]
end
deepbook["DeepBook v3 pool + BalanceManager"]
enclaveobj["Enclave (BallastEnclave) — registered TEE key"]
end
treasury -->|"connect"| dash
dash -->|"mint / issue / fund / revoke (wallet-signed)"| identity
dash -->|"issue / revoke (wallet-signed)"| capability
dash -->|"trigger run"| agent
agent --> sdk
agent -->|"sign trade decision"| enclave
enclave -->|"Ed25519 sig + timestamp"| agent
sdk -->|"execute_trade[_attested] (agent-signed PTB)"| trading
trading -->|"enforce()"| capability
trading -->|"verify_nautilus()"| attestation
attestation -.->|"checks sig vs proven key"| enclaveobj
trading -->|"place_market_order"| deepbook
trading -->|"record_ok + TradeExecuted"| identity
Reading it: the principal never gives the agent a key. It gives it a BallastCap.
The agent builds and signs the trade PTB, but the decision is signed inside the
enclave, and the chain re-checks both the capability rules (enforce) and the
enclave signature (verify_nautilus) before DeepBook is reached. The mock path
skips the enclave and uses attestation::verify (the stub) instead. Same shape,
one fewer guarantee.
Diagram 2: Move module architecture
The package is four small modules. trading is the composition root: it ties an
identity, a capability, an attestation, and a DeepBook pool together into one
gated call.
classDiagram
class identity {
<<module>>
+mint(label, clock)
+record_ok(rep_delta) package
+reputation() u64
}
class AgentIdentity {
UID id
address owner
String label
u64 reputation
u64 actions_ok
u64 actions_blocked
u64 created_at_ms
}
class capability {
<<module>>
+issue(identity, spend_limit, max_lev_bps, markets, expiry)
+enforce(cap, market, amount, lev_bps, clock)
+revoke(cap, ctx)
}
class BallastCap {
UID id
ID agent
address owner
u64 spend_limit
u64 spent
u64 max_leverage_bps
vector~ID~ allowed_markets
u64 expiry_ms
bool revoked
}
class attestation {
<<module>>
+verify(payload, sig) bool_STUB
+verify_nautilus(enclave, ts, agent, market, amount, is_bid, sig) bool
+init_attestation(name, pcr0, pcr1, pcr2)
}
class TradeAttestation {
address agent
address market
u64 amount
bool is_bid
}
class trading {
<<module>>
+execute_trade() mockPath
+execute_trade_attested() teePath
+authorize(cap, market, amount, lev, payload, sig)
+record_success() package
}
class TradeExecuted {
<<event>>
ID agent
ID cap
ID pool
u64 amount
bool is_bid
u64 leverage_bps
u64 client_order_id
u64 reputation
}
identity --> AgentIdentity : defines
capability --> BallastCap : defines
attestation --> TradeAttestation : signs (BCS)
trading --> TradeExecuted : emits
trading ..> capability : enforce()
trading ..> attestation : verify() / verify_nautilus()
trading ..> identity : record_ok()
How execute_trade composes the modules
assert!(capability::agent(cap) == object::id(identity))confirms this cap was issued for this identity (ECapIdentityMismatch, code 2).market = object::id(pool)derives the market from the actual pool object, so it can’t be spoofed in an argument.authorize(...)runscapability::enforce(...)(the five rules) thenattestation::verify(...)(stub). On the attested path it isattestation::verify_nautilus(...)beforeenforceinstead.balance_manager.generate_proof_as_owner(ctx)andpool.place_market_order(...)place the real DeepBook fill.record_success(...)callsidentity::record_ok(reputation) and emitsTradeExecuted.
Because steps 1 to 3 use assert!, any failure aborts the entire Programmable
Transaction Block. The spent increment and any DeepBook settlement chained after
it roll back atomically. The chain refuses to start the trade, so Ballast never
has to undo one.