MEV / Jito settlement policy
Parent: backlog/P0_pre_audit_hardening.md item 6.
Threat: task settlement txs (release, resolve_dispute, close_bidding) contain value-moving side effects public mempool-observers can frontrun or sandwich — e.g., sandwich the Jupiter swap inside treasury_standard::stream_withdraw, or frontrun close_bidding with a synthetic commit/reveal pair.
Pure on-chain defenses are limited (oracle slippage caps already land in treasury_standard). What's left is submission path: send settlement txs through Jito block-engine as bundles, with tip-based priority, so the mempool never sees them.
Scope
- Not a program change. No
.rsdiffs here. - Lives in
services/indexersettlement worker +packages/sdkwallet adapters used byapps/portal. - OtterSec scope: confirm on-chain code is bundle-compositional (no assumptions about tx ordering within a bundle that require trust in leader).
Affected ix — always-bundle
| ix | who submits | bundle with |
|---|---|---|
task_market::release |
crank worker | fee_collector::collect_fee in same bundle |
task_market::close_bidding |
crank worker | refund all losing bond-claims in same bundle |
treasury_standard::swap_via_jupiter |
operator or crank | jupiter route instruction + slippage check |
dispute_arbitration::resolve |
arbiter | any slashing + fee routing |
task_market::submit_result |
agent operator | proof_verifier verify + agent_registry::update_reputation |
Affected ix — bundle optional (cheap path acceptable)
task_market::create_task,fund_task,commit_bid,reveal_bid: not value-extractive to outside parties pre-settlement; plain RPC fine. Agents may opt into Jito for their own protection against orderbook scrapers but not required.
Worker architecture
services/indexer/settlement-worker/:
- tx_builder.rs (builds the atomic instruction bundle)
- jito_client.rs (submits to block-engine; fallback to plain RPC on bundle rejection)
- tip_oracle.rs (pulls Jito tip floor; adds 20% headroom; caps absolute lamports/tip)
- nonce_accounts.rs (durable nonce per worker to survive leader rotation)
Bundles submitted via Jito block-engine REST sendBundle — up to 5 txs, all-or-nothing.
Tip policy
- Target: 50th percentile of observed tips for recent slots × 1.2 (headroom).
- Floor: 1000 lamports (avoid zero-tip rejections).
- Cap: 1% of the task payment amount (bound settlement cost).
- Tip account: Jito's rotating tip PDAs — we don't hold one.
- Log every submission:
{bundle_id, tip_lamports, target_slot, txs: [...]}to indexer.
Fallback
- Bundle rejected (e.g., block-engine 429 / slot miss): retry same bundle twice.
- After 2 failures: fall back to plain RPC submission via Helius, warn-logged with reason. Fallback only permitted for non-swap ix (swap must stay bundled or abort, to avoid sandwich).
- Permanent failure: emit
SettlementStuckmetric + alert. Human review.
Durable nonce
Each settlement worker holds a DurableNonce account so bundles survive slot leader rotation. Recycle per hour or per 100 txs, whichever first.
Composition invariants (on-chain code must honor)
The one on-chain rule: every settlement ix must be composable with siblings in a bundle. Concrete:
- No reliance on
Clock.slotfor ordering within a bundle (we might be one of several bundles in the block). submit_result+update_reputationCPI must both succeed in one tx OR neither — already the case since both are same-tx CPI.- No "pull current leader" ix assumptions — bundles hit any leader.
- Idempotent-on-failure where possible: if
releasefails mid-bundle, escrow still withdrawable viarefundpath. Audit confirms.
Auditor diffs to look at: any place that reads slot/leader and branches — there should be none in settlement flows.
IACP signal
Workers subscribe to IACP bus topics:
task.verified→ schedule release bundle.task.disputed→ pause release until dispute resolution event.bid.reveal_ended→ schedule close_bidding bundle.
Subscription-driven ensures no polling load on RPC.
Verify
- Staging: run worker against devnet, confirm bundle submission returns
bundleId, confirm landed viagetInflightBundleStatus. - Unit tests for
tip_oraclefloor/cap/headroom math. - Integration: a sandwich attempt on devnet swap (send large commit-range order first) → post-bundle price within slippage bound, attacker does not profit.
Open questions
- Jito restaking / regionality: pick region closest to Helius RPC. Default: NY. Tune post-M1.
- If Jito downtime is multi-hour, do we halt settlement or degrade to plain RPC? Proposed policy: halt value-movement ix (release, swap); continue metadata ix (close_bidding with no settlement, commit/reveal). Governance flag
settlement_require_bundle: bool. - When SIMD-0228 (scheduled tx) lands, revisit: may let us embed timing constraints directly rather than via bundle.