How the engine is built
Four modules, one orchestrator, zero global state, zero custody. Everything is a pure function of the input transaction and the RPC response — your keys never enter the engine.
The pipeline
Every invocation goes through the same five-step pipeline. Steps 1–3 are skipped in offline mode — the blue dashed path in the diagram below.
Module layout
crate treecrif/ src/ types.rs LegibilityReport, AccountDiff, RiskLevel, DecodedInstruction, TokenTransfer engine/ mod.rs re-exports simulate.rs RPC fetch + simulateTransaction call, writable-account extraction, pre/post snapshot pairing diff.rs per-account state diff computation decoder/ mod.rs ProgramDecoder trait + decode_all() registry.rs program_id -> Arc<dyn ProgramDecoder> anchor_generic.rs reusable Anchor discriminator matcher system.rs System Program (native) spl_token.rs SPL Token (native, shared with token-2022) token_2022.rs Token-2022 base + extension tags squads.rs Squads v4 (custom, Drift reasoning) jupiter.rs Jupiter v6 (generic anchor) drift_v2.rs Drift v2 (generic anchor) kamino.rs Kamino Lend (generic anchor) marginfi.rs MarginFi v2 (generic anchor) classifier/ mod.rs risk synthesis + drift-pattern detection + human_summary line generation report.rs orchestrator: build_report, build_report_offline, assemble_report main.rs CLI entry point lib.rs crate root tests/ decoder_unit.rs 5 tests (System, SPL Token) squads_unit.rs 6 tests (Squads + drift pattern) protocol_decoders.rs 15 tests (Jupiter, Drift, Kamino, MarginFi, Token-2022) drift_attack_e2e.rs 2 tests (full pipeline + base64 roundtrip) devnet_integration.rs 1 test (ignored, real devnet) examples/ drift_attack.rs synthesizes the Drift 2026 attack tx and prints base64 + offline report
The orchestrator
The entire public API is two functions in src/report.rs:
report.rs (excerpt)/// Full report: runs simulation against live RPC, diffs state, /// decodes, classifies. pub async fn build_report( cfg: &EngineConfig, tx: &VersionedTransaction, ) -> Result<LegibilityReport>; /// Offline report: skips simulation entirely. Runs the decoder /// and classifier against the transaction's static structure only. /// Useful for auditing a tx before it touches any RPC, and for /// programs that may not be deployed on the current cluster. pub fn build_report_offline( tx: &VersionedTransaction, ) -> LegibilityReport;
Both functions converge on a private assemble_report helper that runs decode + classify identically. The only difference is where the state diffs and token transfers come from: the full path extracts them from the simulation outcome, the offline path passes empty vectors.
The decoder registry
A DecoderRegistry is a HashMap<Pubkey, Arc<dyn ProgramDecoder>>. Each protocol decoder implements a three-method trait:
ProgramDecoder traitpub trait ProgramDecoder: Send + Sync { fn program_id(&self) -> Pubkey; fn program_name(&self) -> &'static str; fn decode( &self, ix: &CompiledInstruction, account_keys: &[Pubkey], ) -> Option<DecodedInstruction>; }
Native programs (System, SPL Token, Token-2022) have bespoke decoders that match on the raw instruction tag byte. Anchor programs (Squads, Jupiter, Drift v2, Kamino, MarginFi) go through GenericAnchorDecoder, which takes a static table of (instruction_name, display_name, summary, risk, reasons) tuples and computes Anchor's sha256("global:<name>")[0..8] discriminators at first call, caching them in a OnceLock.
The classifier
The classifier takes decoded instructions, state diffs, token transfers, and a durable-nonce flag, and returns (RiskLevel, Vec<String>) — the overall verdict and the human summary lines. It applies four kinds of rules in order:
- Per-instruction escalation — the overall risk is the maximum of every decoded instruction's risk.
- Durable nonce escalation — if the first instruction is
AdvanceNonceAccount, the risk is bumped to at least HIGH. - Drift pattern detection — if the tx uses a durable nonce AND contains any of
config_transaction_execute,multisig_set_config, orvault_transaction_execute, the risk is forced to CRITICAL with a dedicated Drift 2026 callout. - State diff escalation — large lamport outflows (≥1 SOL) and owner program changes escalate the overall risk.