solana mev forensics agent
docs
overview
hikara watches every slot on solana mainnet, decodes the swap instructions, and reconstructs jito bundles. it classifies the events — sandwich, jit, backrun, liquidation, atomic arb — attributes leader validators and known searchers, and emits a verdict per event with a confidence in [0.0, 0.95].
detector, not oracle. read-only by design. no wallet, no signer, no executor, no trading. anyone forking to build a sniper does so in their own repo.
how it works
a pipeline of small, independent layers. you can swap any layer without touching the others. the contract between layers is the Event dataclass in src/hakiri/core/types.py.
shredstream feed finalized slots
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ ingest-rs │ │ getBlock │
│ jito shreds │ │ getTransac. │
└──────┬───────┘ └──────┬───────┘
│ │
└──────────────┬──────────────────┘
▼
┌──────────────┐
│ decode amm │ raydium · orca · meteora · phoenix
└──────┬───────┘
▼
┌──────────────┐
│ classify │ SAND-01, BACK-01, JIT-01, ARB-01
└──────┬───────┘
▼
┌──────────────┐
│ score │ rule-based, capped at 0.95
└──────┬───────┘
▼
┌──────────────┐
│ ai filter │ optional. only reviews edge cases
└──────┬───────┘
▼
┌──────────────┐
│ output sinks │ stdout · jsonl · webhook
└──────────────┘
supported
| target | status |
|---|---|
| solana mainnet | primary |
| raydium amm v4 | primary |
| raydium clmm | primary |
| orca whirlpools | primary |
| jito bundle stream | primary |
| jito tip account decode | primary |
| meteora dlmm | low coverage |
| phoenix clob | low coverage |
| lifinity v2 | low coverage |
| drift / kamino liquidations | planned |
| pyth oracle-update sandwich | planned |
detection heuristics
each rule has a numbered id used in event.notes so you can trace what fired. new rules ship with both a positive and a negative fixture or they do not get merged.
| id | what it catches | shipped in |
|---|---|---|
SAND-01 | classic sandwich: front + victim + back same pool, opposite directions | v0.1 |
BACK-01 | one-step backrun arb against a user swap | v0.1 |
JIT-01 | just-in-time concentrated liquidity around a victim swap | v0.2 |
ARB-01 | atomic multi-hop arb across 3+ pools in a single tx | v0.2 |
LIQ-01 | drift / kamino / marginfi liquidation w/ priority manipulation | v0.3 |
ORACLE-01 | sandwich-style prep around a pyth / switchboard update | v0.3 |
SAND-01 — classic sandwich
three swaps on the same pool: a front-run by a searcher, a victim swap, a back-run by the same searcher. front and back run in opposite directions.
front.pool == victim.pool == back.poolfront.signer == back.signer != victim.signerfront.token_in == victim.token_inandfront.token_out == victim.token_outback.token_in == victim.token_outandback.token_out == victim.token_infront.tx_index < victim.tx_index < back.tx_index(within the same slot)
scoring: base 0.70. jito-tip transfer raises by +0.10. presence of a victim record raises by +0.05.
BACK-01 — one-step backrun
two consecutive swaps on the same pool by different signers, same direction. the second swap is treated as an arb candidate against the first.
a.pool == b.poola.signer != b.signera.token_in == b.token_inanda.token_out == b.token_outa.tx_index + 1 == b.tx_index
scoring: base 0.50. jito-tip transfer raises by +0.10.
JIT-01 — just-in-time liquidity
orca whirlpools and meteora dlmm. a concentrated position is opened (open_position + increase_liquidity) and closed (decrease_liquidity + close_position) within the same slot, surrounding a victim swap. the searcher captures fees from the victim and rebalances out before the slot ends.
ARB-01 — atomic multi-hop arb
a single transaction touches three or more pools across raydium / orca / meteora / phoenix and ends with a profit denominated in the input mint. typical on stable triangles (SOL → USDC → USDT → SOL).
LIQ-01 — liquidation with priority manipulation
drift, kamino, marginfi, or solend liquidation where the searcher front-runs the liquidate instruction with a price-impact swap that pushes the victim past threshold.
ORACLE-01 — oracle-update sandwich
same shape as SAND-01, but the victim is a pyth or switchboard price-update transaction. the searcher positions before the update lands and exits after.
why each rule has a numbered id
users get a verdict; auditors need a reason. quoting a rule id in a forum post or a postmortem is more useful than re-explaining the heuristic each time.
quickstart
requires python 3.9+ and rust stable.
git clone https://github.com/hakiriagent/hakiri.git hikara cd hikara # install python package + dev deps make install # build the rust ingest crate cd ingest-rs && cargo build --release && cd .. # run an offline demo (zero network) hikara demo investigate # run the live scanner against your rpc cp .env.example .env $EDITOR .env # set HIKARA_RPC_URL (helius / triton) or HIKARA_SHRED_URL (jito) hikara scan
no rpc? hikara demo scan shows the full pipeline against synthetic fixtures.
cli reference
hikara version # version + active config hikara scan # live shred + slot scan hikara scan --once # one slot then exit (smoke test) hikara investigate <sig|slot> # walk the pipeline on a specific target hikara demo scan # canned scan against a synthetic slot hikara demo investigate # full pipeline trace, prints every step hikara demo replay <id> # replay a recorded fixture
example output — hikara demo investigate
─────────────────── step 1. decoded swaps ────────────────────
slot 285000000 idx 0 signer 4Nd1m7t... pool 8sLbNZoA... (raydium) in 8.00 SOL out 1320.40 USDC
slot 285000000 idx 1 signer 9XKpMRkB... pool 8sLbNZoA... (raydium) in 0.50 SOL out 82.10 USDC
slot 285000000 idx 2 signer 4Nd1m7t... pool 8sLbNZoA... (raydium) in 1340.00 USDC out 8.07 SOL
─────────────────── step 2. classifier rules ─────────────────
rules fired: ['SAND-01']
slot verdict: likely
events found: 1
─────────────────── step 3. score per event ─────────────────
sandwich slot 285000000
base[sandwich]=0.70
jito_tip>0:+0.10
bundle.txs>=2:+0.05
victims_present:+0.05
-> verdict=confirmed conf=0.900
─────────────────────── done ────────────────────────────────
architecture
hakiri/
├── src/hakiri/ python package
│ ├── core/ types · classify · score
│ ├── decode/ raydium · orca · meteora + program ids
│ ├── enrich/ leader · searcher · jito tip
│ ├── ingest/ rpc + shredstream + slot stubs (rust later)
│ ├── output/ stdout · jsonl · webhook
│ ├── ai/ optional rule reviewer
│ ├── demo/ offline scripted demos
│ └── cli.py typer entrypoint
├── ingest-rs/ low-level ingest crate (rust)
│ └── src/ {shreds,bundle,slot}.rs
├── tests/ pytest suite
└── docs/ architecture · heuristics · glossary
zones & maintainers
| zone | language | maintainer |
|---|---|---|
| core, scoring, ci | python | @hakiriagent |
| ingest-rs, shredstream | rust | @0xnova |
| classify, heuristics | python | @mikrohash |
| decode, output | python | @luka |
why polyglot
mev forensics on solana is bottlenecked by shred latency. python is fast enough for classification and scoring (a slot is at most a few hundred swaps) but a poor fit for a high-frequency shred-stream loop. rust handles the hot path; python handles the analysis. they meet at the Event boundary.
pipeline layers
ingest
two sources feed the pipeline:
- shredstream (pre-finality). a jito shred-relay subscription. solana has no public mempool — shreds are the earliest signal you can get on a tx before its slot finalizes.
- finalized slots (post-finality). either
getBlock(batched, full slot) orgetTransaction(per-signature, deep instruction trace) over a helius / triton / quicknode rpc.
the live ingest path runs in ingest-rs/. python has stubs at src/hakiri/ingest/{shreds,leader,trace}.py so the rest of the pipeline can be exercised without a node.
decode
instructions and inner-instructions are converted into SwapTx records via program-id dispatch. supported targets:
- raydium amm v4 —
675kPX9MHTjS2zt1qfr1NYHuzeLXfQM9H24wFSUt1Mp8(Swap) - raydium clmm —
CAMMCzo5YL8w4VFF8KVHrK22GGUsp5VTaW7grrKgrWqK(SwapV2) - orca whirlpools —
whirLbMiicVdio4qvUfM5KAg6Ct8VwpYzGff3uctyCc(Swap)
meteora dlmm and phoenix clob are stubs at this stage.
classify
pure functions over SwapTx lists ordered by tx_index. each rule has a numbered id; ids appear in docs/heuristics.md and on the event itself via event.notes.
score
rule-based confidence in [0.0, 0.95]. capped on purpose. the breakdown is returned to the caller so every confidence value is fully traceable.
ai filter
optional. only used when ANTHROPIC_API_KEY is set. the filter reviews edge-case events and returns a minor adjustment, never an upgrade beyond the cap. when disabled, the rule-based score is final.
sinks
three sinks ship in 0.1:
stdout— rich-formatted terminal outputjsonl— append-only file at the configured pathwebhook— POST to an arbitrary url, failures swallowed
all runs every configured sink. add your own by implementing an emit method that takes (event, score).
glossary
domain terms used throughout hikara.
- backrun
- an arbitrage transaction placed immediately after a known price-moving transaction in the same slot.
- block leader
- the validator scheduled for the current slot. unlike ethereum, solana has no proposer-builder separation — the leader builds the block directly. with the jito-solana client, the leader exposes a bundle slot to searchers in exchange for a tip.
- bundle (jito)
- an ordered group of up to 5 transactions submitted via the jito relay. atomic by construction — either all txs land or none. searchers pay the leader a tip via one of the well-known jito tip accounts.
- confidence cap
- hikara scores never exceed
0.95. a detector, not an oracle. - jito relay
- public infrastructure that forwards searcher bundles to jito-solana validators. the relay enforces atomicity, dedupes, and exposes the shredstream feed.
- jito tip
- a sol transfer to one of 8 well-known jito tip accounts. presence and size are the strongest single signal that a transaction is part of a jito bundle.
- jit (just-in-time liquidity)
- opening a concentrated position, capturing fees from one specific incoming swap, and closing the position in the same slot. on solana, mostly seen on orca whirlpools and meteora dlmm.
- priority fee
- per-compute-unit fee paid in addition to the base fee. another lever searchers use for inclusion priority alongside (or instead of) jito tips.
- sandwich
- a front-run + back-run pair surrounding a victim swap on the same pool. the searcher buys before, sells after, captures the spread the victim creates.
- searcher
- a bot operator submitting bundles to the jito relay. distinct from the validator; pays for inclusion via tips.
- shredstream
- jito's low-latency shred feed. partial slot data delivered as it's produced, before finalization. hikara reads it to detect bundles in-flight.
- slot
- solana's notion of a block. lasts ~400ms. a slot can contain hundreds of transactions; ordering inside the slot is set by the leader.
- tx_index
- the position of a transaction within its slot. ordering matters for bundle reconstruction and sandwich detection.
- verdict
- hikara's human-readable label for a scored event:
confirmed(≥0.85),likely(≥0.65),suspected(≥0.40),noise(<0.40).
roadmap
| version | scope | status |
|---|---|---|
| v0.1 | sandwich + backrun rules. raydium amm v4 + orca whirlpools decode. demo + cli. | shipped |
| v0.2 | rust ingest wired via pyo3. jit + atomic arb rules. fixture replay. | now |
| v0.3 | liquidation + oracle rules. meteora dlmm + phoenix clob. drift + kamino support. | planned |
| v0.4 | per-searcher leaderboard. per-leader tip share. weekly digest. | planned |
| v0.5 | jito shredstream integration. low-latency local rpc mode. | planned |
contributing
short version: ship code that passes ci, write a clear pr description, no llm-generated readmes.
new searchers and tip accounts are the easiest path in. open a PR against src/hakiri/enrich/leader.py or src/hakiri/enrich/searcher.py with on-chain evidence in the description.
full guidelines: CONTRIBUTING.md.