HubAssetKey. External contracts are treated as untrusted at the
boundary where their output enters protocol logic.
Trust boundaries
Supply
- User calls
supply(caller, account_id, spoke_id, assets). account_id = 0creates an account bound tospoke_idand mints its position NFT tocaller.- Controller authenticates the caller, checks the spoke listing and halt flags, and enforces the spoke supply cap and position limits.
- Controller transfers the tokens to the pool and credits the measured amount.
- Pool syncs the market row and mints scaled supply shares (floor-rounded).
- Controller writes
SupplyPositions(account_id)and emits the position batch; the pool emits its market-state batch.
Borrow
- User calls
borrow(caller, account_id, borrows, to). - Controller authenticates owner/delegate and loads account state.
- Controller resolves a complete price snapshot, then checks the spoke borrow flags, the spoke borrow cap, LTV, and health factor.
- Pool syncs the hub-asset row, mints scaled debt shares, checks
cashand utilization, then transfers tokens totoorcaller. - Controller writes
BorrowPositions(account_id)and emits position events.
Withdraw
- User calls
withdraw(caller, account_id, withdrawals, to). - Amount
0means close that hub-asset supply position. - If the account has debt, the controller re-checks the LTV gate, health factor, and the minimum-borrow-collateral floor after the withdrawal.
- Pool burns supply shares and transfers tokens. Withdraw is not pause-gated,
and a
frozenlisting still permits exit.
Repay
- Any payer calls
repay(caller, account_id, payments). - Controller transfers payment tokens to pool.
- Pool burns debt shares and refunds overpayment.
- Controller updates debt positions.
Liquidation
- Liquidator calls
liquidate(liquidator, account_id, debt_payments, seize_mode). - Controller resolves a complete price snapshot and requires health factor below one WAD.
- Debt payments repay pool debt; credit is measured, not requested.
- Collateral is seized pro-rata across the account’s whole collateral set. In
SeizeMode::Transferthe pool pays underlying and withholds the protocol fee; inSeizeMode::Creditthe seized supply shares move to a controller account and no collateral tokens move. - Eligible residual bad debt is socialized into the market’s supply index, clamped at the supply-index floor.
Flash loan
- User calls
flash_loan(caller, asset, amount, receiver, data). - Controller validates
asset: HubAssetKeyand flash-loan availability; the pool computes the fee and returns it. - Pool snapshots balance, transfers the amount, invokes receiver callback, pulls repayment, and verifies final balance.
- Fee is recorded as protocol revenue.
Strategy routes
Strategy entrypoints take opaque aggregator route bytes. The controller does not decode them. It checks its own balance deltas and the account’s final risk. Slippage protection lives inside the route payload, and only the aggregator enforces it. The controller will not catch a bad price for you — see Strategies.Who enforces what
The same operation is gated in two places, and the split is easy to get wrong:
Caps are a controller concern because they are per spoke. The pool knows
nothing about spokes — it only tracks per-market totals.

