Architecture
Technical architecture overview of the Rate Swap Protocol.
System Overview
The Rate Swap Protocol is a Solana program built with the Anchor framework that implements margined, mark-to-market interest rate swaps using a quote-only closed system with accounting-only settlement.
Quote-Only Closed System
The protocol uses a single accounting currency called the quote token (e.g., USDC) that serves as:
- The unit of collateral for traders' margin accounts
- The vault currency for LP liquidity
- The settlement medium for all PnL accounting
All settlement is accounting-only: no SPL token transfers occur during funding settlement. Tokens only move during explicit deposit/withdraw operations.
Core Components
- Pool: Liquidity vault and LP management, holds quote tokens
- Market: Swap instrument with AMM pricing and pool-side counterparty accounting
- Margin: User collateral account (raw token amount, not shares)
- Position: User swap positions (embedded in Margin), tracks realized PnL
- RateOracle: Rate index data source for settlement
- AMM: Rate curve pricing engine (external crate)
Program Structure
programs/rate-swap/src/
├── lib.rs # Program entrypoint, instruction routing
├── state/
│ └── mod.rs # Account structures (Pool, Market, Margin, Position, RateOracle)
├── instructions/ # Instruction handlers
│ ├── init_pool.rs # Pool creation with risk parameters
│ ├── deposit_pool.rs # LP deposits
│ ├── withdraw_pool.rs # LP withdrawals
│ ├── init_market.rs # Market creation with caps and fees
│ ├── remove_market.rs # Expired market cleanup
│ ├── refresh_market_liquidity.rs
│ ├── init_margin.rs # Trader margin account
│ ├── deposit_margin.rs # Collateral deposit
│ ├── withdraw_margin.rs # Collateral withdrawal
│ ├── init_position.rs # Position slot allocation
│ ├── swap.rs # Trading with DV01 gating
│ ├── liquidate.rs # Crank-only liquidation
│ ├── init_oracle.rs # Oracle initialization
│ └── update_oracle.rs # Oracle updates
├── math/ # Core calculations
│ ├── lp_nav.rs # LP NAV calculations
│ ├── liquidity.rs # Liquidity depth and DV01 gating
│ ├── settle.rs # Accounting-only settlement and health
│ ├── dv01.rs # DV01 calculations
│ ├── margin_floor.rs # Per-position margin floors
│ ├── shares.rs # Equity calculations
│ ├── fees.rs # Swap fee calculations
│ ├── oracle.rs # Oracle validation
│ └── conversions.rs # WAD conversions
├── errors.rs # Custom error types
└── macros.rs # Helper macros (require_msg!)Account Architecture
Pool Account
PDA Seeds: ["pool", shares_mint]
Fields:
authority: Pool adminshares_mint: LP share token mintquote_mint: Quote token mint (e.g., USDC)quote_vault: Quote token vault holding all liquiditytotal_trader_collateral: Sum of all trader collateral (u64)max_rate_move_bps: Max rate move for DV01 reserve (basis points)total_liquidity_weight_bps: Sum of market liquidity weightsutil_kink_wad: Utilization kink for scalar (WAD)util_max_wad: Max utilization (WAD)min_risk_scalar_wad: Minimum scalar at max utilization (WAD)markets: Array of 16 market pubkeys
Purpose: Central liquidity vault, tracks aggregate trader collateral for LP NAV
Market Account
PDA Seeds: ["market", pool, amm_seed (u64 le bytes)]
Fields:
pool: Associated pooloracle: Rate oracle referenceamm: AMM state (rate curve)pool_rate_accumulator_wad: Pool's cumulative rate payments (WAD, signed)pool_net_notional_wad: Pool's net exposure (WAD, signed)pool_net_entry_fixed_rate_wad: Pool's weighted-avg entry rate (WAD)pool_last_funding_index_wad: Pool's funding index snapshot (WAD)lp_fee_accrued: Accumulated LP fees (token units)protocol_fee_accrued: Accumulated protocol fees (token units)swap_fee_bps: Trading fee (basis points)maintenance_margin_bps: Maintenance margin requirementinitial_margin_bps: Initial margin requirementliquidation_penalty_bps: Liquidation penalty rateprotocol_fee_share_bps: Protocol's share of feesmin_rate_floor_wad: Minimum rate for margin floor (WAD)im_mult_wad: Initial margin floor multiplier (WAD)mm_mult_wad: Maintenance margin floor multiplier (WAD)status: Circuit breaker (Normal/ClosingOnly/Halted)oi_gross_notional_wad: Gross open interest (WAD)oi_cap_notional_wad: OI cap (WAD)dv01_gross_wad: Gross DV01 (WAD)dv01_cap_wad: DV01 cap (WAD)risk_weight_wad: DV01 weight for reserve calc (WAD)min_time_floor_secs: Minimum time for DV01 (seconds)liquidity_weight_bps: Market's share of pool liquidity
Purpose: Defines swap instrument, manages AMM pricing and pool-side counterparty accounting
Margin Account
PDA Seeds: ["margin", pool, authority]
Fields:
authority: User ownerpool: Associated poolcollateral_amount: Deposited collateral in token units (u64, not WAD)positions: Array of up to 8 positions (embedded)
Purpose: User's collateral and position container
Position Struct (Embedded)
Fields:
market: Associated market (Pubkey::default() = empty slot)notional_wad: Position size (WAD-scaled, signed)entry_fixed_rate_wad: Weighted-average entry rate (WAD)last_funding_index_wad: Last funding index snapshot (WAD)float_exposure_wad: Cumulative AMM float exposure (WAD)realized_pnl_wad: Accumulated funding + closed PnL (WAD, signed)opened_at: Position open timestamplast_updated_at: Last modification timestamp
Purpose: Tracks individual swap position state and accumulated PnL
Sign Convention:
notional_wad > 0: Pay fixed, receive floating (long rates)notional_wad < 0: Receive fixed, pay floating (short rates)
RateOracle Account
Type: Regular keypair account (not PDA)
Fields:
rate_index_wad: Cumulative rate index (WAD, signed)last_update_ts: Last update timestampmax_staleness_secs: Maximum age before stale
Purpose: Provides cumulative rate index for settlement
Data Flow
Opening a Position (Swap)
User → swap instruction
↓
1. Load margin, market, oracle, pool accounts
2. Check market status (circuit breaker)
3. Find/allocate position slot
4. Settle existing position (accounting-only)
5. Check pre-swap health
6. Compute DV01-based liquidity depth:
a. Calculate gross LP equity
b. Subtract DV01 reserve for risk-increasing trades
c. Apply utilization-based scalar
7. Execute AMM swap against effective depth
8. Deduct swap fee from realized_pnl_wad (accounting-only)
9. Update position: notional, entry_rate, float_exposure
10. Update pool counterparty: net_notional, entry_rate
11. Update market OI and DV01
12. Check post-swap health and initial margin
13. If position closed: settle realized PnL to collateralAccounting-Only Settlement
settle_position()
↓
1. require_oracle_fresh() - Check staleness
2. Compute rate settlement:
rate_delta = oracle.rate_index_wad - position.last_funding_index_wad
rate_settled_wad = notional_wad × rate_delta
3. Apply settlement (accounting-only, no token transfer):
position.realized_pnl_wad += rate_settled_wad
market.pool_rate_accumulator_wad -= rate_settled_wad
4. Update position.last_funding_index_wadSettlement is idempotent: calling twice with same oracle produces same result.
Liquidation (Crank-Only)
liquidate instruction (permissionless)
↓
1. Settle victim's funding (accounting-only)
2. Compute health - require health < 0
3. Calculate close amount using closed-form approximation:
K = |health| × WAD / (maintenance_rate - penalty_rate - sign_adjusted_mtm)
4. Execute AMM close trade
5. Update victim position (reduce notional)
6. Deduct penalty from victim (accounting-only):
victim.realized_pnl_wad -= penalty_wad
7. Credit penalty to pool (accounting-only):
market.pool_rate_accumulator_wad += penalty_wad
8. Verify post-liquidation health improvedKey difference from traditional liquidations: No tokens transfer to liquidator. The penalty goes entirely to the pool via accounting entries.
LP Deposit/Withdraw
Deposit:
1. Read LP NAV: vault_balance - protocol_fees - total_trader_collateral
2. Transfer quote tokens from depositor to vault
3. Mint shares: floor(amount × total_shares / NAV)
4. Special case: First deposit mints 1:1Withdraw:
1. Read LP NAV
2. Calculate DV01 reserve lock:
reserved = weighted_total_dv01 × max_rate_move_bps / 10000
3. Calculate available LP equity:
available = LP_NAV - reserved (saturating at 0)
4. Burn shares
5. Transfer assets: min(floor(shares × NAV / total_shares), available)Math & Calculations
LP NAV Calculation
LP_NAV = vault_balance - protocol_fees - total_trader_collateralFuture: Add pool_rate_accumulator_wad for unrealized pool PnL.
DV01-Based Liquidity Gating
For risk-increasing trades:
// Step 1: Calculate weighted DV01 reserve
weighted_total_dv01 = Σ(market.dv01_gross × market.risk_weight / WAD)
reserved = weighted_total_dv01 × max_rate_move_bps / 10000
// Step 2: Get market's allocated depth
allocated_depth = lp_equity × market.liquidity_weight_bps / 10000
// Step 3: Calculate risk budget
risk_budget = allocated_depth - reserved // saturating at 0
// Step 4: Apply utilization scalar
util = reserved / allocated_depth
if util <= util_kink: scalar = WAD
else if util >= util_max: scalar = min_risk_scalar
else: scalar = linear_interpolation(WAD, min_risk_scalar)
effective_depth = risk_budget × scalar / WADRisk-reducing trades use full allocated_depth (no gating).
Equity Calculation
equity = collateral_value + sum(realized_pnl) + sum(unrealized_mtm)
where:
collateral_value = token_amount_to_wad(margin.collateral_amount, decimals)
realized_pnl = sum(position.realized_pnl_wad) across positions
unrealized_mtm = sum(compute_mtm(position)) across positionsMTM Calculation
time_to_maturity = maturity - current_time
MTM = (current_rate - entry_rate) × notional × time_to_maturity / SECONDS_PER_YEARHealth Calculation
maintenance_req = sum(|notional| × maintenance_margin_bps / 10000)
health = equity - maintenance_reqHealthy if health >= 0
Margin Floor (Per-Position)
R_eff = max(abs(mark_rate), min_rate_floor)
T_eff = max(time_to_maturity, min_time_floor)
floor_IM = |notional| × R_eff × T_eff × im_mult / (WAD × SECONDS_PER_YEAR)
floor_MM = |notional| × R_eff × T_eff × mm_mult / (WAD × SECONDS_PER_YEAR)
effective_IM = max(bps_IM, floor_IM)
effective_MM = max(bps_MM, floor_MM)Security Model
PDA Validation
All PDAs derived on-chain with seeds validation. Prevents account substitution attacks.
Zero-Copy Accounts
Efficient storage, prevents stack overflow on large accounts.
Checked Arithmetic
All math uses checked_* operations to prevent overflow/underflow.
Oracle Freshness
require_oracle_fresh() prevents stale data from affecting settlement.
Health Before Transfer
NAV and health checked before token transfers prevent manipulation.
Idempotent Settlement
Same oracle index → same settlement result. No double-settlement possible.
Circuit Breakers
Market status controls trading:
- Normal: All trades allowed
- ClosingOnly: Only risk-reducing trades (|new_notional| <= |old_notional|)
- Halted: Only liquidations and admin operations
Program ID
CPrPi4AnqM2RhQXZ9n7gyWb4i7BcTJDDWruhonuRU1jcExternal Dependencies
rate-swap-amm
Git submodule in .deps/rate-swap-amm/
Provides AmmState struct with rate curve logic:
- Maps net exposure → implied fixed rate
- Handles rate bounds and liquidity depth
Deployment Model
- Testnet: Initial development and testing
- Devnet: Community testing, integration testing
- Mainnet: Production deployment (after audits)
Upgradeability
Anchor programs are upgradeable via:
- Program upgrade authority
- Future: Governance-controlled upgrades
Next Steps
- Smart Contracts - Detailed contract reference
- API Reference - Instruction interfaces
- Development Guide - Build and deploy
- Testing - Test suite documentation