Skip to Content
Protocol overview

Protocol overview

An Onchain Traded Fund is a vault that holds a basket of ERC-20 tokens, called constituents. You hold fund shares, which represent a proportional claim on that basket. Minting creates shares by adding basket tokens; redemption burns shares and returns basket tokens after fees.

The creator fixes the ordered token list, initial quantities (bootstrap units), fee rates, expense beneficiary, and fund thesis at creation. The fund does not rebalance, so each token’s percentage of the basket’s market value can change as prices move. The vault tracks token quantities without a price oracle or net asset value (NAV) calculation.

Each fund has its own share token. The separate protocol token, OTF, is described in OTF token economics.

Architecture

The vault holds the basket and issues shares. The entry and exit router handles the swaps needed to assemble or sell the basket. Holders can also redeem directly from the vault to receive the constituent tokens.

Scroll horizontally to explore the diagram.

The application supplies basket units to OTFFactory, which creates a ManagedOTFVault. Holders mint or redeem through OTFEntryExitRouter and approved Uniswap V3 or V4 adapters, or redeem basket tokens directly from the vault. The vault holds the tokens and accounts for fund shares.
Fund creation, direct redemption, and routed basket settlement.Open full-size diagram

OTFFactory deploys ManagedOTFVault clones and records their addresses. Depositors mint shares through OTFEntryExitRouter, which uses approved trade adapters for swaps. Protocol-token launch, vesting, rewards, and fee buybacks use separate contracts.

Creating a vault

Creating a vault fixes its basket and fee terms without depositing any tokens. The connected wallet becomes its creator. OTFFactory.createVault accepts:

  • A name of at most 50 UTF-8 bytes, including the exact suffix OTF, with at least one ASCII letter or digit before that suffix; a ticker of 1–8 ASCII letters or digits (A–Z, a–z, 0–9); and a fund thesis of one to 2,048 UTF-8 bytes.
  • Two to 20 ordered constituents and their raw token units per share (1e18 share units).
  • An expense beneficiary who receives the creator portion of fees.
  • Annual expense, mint, and redeem rates, capped at 10%, 2%, and 1% respectively.

The first mint funds the empty vault.

Application calculation

The application converts your chosen percentages into initial token quantities using offchain prices and a $1 target per share. It starts with market-cap weights and recalculates them when you change the basket. Editing a percentage switches to manual mode: new tokens start at 0%, and removing a token leaves the other percentages unchanged. Selecting Market-cap weighted restores automatic weights.

Every percentage must be positive, and the total must be exactly 100%. The application uses 18-decimal percentage units. With a $1 target V = 1e18, percentage units w_i, total percentage units T, USD price P_i scaled by 1e18, and token decimals d_i:

allocationUsdWad_i = floor(V * w_i / T) bootstrapUnits_i = floor(allocationUsdWad_i * 10**d_i / P_i)

Each token must receive at least one raw unit. The application shows the minimum percentage needed and the allocation after rounding.

The $1 target is not a peg or guaranteed market price. The vault stores the final raw units and creation parameters; prices and weighting labels stay offchain.

The fund label is Market-cap weighted only if every final percentage exactly matches the calculated default; otherwise it is Modified market-cap weighted. The creator’s browser can retain this allocation snapshot. Without it, the fund page shows Weighting method unavailable.

Minting, redemption, and accounting

Minting adds each constituent in proportion to the shares being created. Redeeming returns the corresponding portion of each constituent, after fees. For example, with all fees set to zero and sound backing, a vault with 100 shares backed by 100 token A and 200 token B returns 10 token A and 20 token B when you redeem 10 shares.

Vault shares have 18 decimals. The first mint must deliver at least 0.01 shares (1e16 raw units) to the depositor. It uses the creator’s bootstrap units; later mints use the vault’s accounted balances. Basket inputs cover both the depositor’s shares and mint-fee shares. With grossShares equal to that combined amount, the first mint requires:

amountIn_i = ceil(bootstrapUnits_i * grossShares / 1e18)

Later mints use accounted balances and total supply after pending expense fees:

amountIn_i = ceil(accountedBalance_i * grossShares / totalSupply)

A redemption pays each constituent’s pro-rata accounted balance, rounded down, after any redemption fee. If the shares redeemed after fees equal total supply, the vault returns all accounted balances exactly.

The vault records deposits and withdrawals in accountedBalance for each token. Direct token donations do not increase this ledger or the amounts owed to shareholders. Normal minting and redemption fail if actual backing falls below the ledger.

After a redemption, supply below 0.01 shares triggers permanent shutdown; exactly 0.01 stays live. Shutdown stops minting and fee accrual but allows holder exits. A vault that reaches zero cannot restart.

Direct redemption needs no swap liquidity: holders call redeemInKind(shares, receiver, minAmountsOut, skipMask) to receive basket tokens. While active, the vault charges configured fees and requires full backing for each unskipped asset. After shutdown, exits charge no new fees and use the lower of actual and accounted balances.

The recovery parameter skipMask lets a holder skip a broken token and permanently forfeit their entitlement to it. The vault reduces the ledger without calling or moving that token. If backing is deficient, remaining holders may benefit from the tokens left behind. The normal application does not expose this control.

Fees

The three fee rates and beneficiary cannot change. Each fee affects holders differently:

FeeEffect on holders
Annual expenseReduces the basket backing per share over time by issuing additional fee shares.
Mint feeRequires extra basket tokens to back the fee shares issued alongside your shares.
Redemption feeReduces the shares used to calculate your basket payout.

The vault collects fees by issuing shares. Annual expense accrues over time and is collected when fees are checkpointed. Preview functions include pending fees and use the same rounding as execution.

Fee shares go to BuybackCollector. When charging a fee, the vault uses its accounted protocol OTF balance to determine the creator and buyback portions. That fee keeps its recorded split through settlement; direct OTF donations do not affect it.

Only the beneficiary can settle the complete pending fee account. They can sell the fee shares for WETH or redeem them and sell the basket. The collector pays the beneficiary in WETH, spends the rest buying OTF through the canonical pool, and burns the purchased OTF. See fund fees for formulas.

Routed entry and exit

The router lets you mint from one input asset or redeem into one output asset by swapping the basket tokens as needed. Anyone can submit a route as an ordered SwapLeg[]. Each leg is one swap instruction; legs can split input across adapters or spend an earlier leg’s output. They can use and refund only balances created during the current call.

The router owner approves and revokes adapters. Each approved adapter must be bound to that router, and revocation takes effect immediately. Users rely on the owner to approve safe adapter code.

The router groups consecutive trades for one adapter into a batch. It preserves their order, including dependencies between different adapters. The generic interface accepts multiple input and output tokens; the entry router reconciles returned amounts against its actual balances.

UniswapUniversalRouterAdapter builds one command sequence per batch using Universal Router 2.1.1. Route data starts with 0x03 for a packed V3 path or 0x04 for an encoded V4 currency and pool-key path. Each trade permits at most three hops; a batch permits 40 trades. V3 validation checks factory membership, pool properties, and the CREATE2 address derived from the configured pool init-code hash. V4 validation checks initialized pool keys through StateView and bounds hook data.

The adapter fixes recipients, enforces minimum outputs, clears temporary allowances, and preserves earlier contract balances. It prefunds the Universal Router with this operation’s tokens. Intermediate outputs can fund later commands; unused tokens return through the entry router.

For supported two-hop mint routes, the planner can combine a shared first pool and keep each downstream pool distinct. It quotes the total first-swap input and each downstream requirement. Half the quoted intermediate surplus strengthens the first-swap price tolerance; the other half increases downstream budgets. Those budgets follow quoted downstream costs, with the final trade receiving integer rounding residue. Original constituent minimums remain mandatory. Failed quotes, excessive price impact, repeated downstream pools, or V4 hooks retain the original route.

A combined route can change minted shares and the composition of refunds. The application simulates the complete plan before offering execution.

Native ETH boundary

For basket entry and exit, mintFromNative wraps the exact ETH input to canonical WETH and refunds WETH change as ETH. redeemToNative sells constituents for WETH, checks the minimum output, and unwraps it to ETH for the caller. A failed payout reverts the transaction.

Vaults and adapter trade boundaries use ERC-20 tokens. V4 pools may use native ETH; the adapter wraps or unwraps it within the command sequence. The router preserves pre-existing ETH and WETH and accepts callback ETH only from canonical WETH.

Application execution

Apart from ETH/WETH wrapping, Swap requires a vault share or protocol OTF on at least one side. Fund routes can trade shares through a pool, mint a basket, redeem a basket, or redeem one fund into another. The application selects the fresh quote with the highest expected output among queried routes. Users can choose another route.

Direct and basket quotes use registered V3, V4, and mixed paths on both networks. Enabled assets can trade through approved pools without a verification badge. Mainnet can request a fallback path from the Trading API; testnet uses registered routes only. Both networks use the canonical OTF/WETH helper for protocol OTF constituents, including the launch hook’s price limits. Mainnet remains unavailable until its deployment is configured.

Fund-to-fund swaps settle through WETH. Redemptions target the requested token and can combine compatible shared exit steps before their final conversion. The planner re-quotes combined amounts and simulates the complete transaction. Each basket quote allows one API discovery request per missing endpoint pair; later sizing uses authenticated paths and on-chain Quoters. Simulation failures return unavailable. Quotes report simulated gas and refunds while keeping output estimates conservative.

ETH and WETH appear separately in the asset selector. ETH needs no token approval, and Max leaves ETH for gas.

Before submission, the application simulates the exact transaction with eth_call and estimates gas. If the output vault has zero supply, its guaranteed minimum output must be at least 0.01 shares. Later mints have no bootstrap minimum.

During the protocol-token launch, OTFLaunchRouter handles OTF/WETH trades, with ETH wrapping for buys and unwrapping for sells. At a range boundary it takes only the input used, leaves unused tokens in the wallet, and refunds unused native ETH. After graduation, the application uses the ordinary Uniswap route.

Verification

Repository checks cover Solidity compilation and tests, security gates, application tests, type checking, and builds. Passing them does not constitute an audit or approve a deployment.

Last updated on