Appearance
MasterAccountManager
Manages the complete lifecycle of funded trading master accounts on Hyperliquid, including KMS key generation, capital deposit, agent authorization, health checks, and deactivation.
High-Level Design
Role: Funded Account Lifecycle Owner
MasterAccountManager is the heaviest manager in TradeFundAccountRegistry. It owns everything between "application approved" and "account deactivated" — provisioning real Hyperliquid wallets, managing delegated trading keys, checking account health, and tearing down accounts when risk limits are breached.
See Key Concepts for the KMS-Only Signing security model, Agent Accounts overview, and MLL definition. This doc focuses on implementation-level details.
Account Status Lifecycle
NOT_ASSIGNED → CREATED → ACCEPTED_TERMS → DEPOSITED → AUTHORIZED_AGENT → ACTIVE → ACTIVE_TRADING
│
PAYMENT_WAIVED
(bypasses deposit)
Active states: ACTIVE, ACTIVE_TRADING, PAUSED
Terminal states: DEACTIVATED_MLL (max loss breach)
DEACTIVATED_UNLISTED (forbidden token)
DEACTIVATED_VOL (voluntary by user)
DEACTIVATED_STALE (inactivity)ACTIVE is the initial active state after provisioning. ACTIVE_TRADING indicates the user has started trading. PAUSED suspends trading without deactivation.
Account Provisioning Flow
When the parent DO approves an application, it delegates to createAndAssignMasterAccount() which runs a multi-step pipeline with intermediate status checkpoints:
createAndAssignMasterAccount(userId, challengeType)
│
├─ AWS KMS: createKey(secp256k1) → status: CREATED
├─ SQLite: insert monitoring + params (payout params from challengeType)
├─ Hyperliquid: acceptTerms (KMS-signed) → status: ACCEPTED_TERMS
├─ Depositor wallet: usdSend(capital) → status: DEPOSITED
├─ viem: generatePrivateKey()
├─ Hyperliquid: approveAgent (KMS-signed, 180-day expiry)
├─ KMS: encrypt agent key → SQLite → status: AUTHORIZED_AGENT
└─ Final → status: ACTIVEThe deposit step is idempotent — it checks current account value before sending and skips if already funded. A $1 fee is added for brand-new (zero-balance) Hyperliquid accounts.
Agent Key Lifecycle (Implementation Details)
Beyond what's described in the parent doc:
- Legacy handling:
isEncrypted()check gracefully handles pre-encryption agent keys that were stored before the encryption feature was added. - Atomic rotation:
rotateAgentAccount()revokes the old agent on Hyperliquid + deletes from SQLite, then creates a new one in a single operation. Returns both the previous agent address and new credentials. - On-demand decryption:
getDecryptedAgentCredentials()requires a KMS call each time — keys are never cached in plaintext.
Health Check Logic
Called by MonitoringManager every 30 seconds for each active account:
checkAccountHealth(address)
│
├─ Read monitoring record (MLL threshold)
├─ Parallel fetch from Hyperliquid:
│ ├─ clearinghouseState → account value, positions
│ └─ openOrders → pending orders
│
├─ MLL check: accountValue <= propMllBalance?
├─ Unlisted token check: positions OR orders in unlisted tokens?
│
├─ If healthy → update monitoring (account value, balance, ATH)
└─ If breached → return breach reason for MonitoringManager to act onATH tracking is a one-way ratchet — the all-time high account value only increases, never decreases. (The field is named mcAthBalance but it tracks account value, not withdrawable balance.)
Deactivation Flow
Triggered when a health check detects a breach, or for voluntary/staleness reasons:
deactivateMasterAccount(address, reason)
│
├─ Idempotency guard: return early if already deactivated
├─ Hyperliquid: cancelAllOrders()
├─ Hyperliquid: closeAllPositions()
├─ For each agent: revokeAgent() + DELETE from SQLite
├─ Update status to deactivation reason
├─ If MLL breach: fetch fresh clearinghouseState, record breach snapshot
│ (note: snapshot is taken AFTER positions are closed)
│ (idempotent — first breach data preserved on concurrent calls)
└─ Recycle remaining funds → depositor walletSoft failure on teardown: Agent revocation and fund recycling use error logging but do not throw. This prevents partial deactivation states where some cleanup steps succeed but later ones fail, leaving the account in limbo.
Depositor Wallet
A shared hot wallet (configured via HL_DEPOSITOR_PRIVATE_KEY) that:
- Funds new master accounts during provisioning
- Receives recycled funds from deactivated accounts
This centralizes capital flow through a single wallet rather than requiring per-account funding infrastructure.
See Also
- MonitoringManager - Alarm-based health monitoring that drives deactivation.
- PayoutManager - Payout flow that reads master account data.
- ApplicationManager - Application approval triggers account creation.
- AdminReviewManager - Bulk operations and admin tooling.
- Parent DO: TradeFundAccountRegistry