How it works
IndexPad's whole design is a single fee pipeline that runs end-to-end on-chain. This page traces one unit of trade fee from swap to claim.
The lifecycle of a fee
1. Fees accrue in-token
Every IndexPad token extends FeeToken: on transfers to/from a registered AMM pair, the buy/sell fee (immutable, hard-capped at 1000 bps) is skimmed inside the token and parked on the token contract itself. Wallet-to-wallet transfers are never taxed, and fee-exempt infrastructure (the token, the FeeProcessor, the treasuries) is fixed at initialization — there is no arbitrary exemption list and no blacklist.
2. Anyone converts
FeeProcessor.convert(index, minReserveOut, stockMinOuts, deadline) is permissionless. The caller supplies slippage bounds (each must be > 0); the processor then:
- Pulls the accrued fee tokens (
releaseFees()— untaxed, the processor is fee-exempt). - Swaps them to the reserve asset (USDG) through the registered router.
- Splits the proceeds: 10% → protocol treasury (fixed),
rfvShareBps→ the index's RFV treasury, and the remainder buys the basket's stock tokens. - Transfers the bought stocks to the IndexToken and calls
distribute(stock)for each.
Bad basket legs can't brick the pipeline: each stock buy is isolated with try/catch (with the min-out capped at the router quote so the skip path can't be forced), and if nothing was distributable the call reverts rather than silently consuming fees.
3. The accumulator credits every holder
distribute(stock) folds the newly received stock balance into a magnified dividend-per-share index (magnifiedDividendPerShare[stock], scaled by 2¹²⁸). Per-holder corrections are maintained automatically inside the ERC-20 _update hook, so balances can move freely — buy, sell, transfer — and accrued dividends stay exact.
Key properties:
balanceOfis standard — no reflections. Dividend state is separate.- Excluded accounts (the token itself, the FeeProcessor, both treasuries, registered AMM pairs) don't accrue — pool liquidity never dilutes holders.
- Work per trade is O(number of stocks in the basket), bounded by MAX_BASKET_SIZE = 16.
4. Holders claim — or redeem
claim()transfers every accrued stock token to the holder (with a per-stockclaim(stock)escape hatch if one equity is paused). The holder keeps their index tokens and keeps accruing.IndexTreasury.redeem(amount)burns index tokens for a pro-rata share of the USDG reserve — the RFV floor. Because redemption shrinks supply, it can only raise the floor for everyone else.
Why keeper-free matters
Earlier versions used a keeper that snapshotted balances and published Merkle roots. That was a point of centralization — and it couldn't scale to hundreds of index tokens. v3 replaced it with the in-token accumulator:
| v2 (keeper + Merkle) | v3 (on-chain accumulator) | |
|---|---|---|
| Distribution trigger | Trusted keeper | Anyone (convert is permissionless) |
| Holder accounting | Off-chain snapshot | ERC-20 transfer hook, exact |
| Claim | Merkle proof | Direct claim(), no proof |
| Infra per index | Keeper + DB | None |
| Trust assumption | Keeper honesty/liveness | Contract code only |
The optional keeper/keeper.ts script still exists, but it is just a convenience poker — it computes reasonable min-outs and calls convert(). Anyone can run it; nobody has to.
Supply and launch economics
- Supply is fixed at launch: 1% is minted to the protocol treasury, 99% to the creator. Nothing can mint afterwards; the token is burnable (
ERC20Burnable— RFV redemption burns). - The creator seeds the AMM pool (index/USDG) and registers the pair; the pair is validated as a genuine v2 pool and automatically dividend-excluded.
- An optional native launch fee (
FeeRegistry.launchFeeWei) is forwarded to the protocol treasury at creation.