For the complete documentation index, see llms.txt. This page is also available as Markdown.

Technical Architecture

Knox Protocol's smart contract system is built on a modular, upgradeable architecture using EIP-1167 minimal proxies for gas-efficient pool deployment.

System Overview

SpectrumFactory

├── SpectrumAccountant
├── AccessController

├── TrancheVault
├── IAllocator (Senior / Spectrum / Junior)

└── UnderlyingMarket

Core Contracts

KnoxRouter

Purpose: User-facing entry point for all deposits

Key Functions:

  • depositSeniorSpectrum(accountant, assets, receiver)

  • depositSpectrumTranche(accountant, apyBps, assets, receiver)

  • depositJuniorSpectrum(accountant, assets, receiver)

Permit2 variants:

  • depositSeniorSpectrumPermit2(...)

  • etc.

Flow:

  • Pull assets from user (via transferFrom or Permit2)

  • Approve accountant to spend assets

  • Call corresponding accountant deposit function

  • Reset approval to 0 (defensive cleanup)

Why Router?

  • Single contract interface for users

  • Handles approval management

  • Supports both ERC20 approvals and Permit2 signatures

  • Cleaner UX than direct accountant calls

SpectrumAccountant

Purpose: The brain of each pool

Responsibilities:

  • Deposit validation and processing

  • Share pricing calculations

  • Lazy spectrum vault creation

  • Waterfall computation

  • Settlement execution

  • Async redeem tracking

  • Rescue mechanism

Key Functions:

Deposits

  • depositSenior(assets, receiver) — validates capacity, snapshots yield, mints shares

  • depositSpectrum(apyBps, assets, receiver) — validates grid, creates vault if new, mints shares

  • depositJunior(assets, receiver) — computes residual value, mints shares

  • registerDeposit(amount) — callback from vault direct deposits

Lifecycle

  • redeemShares(shares) — exit underlying market (sync or async)

  • claimRedeemedAssets() — claim async exits (permissionless)

  • settlePool() — execute waterfall, transition to SETTLED

Views

  • seniorTrancheCurrentValue() — compounded senior value

  • juniorTrancheCurrentValue() — residual after waterfall

  • seniorCapacity() — max senior deposits allowed

  • underlyingAssetCurrentValue() — current market value

  • maxPendingExitUnlockAt() — latest async exit timestamp

Deployment: Cloned via SpectrumFactory.deploy() (EIP-1167 minimal proxy)

TrancheVault

Purpose: ERC4626 vault for each tranche position

Instances per pool:

  • 1 × Senior vault (created at deployment)

  • 1 × Junior vault (created at deployment)

  • N × Spectrum vaults (created lazily)

Key Modifications from Standard ERC4626:

  • deposit() — blocked after pool maturity

  • withdraw() — blocked until pool is SETTLED

  • Share pricing delegated to accountant via callbacks

  • accountantMint() — function for accountant-driven minting

Share Pricing:

Calls accountant.previewDeposit(vault, assets) which dispatches based on vault type:

  • For senior/spectrum: compounding formula

  • For junior: proportional to current waterfall value

Special Behavior:

  • Last redeemer gets entire remaining balance (prevents dust)

Deployment:

  • Senior/junior: cloned by factory during pool deployment

  • Spectrum: cloned by accountant on first deposit at new grid rate

SpectrumFactory

Purpose: Deploy new pools atomically

Single-Transaction Deployment:

What Gets Created:

  • SpectrumAccountant clone (EIP-1167)

  • Senior TrancheVault clone

  • Junior TrancheVault clone

All initialized in the same transaction. Pool immediately transitions to ACTIVE.

Access Control:

  • DEPLOYER_ROLE — can call deploy()

  • FEE_SETTER_ROLE — can toggle applyProtocolFees

  • RESCUE_ROLE — delegated to accountants for rescue calls (resolved dynamically from AccessController)

Global State:

  • applyProtocolFees — global on/off switch for protocol fee collection

AccessController

Purpose: Centralized role management

Based on: OpenZeppelin AccessControl

Roles:

  • DEFAULT_ADMIN_ROLE — can grant/revoke all roles

  • DEPLOYER_ROLE — authorized to deploy pools

  • FEE_SETTER_ROLE — toggle protocol fees

  • RESCUE_ROLE — execute rescue calls on accountants

  • CLAIM_TARGET_ADMIN_ROLE — register claim targets (Strata allocator)

Dynamic Resolution: Accountants query the factory's AccessController at call time, allowing role rotation post-deployment.

Allocators (IAllocator Interface)

Purpose: Pluggable adapters between accountant and yield markets

Interface Methods

Implementations

ERC4626Allocator

Markets: Standard ERC4626 vaults (e.g., Morpho Blue) Redemption: Synchronous

Flow:

  • deposit()vault.deposit()

  • redeem()vault.redeem()

AaveV3L2Allocator

Markets: Aave V3 on L2 chains Redemption: Synchronous Optimization: Calldata compression for L2 gas savings

Flow:

  • deposit()pool.supply()

  • redeem()pool.withdraw()

ERC4626AsyncStrataMainnetAllocator

Markets: Strata Finance (Ethena USDe, Neutrl NUSD on mainnet) Redemption: Asynchronous (7-day cooldown)

Flow:

  • deposit()vault.deposit()

  • requestRedeem()vault.requestRedeem()

  • + store cooldown in exit record

  • claimRedeem() → check IUnstakeCooldown.activeRequests()

  • + finalize()

Exit Handle Format:

Claim Target Registration:

CLAIM_TARGET_ADMIN_ROLE can register new unstake contracts for new asset types.

Deposit Flow Details

1

Router Deposit (Primary Path)

2

Direct Vault Deposit (ERC4626 Path)

Limitation: Cannot create new spectrum vaults (only accountant can)

Senior Value Tracking (Snapshot Mechanism)

Problem: Deposits arrive at different times, each earning from their deposit timestamp to maturity.

Solution: Piecewise compounding via snapshots

State Variables:

  • yieldLastSeniorDeposit — accumulated yield at last deposit

  • timeLastSeniorDeposit — timestamp of that snapshot

Before Each Senior Deposit:

Computing Current Value:

Effect: Each deposit's accumulated value becomes the new base principal for subsequent compounding.

Example:

  • Day 0: Deposit $100 → 105 shares (5% APY, 365d)

  • Day 183: Senior value = $102.47

  • Snapshot:

    • yieldLastSeniorDeposit = $2.47

  • New deposit $50 → shares =

  • New principal: $150 + $2.47 = $152.47

  • This $152.47 now compounds for remaining 182 days

Lazy Spectrum Vault Creation

When: First deposit at a new grid rate

1

Validate grid alignment

2

Check if vault exists

3

Clone vault implementation (EIP-1167)

4

Initialize vault

5

Register vault

Insert rate into sorted array (O(n) insertion sort)

6

Proceed with deposit

activeSpectrumRates Array: Maintained in sorted order (low → high) for O(n) waterfall iteration

Rescue Mechanism

Availability: tStart + dPeriod + 28 days after maturity

Purpose: Recover stuck assets from edge cases

Safety Rails:

  • Role resolved dynamically from factory (allows rotation)

  • Asset transfers restricted to tranche vaults or accountant

  • Unrestricted for other targets (allocators, markets)

Key Takeaways

  • Modular design: Router → Accountant → Allocator → Market

  • Gas-efficient: EIP-1167 clones for pool deployment

  • Lazy creation: Spectrum vaults created on-demand

  • Flexible allocators: Pluggable adapters for any yield source

  • Snapshot tracking: Piecewise compounding for senior value

  • Safety mechanisms: Dynamic roles, rescue with restrictions

  • Async support: Native handling of cooldown-based markets