Risk Register
Real, known limitations and open risks, kept here rather than left implicit or buried in code comments. Every item below was found while actually building and running this protocol, not hypothesized in the abstract. Status reflects where things stand as of this writing (devnet-only, pre-audit, pre-mainnet).
Custody and governance
Upgrade authority is a single EOA keypair, not a multisig
The program's upgrade authority (DGGdEdA4ug9JnHLYorvSJSAfNt5zw9CojYGHeR4sBfKu,
confirmed via solana program show) is currently one personal keypair held by
the founder, not a multisig. This is a single point of failure: loss or
compromise of that one key means loss or compromise of upgrade control over
a program that holds user funds in flight.
Status: open. Must move to a multisig (e.g. Squads) with a defined
signer threshold before mainnet deployment or before any non-trivial value
flows through the program.
Treasury is a devnet-only placeholder keypair
The 5% treasury share of a rescue payout currently goes to a throwaway devnet address, not a secured production account. Status: open. Must be replaced with a properly secured (likely multisig) treasury address before mainnet.
Security review
No external audit has been performed
What exists today: an internal adversarial security review of the shipped
contract, plus a 100,000-iteration Trident fuzz campaign (~663,600 total
instruction invocations) across all three fund-moving instructions
(self_rescue, cleanup_expired_escrow, execute_swap — covering the
ordinary path, the anti-snipe decay curve at randomized points, the slippage
floor, and the account-presence checks from the CPI account-ordering
redesign), with zero assertion panics. That is real coverage, but it is not a
substitute for a third-party audit.
Status: open. Recommended before mainnet or before the program handles
non-trivial value.
Protocol design — known residual risk
Anti-snipe tip decay removes the profit motive for sniping, not the ability to act
Quoting the design note in execute_swap.rs directly: the rescue-path tip
requirement starts equal to the keeper's own reward at the earliest
rescue-eligible slot (making profit-seeking sniping mathematically
break-even-or-negative exactly when genuine-rescue evidence is weakest), then
decays linearly back to the normal floor over TIP_DECAY_SLOTS (~6 seconds).
This is a real fix for the original flat-tip design, where sniping was free
to repeat across every escrow. It is not a complete fix: a griefer willing to
eat a guaranteed loss (pay a tip ≥ the reward, gain nothing back) can still
trigger a premature rescue and redirect a specific user's buffer away from
them, with zero upside for the attacker. The attack is now bounded by cost —
it scales linearly with money spent per victim — but not eliminated.
Status: known, documented in source, accepted residual risk pre-audit.
Revisit before mainnet.
Keeper race safety is proven; keeper race fairness is not
A live devnet run with two independent keeper identities racing the same
pending escrows (August 2026) confirmed the race-resolution mechanism is
safe: every real concurrent race produced exactly one clean RESCUED
outcome and correct LOST-RACE detection on the losing side — no
double-spends, no stuck funds, no wasted on-chain failures. It did not
demonstrate that races are fair under realistic conditions. Both test
keepers ran identical fixed-interval polling loops from the same operator;
the skewed win distribution observed is more plausibly explained by a
deterministic clock-phase relationship between two identical loops than by
genuine competitive parity, though the winner did flip at least once,
showing the outcome isn't fully locked to one side either.
Status: open. Real-world keeper diversity (independent operators,
varied infra, varied latency, varied polling strategy) has not been tested.
DEX integration
Raydium's classic (pre-Anchor) AMM cannot be integrated as-is
Confirmed by reading Raydium's real instruction-encoding source: the classic
AMM program predates the Anchor 8-byte discriminator convention and encodes
its swap instruction as a single native tag byte followed directly by raw
amount fields — there is no stable 8-byte prefix to check at all, since
EscrowState.expected_discriminator is a fixed [u8; 8] and those bytes
would vary with the swap amount on that program. Orca Whirlpools, Raydium's
newer CPMM, and Meteora's DLMM all use the Anchor convention and all three
integrate correctly today.
Status: known limitation, explicit non-goal for the current version.
Supporting pre-Anchor programs would need a configurable discriminator
length, which has not been built.
Third-party DEX SDKs can silently assume authority == token owner
Meteora's DLMM SDK derives token accounts from the swap authority, assuming
authority and token owner are always the same party — which, for Inertia's
delegated-authority pattern, silently pointed a built instruction at an
account the escrow doesn't actually own. This was caught, not missed: the
generalized CPI account-ordering redesign's own presence checks rejected the
malformed transaction outright with MissingRequiredSwapAccount rather than
letting a wrong transaction through.
Status: caught and handled correctly for Meteora. This is a generic
class of risk — every future DEX integration needs to be checked for this
assumption independently; it cannot be assumed safe just because prior
integrations were fine.
Integrator footguns
Concurrent escrows sharing one input token account silently strand earlier ones
SPL Token's approve replaces rather than adds to an existing delegation.
If a single input token account is reused across multiple overlapping
escrows, only the most recent delegation survives — earlier escrows lose
their spending authority silently, with no explicit error at the time it
happens. This is standard SPL Token behavior, not an Inertia bug, but it is
a real footgun for integrators. The reference tooling in this repo
(packages/keeper/devnet-orca-live.mjs) works around it by creating a
fresh, non-associated token account per escrow.
Status: known behavior, must be documented clearly for integrators (not
yet enforced or checked by the contract, which has no way to detect an
approval silently overwritten by an unrelated later instruction).
Public RPC rate limits are a real constraint for keeper operators
Sustained keeper polling against the free public devnet RPC produces
frequent 429 Too Many Requests responses under realistic continuous
operation — observed directly and repeatedly in this repo's own live devnet
runs. On mainnet, a keeper relying on a free or shared RPC endpoint may see
degraded responsiveness exactly when responsiveness matters most (racing to
rescue).
Status: known, external infra constraint. Not something the protocol
itself can fix; should be called out explicitly to prospective keeper
operators (use a paid RPC provider).
Deployment status
Not yet on mainnet
Everything above concerns a devnet deployment. There is no production deployment, no production monitoring or alerting, and no mainnet rollout plan executed yet. This is the umbrella status the rest of this document sits under, not a separate finding.