Loading
Loading
Two contracts. IndexFactory is deployed once; IndexVault is deployed once per launch, by the factory. Source in contracts/src/. ABIs ship in the @cf/abi workspace package (packages/abi/src/abi.ts) as indexfactoryAbi and indexvaultAbi.
Set RPC=https://rpc.mainnet.chain.robinhood.com for the cast examples below. Replace $FACTORY with the address from contracts/deployments/4663.json or the site's NEXT_PUBLIC_INDEX_FACTORY.
| Contract | Address |
|---|---|
| Pons v2 launch factory | 0x7eD598BcEf8bd9Edd8C97A195C6d13f40801EC7e |
| Pons v2 fee escrow | 0xd3AFEB2a57f70eF218Aa82451c51B2fb0416Ac9e |
| Pons v2 meme hook (Uniswap v4) | 0xE5e702641Ea86F4ae6cC3cDaeD2B886f976Be044 |
| Pons launch locker | 0x267444D099b10fB5Ed7c3Cc7B7c767AdcA574952 |
| Uniswap v3 factory (stocks dex) | 0x1f7d7550B1b028f7571E69A784071F0205FD2EfA |
| SwapRouter02 (stocks dex) | 0xCaf681a66D020601342297493863E78C959E5cb2 |
| WETH | 0x0Bd7D308f8E1639FAb988df18A8011f41EAcAD73 |
| Multicall3 | 0xcA11bde05977b3631167028862bE2a173976CA11 |
| NVDA (tokenized) | 0xd0601CE157Db5bdC3162BbaC2a2C8aF5320D9EEC |
Explorer: https://robinhoodchain.blockscout.com.
Ownable2Step. Deployed with the constants in contracts/script/Deploy.s.sol.
| Name | Type | Mainnet value |
|---|---|---|
PONS_FACTORY | IPonsV2Factory | Pons launch factory above |
V3_FACTORY | address | Uniswap v3 factory above |
ROUTER | address | SwapRouter02 above |
WETH | address | WETH above |
LAUNCH_CONFIG_ID | uint256 | 0 |
TWAP_WINDOW | uint32 | 900 (15 minutes) |
MAX_TICK_DEVIATION | int24 | 200 |
REWARD_DURATION | uint256 | 604800 (7 days) |
APP_FEE_BPS | uint16 | 2000 (20%) |
MAX_APP_FEE_BPS | constant | 5000 |
launch(LaunchParams p) payable → (token, curve, vault)Deploys the vault, launches on Pons, binds, runs the dev buy, records the launch, refunds leftover ETH.
struct LaunchParams {
string name;
string symbol;
string logo; // URL
string description;
IPonsV2Factory.Socials socials; // twitter, telegram, discord, website, farcaster (URLs)
address stock; // tokenized stock the vault accumulates
uint24 stockPoolFee; // WETH/stock v3 fee tier; ignored when pairWithStock
bool pairWithStock; // price the curve in the stock
uint16 vaultTaxBps; // 0..1000, Pons creator tax, all to the vault
bytes32 expectedEconomics; // from PONS_FACTORY.previewLaunchEconomics(configId, pairToken)
uint256 devBuyQuote; // opening buy in the pair asset; 0 for none
uint256 devBuyMinTokensOut;
address[] snipeTaxExemptions; // up to 31; caller is added by the factory
bytes32 salt; // scoped per caller
}
msg.value must equal PONS_FACTORY.launchFee() plus devBuyQuote for an ETH pair, or the launch fee alone for a stock pair. For a stock pair with a dev buy, approve the factory for devBuyQuote of the stock first.
Reverts: ZeroAddress, TooManyExemptions, StockNotApprovedAsPair, StockPoolMissing, WrongValue, RefundFailed, plus anything Pons reverts with.
Emits IndexLaunched(token, vault, stock, curve, creator, pairedWithStock, devBuyQuote, devTokensOut, name, symbol).
The vault address is deterministic: CREATE2 with salt keccak256(abi.encode(msg.sender, p.salt)).
launches(address token) → Launchstruct Launch {
address vault;
address curve;
address stock;
address creator;
bool pairedWithStock;
uint64 launchedAt;
}
cast call $FACTORY "launches(address)(address,address,address,address,bool,uint64)" <token> --rpc-url $RPC
allTokens(uint256 i) → address, tokenCount() → uint256, tokensPaged(offset, limit) → address[]tokensPaged returns newest first. The site reads tokensPaged(0, 60) for the home page.
cast call $FACTORY "tokenCount()(uint256)" --rpc-url $RPC
cast call $FACTORY "tokensPaged(uint256,uint256)(address[])" 0 20 --rpc-url $RPC
collectAppFees(address[] tokens, address to) (owner only)Transfers the factory's full balance of each listed token to to. Emits AppFeesCollected(token, to, amount) per non-zero transfer. Skips zero balances. See Deploying.
owner(), pendingOwner(), transferOwnership(address), acceptOwnership(), renounceOwnership(). Standard OpenZeppelin Ownable2Step: the new owner must accept.
ReentrancyGuard. No owner. Deployed by IndexFactory.launch.
LAUNCHER, PONS_FACTORY, ESCROW, HOOK, V3_FACTORY, ROUTER, WETH, STOCK, STOCK_IS_PAIR, STOCK_POOL_FEE, TWAP_WINDOW, MAX_TICK_DEVIATION, REWARD_DURATION, APP_FEE_BPS. All public getters.
| Getter | Meaning |
|---|---|
memeToken() | The launched token. Set once by bind. |
curve() | Its Pons bonding curve. |
poolId() | Uniswap v4 pool id, cached on the first post-graduation harvest. Zero before. |
totalStaked() | Memecoin staked, total. |
stakedOf(account) | Memecoin staked by account. |
rewardPerTokenStored() | Accumulator, scaled by 1e18. |
rewardRate() | Stock per second, scaled by 1e18. |
periodFinish() | Unix time the current stream ends. |
lastUpdateTime() | Last time the accumulator was advanced. |
rewardResidual() | Stock that streamed while nothing was staked. Folded into the next stream. |
rewardPerTokenPaid(account) | Accumulator value at the account's last settlement. |
rewards(account) | Stock settled to the account and not claimed. |
totalStockAcquired() | Cumulative stock from harvests, app fee included. |
totalAppFees() | Cumulative stock sent to the launcher. |
totalStockClaimed() | Cumulative stock claimed by stakers. |
harvestCount() | Successful harvests. |
lastHarvestAt() | Timestamp of the last one. |
earned(address account) → uint256. Stock the account could claim now.
rewardPerToken() → uint256. Accumulator brought up to the current block.
lastTimeRewardApplicable() → uint256. min(block.timestamp, periodFinish).
remainingRewards() → uint256. Stock still to stream from the current period, plus the residual.
stockBalance() → uint256. STOCK.balanceOf(vault).
pendingFees() → (escrowEth, escrowStock, unsweptQuote, unsweptTax). Escrow balances the vault can claim, plus fees sitting on the curve (phase 0) or the hook (phase 2) that have not been swept. The unswept figures are pre-split totals.
cast call <vault> "pendingFees()(uint256,uint256,uint256,uint256)" --rpc-url $RPC
cast call <vault> "earned(address)(uint256)" <you> --rpc-url $RPC
cast call <vault> "remainingRewards()(uint256)" --rpc-url $RPC
stake(uint256 amount). Pulls amount of the memecoin with transferFrom; credits what arrived. Reverts NotBound, ZeroAmount. Emits Staked(account, received).
unstake(uint256 amount). Returns amount to the caller. Reverts ZeroAmount, InsufficientStake. Emits Unstaked(account, amount).
claim() → uint256 paid. Sends accrued stock to the caller, capped at what the vault holds; any shortfall stays credited. Reverts NothingToClaim. Emits RewardPaid(account, stock).
exit() → (unstaked, paid). unstake(stakedOf[caller]) then claim() if anything is earned. Skips either half when it would be zero.
# approve, then stake 1,000,000 tokens
cast send <token> "approve(address,uint256)" <vault> 1000000000000000000000000 --rpc-url $RPC --account me
cast send <vault> "stake(uint256)" 1000000000000000000000000 --rpc-url $RPC --account me
cast send <vault> "claim()" --rpc-url $RPC --account me
cast send <vault> "exit()" --rpc-url $RPC --account me
harvest() → uint256 stockGained. Permissionless. Sweeps (best effort), claims both escrow ledgers, swaps ETH to stock on an ETH-priced vault under the TWAP guard, sends the app fee, opens a stream. Reverts NotBound, NothingToHarvest, OracleTooShallow(pool), PriceOutOfBand(pool, spotTick, twapTick), PoolMissing. Emits Harvested(caller, ethClaimed, stockClaimed, stockGained, appFee) and RewardAdded(stock, periodFinish).
warmOracle(uint16 cardinality). Calls increaseObservationCardinalityNext(cardinality) on the WETH/stock pool. No-op on a stock-priced vault. The site passes 64.
cast send <vault> "harvest()" --rpc-url $RPC --account me
cast send <vault> "warmOracle(uint16)" 64 --rpc-url $RPC --account me
bind(address token, address curve)Called once by the factory during launch. Reverts NotLauncher, AlreadyBound, ZeroAddress. Not callable by anyone else.
receive()Accepts ETH. The escrow pays native claims this way. Any other ETH is swapped on the next harvest of an ETH-priced vault; a stock-priced vault leaves it alone.
| Contract | Event | When |
|---|---|---|
| IndexFactory | IndexLaunched(token, vault, stock, curve, creator, pairedWithStock, devBuyQuote, devTokensOut, name, symbol) | Every launch |
| IndexFactory | AppFeesCollected(token, to, amount) | Owner sweeps a token's app fees |
| IndexVault | Bound(memeToken, curve) | Once, at launch |
| IndexVault | Harvested(caller, ethClaimed, stockClaimed, stockGained, appFee) | Each successful harvest |
| IndexVault | RewardAdded(stock, periodFinish) | Each harvest that opened a stream |
| IndexVault | Staked(account, amount) | |
| IndexVault | Unstaked(account, amount) | |
| IndexVault | RewardPaid(account, stock) | Each claim |
Vault addresses come from IndexLaunched; subscribe to that on the factory, then to the vault events per token.
apps/web/lib/launches.ts has getLaunches(limit) and getTokenDetail(token), which batch the reads above through Multicall3. apps/web/lib/chain.ts exports the addresses and Pons constants. Both import from @cf/abi. Regenerate the ABIs after any contract change with bun run abi from the repo root.