One door to the exchange
Exactly one code path spends testnet capital.
T3 Trade lets a language model submit signed testnet orders under fixed constraints. This page lists 17 invariants the system will not weaken. Each includes its purpose, enforcement point, and operational cost. A safety auditor should read this page first.
apps/server/src/trading/HyperliquidExecutionService.ts:4
apps/server/src/trading/HyperliquidReconciler.ts:66-76
apps/server/src/trading/TradingEmergencyCloseService.ts:51
Every article below can make a flow fail; none of them can be weakened to make a flow
pass. That is the working rule of the repository, stated in its priorities: never
weaken an execution guard, a loss budget, a protection requirement, an authority
check, an idempotency boundary, or a reconciliation rule to make an agent or UI flow
pass AGENTS.md:11. The seventeen articles are grouped into
four clauses: how capital moves, what counts as truth, who holds control, and what
stays isolated.
Seven articles govern the only path that can spend. Their shared shape: the decision is persisted before it is irreversible, deduplicated locally because no one else will, and checked against risk twice.
Exactly one code path spends testnet capital.
The execution record and its risk reservation are persisted before anything is signed, and a retry is refused unless the record still sits in a pre-submission status.
T3 deduplicates its own submissions; Hyperliquid does not deduplicate marketable IOCs.
The §16.3 position-increase checklist runs at preview, and the mandatory-stop gate runs again against the price actually going on the wire.
No acknowledged position increase may remain without a confirmed exchange-native reduce-only stop beyond the bounded reconciliation window.
When the cumulative loss budget is exhausted, increasing orders are cancelled, new increases are refused, and only an explicit user resume clears the blocked state.
The risk policy type forbids profit-funded budget expansion outright.
Three articles decide what counts as a fact. The exchange is authoritative for positions, orders, and fills. This section calls that exchange state canonical. Local data is corrected to match it, and unconfirmed outcomes remain unknown.
Local tables are overwritten with canonical exchange state; local state never outranks Hyperliquid.
A cancellation succeeds only on an explicit exchange acknowledgement; a submitted but unconfirmed close is reported as unknown.
Profit targets are handled by waking the bound agent runtime, not by placing server-managed resting orders; watches fire exactly once; a mission holding a position is never deaf.
Two articles place the operator above the machinery. The buttons that reduce risk answer with no model running at all, and the knobs that tune authority can only make it smaller in kind, never broader.
None of the seven deterministic controls may require a harness turn, the subsystem's name for a bound agent-runtime turn. The controls work while the provider process is stopped, the session is unreachable, or the model is generating a response.
Five articles keep writers single and boundaries hard. One mission owns a market, one run owns a decision, one process owns the database, one lane owns every signature, and trading code never becomes a second provider runtime.
One active mission per installation, and a market belongs either to a mission or to the user's manual trading, never both.
One run at a time may own a mission's decision lease, and one process at a time may run the trading runtime against a database.
No armed signer means no signed action, reported as a refusal, and a key the rest of the machine can read is refused loudly.
The trading extension may not start provider processes or create a second provider session directory, and a static test scans for every process-spawning pattern.
Every signature is taken inside one serialized nonce lane, and the testnet-ness of the signature domain is derived from the resolved endpoints.
The protection watchdog does not re-place a stop forever. When the window closes
uncovered,
apps/server/src/trading/TradingProtectionService.ts:30-32
reports escalate and the §17.5 bounded emergency close takes over: block
increases, cancel non-reduce-only orders, read fresh canonical state, submit a
reduce-only IOC, reconcile, retry with fresh reads, at most three attempts, then an
honest report of what is left
apps/server/src/trading/TradingEmergencyCloseService.ts:12-32,
apps/server/src/trading/TradingEmergencyCloseService.ts:51.
An unbounded retry loop against a market that will not take the size is a way to keep
paying fees forever; three attempts and the truth is the specified behavior.
Every position-increasing request clears the same layers, in the ribbon above and in code. Each layer has a named refusal, and none of them can be skipped to make a flow pass.
Fourteen increase rows plus two exit-only items, each with its own rejection reason
and a test pinning it. Refuses as
TradingPreviewRejection naming the first failing item.
apps/server/src/trading/TradingPreviewService.ts:40-70
The exhaustion gate, run before any nonce is spent. Refuses as
budget_exhausted or
action_not_permitted_under_exhaustion; cancel, reduce,
close, and modify_stop stay permitted because §16.4 blocks taking on risk, not
managing risk already open.
apps/server/src/trading/TradingExecutionGuard.ts:43-49,
packages/trading-contracts/src/lossAccounting.ts:145-153
The pure decider over reconciled inputs computes what remains, and the reservation
against that budget is persisted with the record before signing.
packages/trading-contracts/src/lossAccounting.ts:106,
apps/server/src/trading/HyperliquidExecutionService.ts:774-784
Refuses as signer_not_configured when no key is armed,
and as insecure_key_permissions when the key file is
readable by group or other. One signature site, inside the serialized nonce lane.
apps/server/src/trading/HyperliquidExecutionService.ts:508-518,
apps/server/src/trading/HyperliquidExecutionService.ts:527-552
Every per-order status in the response is inspected, and a grouped response is
attributed to legs positionally. A cancel is confirmed only by an explicit per-order
success.
apps/server/src/trading/HyperliquidExecutionService.ts:14,
apps/server/src/trading/HyperliquidExecutionService.ts:995-1007
Eight triggers force a pass that reads canonical account, orders, and fills, and
overwrites local tables with what the exchange says.
apps/server/src/trading/HyperliquidReconciler.ts:66-76,
apps/server/src/trading/HyperliquidReconciler.ts:1117-1152
Each article here is enforced inside a subsystem with its own page in the atlas. The reader-facing titles below carry the detail this constitution cites.
The submit sequence step by step, on the one path that spends capital.
The loss budget equations and the checklist that guards every increase.
The eight triggers, the drift classes, and the repair loop that keeps the exchange canonical.
Key resolution and permissions, the nonce lane, and the authority ceilings.
Mission statuses, per-market exclusivity, and the wake loop that keeps a holder never deaf.
The typed spine every control, event, and refusal literal rides.
The incidents that wrote these laws, RC02 through RC06, told as postmortems.
apps/server/src/trading/HyperliquidExecutionService.ts:4
the one path that spends
apps/server/src/trading/HyperliquidExecutionService.ts:605-624
second stop gate, wire price
apps/server/src/trading/HyperliquidExecutionService.ts:668-784
persist before signing
apps/server/src/trading/HyperliquidExecutionService.ts:770-772
retry refusal
apps/server/src/trading/HyperliquidExecutionService.ts:995-1007
cancel acknowledgement
apps/server/src/trading/TradingProtectionService.ts:4-9
the stop invariant
apps/server/src/trading/TradingEmergencyCloseService.ts:12-32
the bounded close order
apps/server/src/trading/TradingExecutionGuard.ts:1-14
§16.4 enforcement
packages/trading-contracts/src/authority.ts:41
Literal(false)
apps/server/src/trading/TestnetAuthority.ts:13-23 the
four knobs
apps/server/src/trading/HyperliquidReconciler.ts:66-76
the eight triggers
apps/server/src/trading/HyperliquidReconciler.ts:1117-1152
local never outranks
apps/server/src/trading/TradingControlService.ts:78-84
the unknown-close line
apps/server/src/trading/WatchEvaluator.ts:16-23
exactly-once guards
packages/trading-contracts/src/watch.ts:1288 the
coverage floor
apps/server/src/trading/TradingTurnCoordinator.ts:5-11
the decision lease
apps/server/src/trading/TradingRuntimeLease.ts:51-71
bounded dual belief
apps/server/src/trading/InterimSignerConfig.ts:248-259
vitest cannot arm the key
apps/server/src/trading/ProviderBoundary.test.ts:44-64
no provider spawns
packages/hyperliquid/src/NonceCoordinator.ts:4-18 one
serialized lane