The perp crank
Without it a deployed perp is a frozen number, and funding never manufactures a counterparty.
cd relayer && npm run crank # continuous
npm run crank -- --once # one pass
npm run crank -- --config config/devnet.yaml
sync_index and settle_funding had no caller outside the test files until
2026-08-24, which meant a deployed perp was a frozen number. init_market
writes an index once and nothing moves it again, so the mark never converged on
the underlying and cumulative_funding stayed at zero.
A perp with no funding is not a thin perp. It is a fixed price people can take positions against, with the one mechanism that manufactures a counterparty retired.
Two rules shape it
settle_funding prices the premium off m.index_price, which is whatever
sync_index last wrote.
Split across two transactions they can straddle a price move, and funding gets charged against an index that is already gone. Bundled, they are guaranteed to see the same number, which is what the premium clamp assumes.
A sync that would write back the index already on chain costs a fee and a slot to achieve exactly nothing, and at one market per pairable that is most of the fleet most of the time.
Pairable accounts are read in one free batch first, and a market is cranked only when the index actually moved or is halfway to unusable.
Measured on devnet on 2026-08-24: 48 markets, 5 synced, 43 costing nothing. Run again immediately, zero transactions. The split moves with the feeds, so it is a sample rather than a constant.
getMultipleAccountsInfo caps out, so the batch is chunked at 100 rather than
discovering the limit at 101.
What the crank cannot fix
Funding has fired. On 2026-08-25 a position on PEAR-EA created a 40.1bps
premium and the next interval charged it: cumulativeFunding moved to
2,400,000,000 and a 166 pear long owed $0.399999, against a prediction computed
beforehand from the program's own arithmetic. 32 of the 48 devnet markets carry
a non-zero cumulative_funding as of that evening.
A market whose index has aged out does not get cranked back to life either. The
crank can only carry a value the registry already has, and index_ok refuses a
stale, frozen or expired index on every path that moves value. That is the safe
direction: better a frozen book than liquidations priced off a number nobody
stands behind.
What the crank does not include
Liquidation. liquidate is permissionless and pays liquidation_fee_bps to
whoever calls it, which is the only reason anybody would run one. The fee is a
cut of what survives unwinding against the virtual reserves rather than a slice
of remaining collateral, and it is exactly zero when the unwind settles below
zero. relayer/src/liquidate.ts is the caller and is dry by default, so running
it by accident cannot liquidate anybody. It has never been run against
devnet, which only became possible on 2026-08-25 when the first real position
was opened. At initial_margin_bps of 10000 a position cannot realistically go
under water, so that gap is survivable on devnet and is a prerequisite for
leverage and for mainnet.
Market.bad_debt is where the consequence would show up. It is not a rounding
counter: a non-zero figure means liquidation was too slow at least once, and it
belongs in front of anyone depositing.
peard