> For the complete documentation index, see [llms.txt](https://knox-fi.gitbook.io/docs/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://knox-fi.gitbook.io/docs/technical-architecture.md).

# 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:**

```solidity
function deploy(
    SpectrumPoolConfig memory config,
    address underlyingMarket,
    address allocator,
    address protocolFeeBeneficiary
) external returns (address accountant)
```

**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

```solidity
interface IAllocator {
    // Synchronous
    function deposit() external returns (uint256 shares);
    function redeem(uint256 shares) external returns (uint256 assets);

    // Asynchronous
    function requestRedeem(uint256 shares) external returns (bytes32 handle, uint256 immediateAssets);
    function claimRedeem(bytes32 handle) external returns (uint256 assets);
    function pendingRedeem(bytes32 handle) external view returns (bool claimable, bool completed);

    // Common
    function convertedBalanceOf(address market, address account) external view returns (uint256);
}
```

### 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:**

```solidity
struct ExitRecord {
    uint256 requestedShares;
    uint256 unlockAt;
    bool completed;
}

mapping(bytes32 => ExitRecord) public exitRecords;
```

**Claim Target Registration:**

`CLAIM_TARGET_ADMIN_ROLE` can register new unstake contracts for new asset types.

## Deposit Flow Details

{% stepper %}
{% step %}

#### Router Deposit (Primary Path)

```
User
│
└─> KnoxRouter.depositSeniorSpectrum(accountant, assets, receiver)
   ├─> transferFrom(user, router, assets) [or Permit2]
   ├─> approve(accountant, assets)
   ├─> accountant.depositSenior(assets, receiver)
   │   ├─> _capturePreDepositSeniorState() [validate capacity]
   │   ├─> calculate shares via compounding
   │   ├─> _pullAndDeposit(router, allocator, assets)
   │   │   ├─> transferFrom(router, allocator, assets)
   │   │   └─> allocator.deposit() → market
   │   └─> seniorVault.accountantMint(receiver, shares, assets)
   └─> approve(accountant, 0) [cleanup]
```

{% endstep %}

{% step %}

#### Direct Vault Deposit (ERC4626 Path)

```
User
│
└─> TrancheVault.deposit(assets, receiver)
   ├─> transferFrom(user, allocator, assets)
   ├─> accountant.registerDeposit(assets)
   │   ├─> validate cap
   │   ├─> _capturePreDepositSeniorState() [if senior]
   │   └─> allocator.deposit() → market
   ├─> shares = accountant.previewDeposit(vault, assets)
   └─> _mint(receiver, shares)
```

**Limitation:** Cannot create new spectrum vaults (only accountant can)
{% endstep %}
{% endstepper %}

## 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:**

```solidity
function _capturePreDepositSeniorState() {
    yieldLastSeniorDeposit = seniorTrancheCurrentValue() - seniorVault.totalDeposits();
    timeLastSeniorDeposit = block.timestamp;
}
```

**Computing Current Value:**

```solidity
function seniorTrancheCurrentValue() {
    uint256 principal = seniorVault.totalDeposits() + yieldLastSeniorDeposit;
    uint256 elapsed = block.timestamp - timeLastSeniorDeposit;
    uint256 remaining = (tStart + dPeriod) - timeLastSeniorDeposit;
    return compoundAssets(principal, rSenior, elapsed, remaining);
}
```

**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 =

```
compoundAssets($50, 5%, 182d, 182d) = 51.23
```

* 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

{% stepper %}
{% step %}
Validate grid alignment

```solidity
require(apyBps > rSenior && apyBps <= rMaxSpectrum);
require((apyBps - rSenior) % spectrumGridStep == 0);
```

{% endstep %}

{% step %}
Check if vault exists

```solidity
address vault = spectrumVaults[apyBps];
if (vault == address(0)) {
```

{% endstep %}

{% step %}
Clone vault implementation (EIP-1167)

```solidity
    vault = Clones.clone(trancheVaultImpl);
```

{% endstep %}

{% step %}
Initialize vault

```solidity
    string memory name = string.concat("Knox Spectrum ", apyBps.toString(), "bps");
    string memory symbol = string.concat("kS", apyBps.toString());
    vault.call(abi.encodeWithSignature(
        "initialize(address,string,string,address)",
        asset, name, symbol, address(this)
    ));
```

{% endstep %}

{% step %}
Register vault

```solidity
    spectrumVaults[apyBps] = vault;
    vaultApyBps[vault] = apyBps;
    _isTranche[vault] = true;
```

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

```solidity
    _insertRate(apyBps);
}
```

{% endstep %}

{% step %}
Proceed with deposit

```solidity
// ...
```

{% endstep %}
{% endstepper %}

**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

```solidity
function proxyCall(address target, bytes calldata data) external {
    require(block.timestamp >= tStart + dPeriod + 28 days);
    require(factory.accessController().hasRole(RESCUE_ROLE, msg.sender));

    // Safety: If targeting pool asset, only allow transfer to vaults/accountant
    if (target == asset) {
        bytes4 selector = bytes4(data[:4]);
        require(selector == TRANSFER || selector == TRANSFER_FROM);

        address recipient = abi.decode(data[36:68], (address));
        require(
            recipient == seniorVault ||
            recipient == juniorVault ||
            _isTranche[recipient] ||
            recipient == address(this)
        );
    }

    target.call(data);
}
```

**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
