Skip to main content
The controller mediates lending flows. The pool is controller-owned and stores accounting by HubAssetKey. External contracts are treated as untrusted at the boundary where their output enters protocol logic.

Trust Boundaries

EdgeTrustedValidated
Caller -> controllerNothing by default.Auth, account owner/delegate, pause state, reentry guard, positive amounts, spoke listing, caps, LTV, health factor.
Controller -> poolController is pool owner.Pool checks owner, reserves, caps, utilization, and per-market accounting invariants.
Controller -> oracleNo provider output is trusted blindly.Staleness, future timestamp, decimals, sanity bounds, tolerance, and source availability.
Controller -> aggregatorRoute output is untrusted.Controller snapshots balances and verifies input/output deltas.
Pool -> tokenToken transfer semantics.Pool uses internal cash for borrowable liquidity and reserve checks.
Pool -> receiverReceiver is arbitrary code.Pool snapshots balance and verifies repayment after callback.

Supply

  1. User calls supply(caller, account_id, spoke_id, assets).
  2. account_id = 0 creates an account bound to spoke_id.
  3. Controller authenticates caller and checks spoke asset configuration.
  4. Controller transfers tokens to pool.
  5. Pool syncs the hub-asset row, mints scaled supply shares, enforces caps.
  6. Controller writes SupplyPositions(account_id) and emits position events.

Borrow

  1. User calls borrow(caller, account_id, borrows, to).
  2. Controller authenticates owner/delegate and loads account state.
  3. Controller resolves strict oracle prices, checks spoke borrow flags, caps, LTV, and health factor.
  4. Pool syncs the hub-asset row, mints scaled debt shares, checks cash and utilization, then transfers tokens to to or caller.
  5. Controller writes BorrowPositions(account_id) and emits position events.

Withdraw

  1. User calls withdraw(caller, account_id, withdrawals, to).
  2. Amount 0 means close that hub-asset supply position.
  3. If the account has debt, controller applies strict risk checks after the withdrawal. Debt-free withdrawals use the de-risking path.
  4. Pool burns supply shares and transfers tokens.

Repay

  1. Any payer calls repay(caller, account_id, payments).
  2. Controller transfers payment tokens to pool.
  3. Pool burns debt shares and refunds overpayment.
  4. Controller updates debt positions.

Liquidation

  1. Liquidator calls liquidate(liquidator, account_id, debt_payments).
  2. Controller resolves strict liquidation prices and requires health factor below one.
  3. Debt payments repay pool debt.
  4. Collateral is withdrawn from pool to liquidator, with protocol fee applied.
  5. Eligible residual bad debt can be cleaned and socialized through the supply index floor.

Flash Loan

  1. User calls flash_loan(caller, asset, amount, receiver, data).
  2. Controller validates asset: HubAssetKey, fee, and flash-loan availability.
  3. Pool snapshots balance, transfers the amount, invokes receiver callback, pulls repayment, and verifies final balance.
  4. Fee is recorded as protocol revenue.

Strategy Routes

Strategy entrypoints use aggregator route bytes. Controller validates balance deltas and final account risk. Slippage control lives in the route payload unless a controller-side minimum-output field is added in a future version.