Rust library usage
The engine is a plain Rust crate. If you are building a wallet, a signing tool, a CI gate, or an exchange's review pipeline, you can call build_report directly and render the resulting LegibilityReport inside your own UI — no subprocess, no CLI plumbing.
Add the dependency
The crate is not yet published on crates.io (v0.1.0 is pre-deployment). For now, use a git dependency:
Cargo.toml[dependencies] crif = { git = "https://github.com/Nulltx-xyz/crif", branch = "main" } solana-sdk = "2.0" tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
Offline report (no RPC)
The offline path is a synchronous function. Given any VersionedTransaction reference, it returns a fully-populated LegibilityReportby decoding and classifying the transaction's static structure alone.
offline usageuse crif::{ report::build_report_offline, types::{LegibilityReport, RiskLevel}, }; use solana_sdk::transaction::VersionedTransaction; fn review_before_sign(tx: &VersionedTransaction) -> bool { let report: LegibilityReport = build_report_offline(tx); match report.overall_risk { RiskLevel::Low | RiskLevel::Medium => { // OK to sign immediately. true } RiskLevel::High => { // Require explicit user confirmation. display_warning(&report); user_confirms(&report) } RiskLevel::Critical => { // Hard stop. Do not sign. display_critical(&report); false } } }
Full report (with RPC simulation)
The full path is async and expects a Tokio runtime. It takes an EngineConfig specifying the RPC endpoint and commitment, then walks the whole pipeline.
full usage (async)use crif::{ engine::EngineConfig, report::build_report, types::{LegibilityReport, RiskLevel}, }; use solana_sdk::transaction::VersionedTransaction; #[tokio::main] async fn main() -> anyhow::Result<()> { let cfg = EngineConfig::devnet(); // Or: EngineConfig::mainnet_beta() // Or: EngineConfig { rpc_url: "https://...helius-rpc.com/?...".into(), // commitment: CommitmentConfig::confirmed(), // replace_blockhash: true } let tx: VersionedTransaction = /* your tx */; let report: LegibilityReport = build_report(&cfg, &tx).await?; // Render, audit, gate — whatever your flow needs. println!("{}", serde_json::to_string_pretty(&report)?); Ok(()) }
Inspect specific fields
The LegibilityReport type derives serde::Serialize and serde::Deserialize, so you can destructure it freely or round-trip it through JSON.
inspectionif report.uses_durable_nonce { // This transaction will not expire — treat the signature // as a long-lived standing order. } for ix in &report.instructions { println!("{}.{} ({:?})", ix.program_name, ix.instruction_name, ix.risk); for reason in &ix.risk_reasons { println!(" reason: {}", reason); } } for diff in &report.account_diffs { if diff.owner_before != diff.owner_after { // Owner change: the classifier will have escalated // overall_risk to Critical already. } }
CI gate pattern
The most common integration outside a wallet is a CI-time gate that refuses to merge pull requests proposing governance transactions that would trip the Drift pattern. A minimal version:
gate.rsuse crif::{ report::build_report_offline, types::RiskLevel, }; use std::process::ExitCode; fn main() -> ExitCode { let Some(tx_b64) = std::env::args().nth(1) else { eprintln!("usage: gate <BASE64_TX>"); return ExitCode::from(2); }; let raw = base64::engine::general_purpose::STANDARD .decode(tx_b64.as_bytes()) .expect("base64 decode"); let tx = bincode::deserialize(&raw).expect("bincode decode"); let report = build_report_offline(&tx); match report.overall_risk { RiskLevel::Critical => { eprintln!("BLOCKED: {:?}", report.overall_risk); for line in &report.human_summary { eprintln!(" {}", line); } ExitCode::from(1) } _ => ExitCode::from(0), } }