Skip to main content

IndexToken

contracts/src/index/IndexToken.sol — a plain, fixed-supply, never-mintable burnable ERC-20 (cloned per launch via EIP-1167) that carries only its basket metadata and the creator's fee-split configuration. It extends ERC20BurnableUpgradeable behind an immutable implementation.

It is deliberately boring: there is no fee logic, no dividend accumulator, and no mint function on the token itself. Fees are taken by the shared IndexFeeHook on the pool; backing lives in the IndexTreasury.

Fixed, never-mintable supply

uint256 constant FIXED_SUPPLY = 1_000_000e18;

The entire supply is minted once, at initialize, to the factory (which seeds it as single-sided liquidity). There is no mint function anywhere — not for the creator, not for bonds, not for governance. Supply can only ever decrease:

  • ERC20Burnable — holders can burn.
  • IndexTreasury.redeem() calls burnFrom to burn on redemption.

Bonds do not mint. They sell index tokens the treasury accumulated via the buyback fee share (see BondDepository).

Basket metadata & the 3-way split config

Set once at initialize and immutable thereafter:

function stocks() external view returns (address[] memory);
function isStock(address) external view returns (bool);
function indexTreasury() external view returns (address);
function rfvShareBps() external view returns (uint16); // USDG-reserve share of each fee
function buybackShareBps() external view returns (uint16); // in-kind index-buyback share

The IndexFeeHook reads rfvShareBps and buybackShareBps on every swap to route the fee. The basket share is implied: NON_PROTOCOL_BPS − rfvShareBps − buybackShareBps.

Per-index governance (no supply power)

address public admin; // default = creator
function setAdmin(address) external; // onlyAdmin — starts a 2-step transfer
function acceptAdmin() external; // pendingAdmin accepts

The admin (creator by default, transferable to a DAO/multisig in two steps) can only enable and configure the index's bond markets within protocol bounds. It has no power over supply — the token is not mintable by anyone.

Initialization guards

initialize(cfg, indexTreasury_, supplyRecipient) enforces:

  • non-empty basket, ≤ MAX_BASKET_SIZE (16),
  • no duplicates,
  • every stock on the FeeRegistry allow-list,
  • no stock may equal the reserve asset (USDG) or the token itself,
  • rfvShareBps + buybackShareBps ≤ NON_PROTOCOL_BPS (9000).

IndexConfig (from IIndexToken.sol) is small — the fee schedule and launch curve are protocol constants, so the creator only supplies identity, basket, and split:

struct IndexConfig {
string name;
string symbol;
address creator; // overwritten with msg.sender by the factory
address[] stocks; // 1..16 allow-listed tokenized equities
uint16 rfvShareBps; // USDG-reserve share of each fee (bps of total), immutable
uint16 buybackShareBps; // in-kind index-buyback share (bps of total), immutable
}

Address mining (USDG is always currency0)

The token is not deployed at an arbitrary address: the factory CREATE2-mines the clone salt so the resulting address sorts above USDG. That makes USDG currency0 and the index currency1 on every pool, so the pool price reads directly as USDG-per-index and the single-sided launch curve is well-defined. ~50% of salts qualify, so mining converges in a couple of iterations.

What was removed (and why)

  • The fee-on-transfer logic — fees are now taken by the V4 hook on the pool, not on transfer, so the token is a fully standard, composable ERC-20.
  • The on-chain dividend accumulator — removed earlier for security (overflow-DoS / griefable maturity).
  • The mint authority (former bond-minter) — bonds moved to a treasury-inventory model, so the launch supply is a hard invariant.