Engineering Log
What actually changed under the hood and why, and the real bugs each real
DEX integration turned up. This is the "what was fixed" record --
docs/INTEGRATION_GUIDE.md is the how-to for someone integrating today;
this is the story of how that pattern was actually found.
Generic CPI account ordering
execute_swap originally hardcoded its CPI account list into a fixed
4-item prefix (input token account, destination, escrow, token program)
followed by whatever extra accounts the caller supplied. That only ever
worked for a program built to match that exact order -- which mock-dex
was, since this repo wrote it. Solana matches CPI accounts positionally,
not by name, so a real, independently-built program like Orca Whirlpools
(whose own swap instruction expects
[token_program, token_authority, whirlpool, token_owner_account_a, ...],
a completely different order) could not be called at all under the old
design.
execute_swap now takes the entire CPI account list from the caller, in
whatever order the target program actually needs. The same security
guarantees the old fixed prefix gave for free are preserved by explicit
checks: the three security-critical accounts (the escrow's real input
token account, its real expected destination, and the real SPL Token
program) must each appear somewhere in the supplied list, and the escrow's
own entry has its signer flag forced to true wherever it appears, since
only invoke_signed's PDA seeds can actually grant that. Verified against
the existing 8-test integration suite (still 8/8) before being proven
against Orca on devnet.
This does not make execute_swap universally compatible, and that limit
was found for real, not assumed: expected_discriminator is a fixed 8
bytes, matching the now-dominant Anchor instruction-discriminator
convention. Orca Whirlpools, Raydium's newer CPMM program, and Meteora's
DLMM all use it, and all three work. Raydium's classic AMM program
predates Anchor and encodes its swap instruction as a single native tag
byte followed directly by the raw amount fields -- there is no stable
8-byte prefix to check at all, since those "discriminator" bytes would
actually vary with the swap amount. Confirmed by reading its real
instruction-encoding source, not assumed from docs; integrating with a
pre-Anchor program would need a further change (a configurable
discriminator length), not attempted here.
The Meteora integration also caught something the other two didn't: its
SDK derives token accounts from the swap authority (assuming authority and
token owner are always the same party), which silently pointed the built
instruction at an account the escrow doesn't own. The generalized
redesign's own new presence checks rejected it outright with
MissingRequiredSwapAccount rather than letting a wrong transaction
through -- a real bug the checks caught unprompted, not a hypothetical
they were designed against.
Live dual-keeper run
A later, separate proof: two independently-keyed keeper bots run
continuously against the same live Orca pool, racing every escrow an
unattended activity generator creates. Every real race resolved to exactly
one clean winner with correct loss-detection on the losing side -- zero
double-spends, zero stuck funds. Over a larger sample the win distribution
converged toward even rather than staying locked to one side, real
evidence against the outcome being pure clock-phase determinism between
two identically-configured bots. See
docs/RISK_REGISTER.md for exactly what this does
and doesn't prove -- it demonstrates race safety, and gives real (not
conclusive) evidence toward race fairness; it does not test real-world
keeper diversity (independent operators, varied infra and latency).
Two real operational bugs surfaced running this live, unrelated to the protocol itself: a WSL2 background-process detachment quirk that silently killed both the activity generator and the keeper on first attempt, and the payer wallet running dry after ~2.5 hours of continuous funding (fixed by recycling the keepers' own real earned rescue profit back to it, rather than waiting on a rate-limited devnet faucet).