Inertia Protocol
A Solana Anchor program that rescues stalled transactions: when a swap fails to
land within 2 slots (~800ms), a public keeper gate opens and independent
keeper bots race to get it included via Jito private bundles, earning a bounty
from a dynamic gas buffer the user posted at submission time. If no keeper
acts within 150 slots, the user reclaims the full buffer via self_rescue.
Status: early development, live on Solana devnet, unaudited, not yet on
mainnet. A continuous keeper has been running unattended against real
liquidity -- see the proof below. Full, current risk list:
docs/RISK_REGISTER.md.
Live proof
- Program:
8ST3LRU5gv8ijZehvXdwRzc6VnvqbVozCCdFzEzqhqbW - Real, genuine rescues against three independent, externally-built DEXes, not just this repo's own test program -- Orca Whirlpools, Raydium CPMM, and Meteora DLMM -- three structurally different liquidity models, each integration found a different real bug. Full story:
docs/ENGINEERING_LOG.md. - Proven under real concurrent competition, not just single-keeper demos: two independently-keyed keeper bots racing the same live pool, every race resolving to exactly one clean winner -- example. What this does and doesn't prove:
docs/RISK_REGISTER.md. - Full lifecycle proof against
mock-dex(this repo's own test program): rescue, self-rescue, permissionless cleanup.
Quick start
# SDK -- build swaps against a real DEX, or wrap all five instructions
cd packages/sdk && npm install && npm run build
# Reference keeper -- watches devnet, only acts once genuinely profitable.
# The env vars below are real and point at devnet by default -- npm start
# alone connects to a local validator that doesn't exist for you yet.
cd packages/keeper && npm install && npm run build
export INERTIA_KEEPER_RPC_URL="https://api.devnet.solana.com"
export INERTIA_KEEPER_KEYPAIR="/path/to/keypair.json" # solana-keygen new, funded via devnet faucet
export INERTIA_KEEPER_ORCA_WHIRLPOOL="122n8Kvj9htD1AkY8JWJBMngzA8rWkWDPa26vPpuiU7z" # the real, live pool this repo's own proofs use
npm start
This alone will sit quietly until some escrow targeting that pool actually
stalls -- which won't happen on its own. See
docs/RUNNING_A_KEEPER.md for the real,
step-by-step walkthrough, including how to generate real work for your
keeper to find and rescue.
See packages/sdk/README.md and
packages/keeper/README.md for real usage
examples, or CONTRIBUTING.md to build and fuzz the
on-chain program itself.
How it works
flowchart TD
A["initialize_escrow<br/>user deposits gas buffer, delegates input_amount,<br/>escrow PDA created, status: Pending"] --> B{"Escrow is Pending<br/>anyone may act, behavior branches on elapsed slots"}
B -->|"elapsed ≤ TTL_SLOTS (2 slots, ~800ms)<br/>execute_swap, ordinary attempt"| C["CPI swap via delegated authority"]
B -->|"elapsed > TTL_SLOTS<br/>execute_swap, rescue attempt<br/>requires a Jito tip, amount decays<br/>from the reward itself down to a floor"| D["CPI swap via delegated authority"]
C --> E{"output ≥ expected_output_amount?"}
D --> E
E -->|"no"| F["reverts, escrow still Pending"]
E -->|"yes, ordinary path"| G["100% of buffer refunded to user<br/>status: Executed, escrow closed"]
E -->|"yes, rescue path"| H["buffer split 90 / 5 / 5<br/>caller (keeper) / partner / treasury<br/>status: Executed, escrow closed"]
B -->|"elapsed > SELF_RESCUE_SLOTS (150 slots)<br/>self_rescue, user_wallet only"| I["token delegation revoked<br/>full buffer + rent returned to user<br/>status: Rescued, escrow closed"]
B -->|"elapsed > CLEANUP_SLOTS (300 slots)<br/>cleanup_expired_escrow, anyone"| J["10% buffer bounty to caller<br/>90% buffer + rent to user<br/>status: Expired, escrow closed"]
Every threshold is a slot count, not a millisecond value, checked against
Clock::get()?.slot -- correct regardless of Solana's actual slot time.
execute_swap is the only instruction that performs the underlying swap;
self_rescue and cleanup_expired_escrow are pure fallbacks that never
touch the swap program. The rescue-path tip is anti-snipe by design: it
starts equal to the keeper's own reward right when the TTL elapses, making
pure profit-seeking sniping break-even-or-negative at the earliest
possible slot, then decays back to a normal floor. Full reasoning and the
known residual risk: docs/RISK_REGISTER.md.
Full documentation
docs/INSTRUCTIONS.md-- every instruction's accounts, params, state transitions, and errorsdocs/INTEGRATION_GUIDE.md-- how to actually integrate, platform-side or keeper/DEX-integration-sidedocs/RUNNING_A_KEEPER.md-- real, step-by-step walkthrough to get a keeper running against devnet and watch it actually rescue somethingdocs/ECONOMIC_DESIGN.md-- the actual buffer/split/anti-snipe-tip formulas, with real numbers worked backward from a live devnet rundocs/WORKED_EXAMPLES.md-- real, running code for the full lifecycle, self-rescue, and permissionless cleanupdocs/ENGINEERING_LOG.md-- what was actually built and fixed, and whydocs/RISK_REGISTER.md-- every known open risk, kept currentdocs/ROADMAP.md-- where this could go next; vision only, nothing here is built- Rendered together at inertia-protocol.odomushi-core.workers.dev/docs -- or run it locally with
npm run dev --prefix site
Layout
programs/inertia-protocol/: the on-chain programpackages/sdk/: TypeScript client SDK, including three real, reusable DEX swap-builder clients (Orca, Raydium CPMM, Meteora DLMM) any integrating platform can use directlypackages/keeper/: reference open-source keeper bot, consuming those same SDK exports rather than special-casing themtrident-tests/: Trident fuzz tests for all three fund-moving instructions -- 100k-iteration campaign, ~663,600 total instruction invocations, zero assertion panicssite/: public-facing landing page and/docs(Next.js)
See CONTRIBUTING.md for how to actually build, test, and fuzz this locally.