How it works
IndexPad's whole design is a single fee split that runs inside each swap, on-chain, with no keeper. This page traces one swap from trade to backing, and shows how holders exit.
The lifecycle of a swap
1. Every swap pays the fee inline
Each index trades on a USDG (currency0) / index (currency1) Uniswap V4 pool wired to the shared IndexFeeHook. The pool is a dynamic-fee pool and the hook overrides the LP fee to 0 — it takes the entire fee itself, via the V4 delta mechanism, and splits it in the same lock:
- A flat 0.30% protocol base fee on swap volume (
PROTOCOL_BASE_FEE_BPS), plus a volatility-scaled index fee (base → 3% cap) with a launch snipe guard (see below). - Because USDG and the index are the pool's two currencies, every swap has exactly one specified and one unspecified side. The USDG portion of the fee is skimmed on the USDG side; the in-kind index buyback on the index side. One is taken in
beforeSwap, the other inafterSwap.
The hook never swaps the index pool itself (the buyback is taken in-kind), so there is no same-pool reentrancy. The only inline swaps are on independent, hookless USDG/stock reserve pools.
2. The fee splits three ways (plus the fixed protocol cut)
From the collected fee the hook routes, in one transaction:
- Protocol — the 0.30% base + 10% of the index fee (
PROTOCOL_SHARE_BPS) → the protocol treasury in USDG. - Reserve —
rfvShareBpsof the index fee → the index treasury as USDG reserve (the redeemable floor). - Buyback —
buybackShareBpsof the index fee → the index token itself, taken in-kind from the swap into the treasury as bond inventory (no swap, no reentrancy; supply is untouched, this is float). - Basket — the remainder buys each basket stock with an even slice of USDG on its hookless
USDG/stockreserve pool, taking the stock straight into the treasury.
Each basket leg is isolated with try/catch: a dead or thin stock pool reverts only that leg, and its USDG falls through to the reserve — a bad stock can never brick a user's trade. The hook emits a FeeSplit event per swap.
3. Backing accumulates in the treasury
All three non-protocol destinations land in the per-index IndexTreasury: USDG reserve, basket stocks, and index-token bond inventory. Together (reserve + stocks) they are the backing. The treasury exposes a Chainlink-priced navUsdg() / backingPerTokenUsdg() for display, but — crucially — the redemption path never touches an oracle.
The bond inventory is the index's own token; it is deliberately excluded from NAV (self-referential) and is untouchable by redemption. It exists only to seed future bond markets.
4. Holders exit by redeeming
IndexTreasury.redeem(amount) burns index tokens for a pro-rata slice of every backing asset — USDG and each basket stock — less a fixed 5% redemption spread that stays in the vault. Because the spread stays behind and burning shrinks supply, every redemption strictly raises backing-per-token for the holders who remain. Each payout leg is isolated: a frozen/paused RWA is skipped (its share stays as backing) rather than bricking the whole exit. See RFV & redemption.
5. (Later) Bonds sell the inventory
The BondDepository — built, but enabled by the creator later — lets users deposit USDG or a basket RWA into the treasury in exchange for index tokens sold from the buyback inventory, at a demand-driven 0–10% discount to the pool's TWAP, floored at backing, vested over a term. Bonds never mint: the payout is float the treasury already bought back. Every bond is strictly accretive (the deposit raises NAV, supply is untouched).
The dynamic fee & launch snipe guard
The index fee is protocol-fixed but volatility-responsive, computed deterministically within a block so beforeSwap and afterSwap agree:
| Regime | Fee |
|---|---|
First 2 blocks after launch (LAUNCH_GUARD_BLOCKS) | 5% (LAUNCH_GUARD_FEE_BPS) — taxes snipers of the thin floor |
| Steady state | base (1.0% buy / 1.5% sell) + a decayed-volatility term, clamped to 3% (MAX_DYNAMIC_FEE_BPS) |
The hook keeps a per-pool volatility accumulator (bumped by each swap's absolute tick move, decaying ~1%/sec of itself) and a Uniswap-style cumulative-tick TWAP. The same observation feeds the bond market's manipulation-resistant price.
Why inline & keeper-free matters
Earlier designs used a keeper (or a permissionless "convert poker") that batched accrued fees, swapped them, and distributed — a point of centralization and latency. The current design deletes that entirely:
| Earlier (keeper / batch convert) | Now (inline hook split) | |
|---|---|---|
| When fees are processed | Later, when someone runs the job | In the same swap |
| Who triggers it | A keeper / any poker | Nobody — it's part of the trade |
| Off-chain infra per index | Keeper + DB (or a poker cron) | None |
| Failure mode | Unprocessed fees pile up | N/A — there is nothing to process |
| Trust assumption | Keeper honesty / liveness | Contract code only |
There is no FeeSwapEngine, no IndexDividends, and no dividend/merkle path in the live protocol — they were removed in the redesign.
Supply and launch economics
- Supply is fixed at 1,000,000 and never mintable — no premine, no founder allocation. The
IndexFactorymints the whole supply to itself and seeds it as 5 stepped single-sided liquidity positions from the 20k-mcap floor ($0.02) up to $1.00. - The index clone address is CREATE2-mined to sort above USDG, so USDG is always
currency0and the pool price reads directly as USDG-per-index. - An optional creator pre-buy runs atomically in the launch transaction as a real market buy through the hook — it pays the fee like any other trade.
- An optional native launch fee (
FeeRegistry.launchFeeWei) is forwarded to the protocol treasury at creation.