Skip to main content

Contract Map

Vanna Protocol is composed of 14 Soroban smart contracts divided into five functional groups.

Layer Diagram


Contract Descriptions

AccountManager

The single entry point for all margin operations. Users never call SmartAccount directly — every operation passes through AccountManager, which:
  1. Authenticates the caller
  2. Asks the RiskEngine whether the operation is allowed
  3. Calls the appropriate LendingPool to issue or collect debt
  4. Calls the SmartAccount to update balances and execute external protocol calls
  5. Emits events
Key functions: create_account, borrow, repay, deposit_collateral_tokens, withdraw_collateral_balance, liquidate, close_account

SmartAccount (Per-User)

Each user who opens a margin account gets their own deployed SmartAccount contract — a separate on-chain instance with its own storage. This isolates risk: a bug in one account cannot affect another. The AccountManager deploys SmartAccounts via the DeployerContract using a deterministic salt. Closed accounts are recycled into an inactive pool and reused on next open to save deployment cost. SmartAccount stores:
  • CollateralTokensList and CollateralBalanceWAD(symbol) — raw collateral
  • BorrowedTokensList — symbols with outstanding debt
  • IsAccountActive, HasDebt — account state flags
  • External positions are tracked via TrackingTokens (not stored here)
External protocol routing is handled by execute(target, action, ...) which routes to Blend, Aquarius, or Soroswap based on action type.

LendingPools (XLM · USDC)

Two separate pool contracts, one per asset. Each pool:
  • Accepts deposits and mints vTokens (vXLM, vUSDC) proportional to share of pool
  • Tracks outstanding borrows using a borrow shares model
  • Calls update_state() before every borrow/repay to accrue interest
  • Has two privileged functions only callable by AccountManager: lend_to() and collect_from()
Pool assets are fully isolated — a problem in one pool cannot affect another.

RiskEngine

A stateless contract that reads position data from SmartAccount and LendingPools, prices everything via Oracle, and returns a boolean (allowed / not allowed) for every gated operation. Three guard functions:
  • is_borrow_allowed(symbol, amount, account) — gross-asset model
  • is_withdraw_allowed(symbol, amount, account) — net-asset model
  • is_account_healthy(balance, debt) — raw comparator (1.1× threshold)
The RiskEngine caches oracle prices within a single call to avoid duplicate cross-contract calls.

RateModel

Computes the per-second borrow rate given current pool utilization. Uses a smooth polynomial curve (not a kinked two-slope model):
Called by each LendingPool during update_state().

Registry

An on-chain address book. Every protocol contract resolves peer addresses through the Registry at runtime — no hardcoded addresses in business logic. This makes upgrades safe: update the Registry entry, not every contract.

Oracle

A thin passthrough to the Reflector oracle. Exposes get_price_latest(symbol) returning (price: u128, decimals: u32). The RiskEngine converts these to WAD format for collateral valuation.

TrackingToken

A single contract that tracks balances for synthetic position tokens — one per tracked position type: When a SmartAccount deploys capital to Blend or Aquarius, the AccountManager mints TrackingTokens on the SmartAccount. The RiskEngine reads these to value the positions.

vTokens (vXLM · vUSDC)

Standard fungible tokens representing LP share in a lending pool. Exchange rate grows over time as borrowers repay interest. Holders earn yield passively by holding vTokens.

Key Data Flows

Deposit Flow (LP Supply)

Borrow Flow (Margin Account)

Liquidation Flow

External Protocol Deployment (Blend)


Authorization Model

SmartAccount rejects all calls that don’t originate from its registered AccountManager. Users cannot call SmartAccount functions directly.

Fixed-Point Arithmetic

All balances, prices, and rates are stored in WAD format (10¹⁸ = 1.0). Asset amounts are converted to WAD at protocol boundaries:
All intermediate math uses mul_wad_down and div_wad_down (floor division) to prevent rounding in the protocol’s favor. For the full formula reference, see Math Reference.