Documentation
Anvil compiles Anchor programs to Pinocchio or Native Rust and proves the port is deploy-safe with a byte-equal differential gate. The CLI is the primary, fully-local interface — install it with npm install -g anvil-sol.
Getting started
The published CLI runs on Node ≥ 20.19 (or ≥ 22.12) — no Bun required.
# install
npm install -g anvil-sol
# 1 — migrate
anvil compile ./my-anchor-program --target pinocchio -o ./out
# 2 — prove it's byte-equal vs Anchor (needs cargo-build-sbf + anchor on PATH)
anvil verify ./my-anchor-programAnvil is safe-by-default: compile refuses to declare success when the validator finds errors, the emit contains TODO(manual) markers, or cargo check rejects the output. --permissive and --no-cargo-check are the explicit opt-outs.
The byte-equal gate
Cargo green is necessary but not sufficient. anvil verify builds your Anchor source and the emitted Pinocchio into separate .so files, runs the same instruction sequence against both inside LiteSVM, and asserts every account's data, lamports, and owner are byte-identical at the end.
$ anvil verify ./my-anchor-program building both .so · replaying scenario in litesvm … ✓ BYTE-EQUAL — all compared accounts match data · lamports · owner identical; negative probes revert identically
The gate also fires negative probes — unauthorized-caller and missing-signer — that must revert identically on both binaries, so access control is verified, not just the happy path. Event payloads (emit!), set_return_data, and msg! text are opt-in comparison surfaces (--compare-events / --compare-return-data / --compare-msg-logs).
Drive the gate with your own scenarios and fuzz the arguments over their full range:
anvil differential ./my-anchor-program --scenario scenario.json --fuzz 100The scenario JSON format is documented in differential-testing.md.
CLI reference
anvil compile <input> --target <pinocchio|native> [-o dir]Transpile to a cargo-buildable project.anvil verify <input> [--target t]One-shot byte-equal proof with negative probes.anvil differential <input> [--scenario s.json] [--fuzz N]Drive the gate with your own scenario.anvil parse <input> [--json]Anchor → Solana IR.anvil validate <input> --target t [--json]Structural checks on the emit.anvil advise <input>Pinocchio vs Native recommendation.anvil refine <input> --target tAI-patch validator errors (your ANTHROPIC_API_KEY).anvil lint <input> --target t [--markdown]Deploy-readiness report.anvil bench <input> [--markdown]Per-instruction CU heuristic.anvil snapshot <input> --save | --checkLock / diff emit shape.anvil diff <before> <after> [--markdown]Compare two emits.Targets & coverage
Pinocchio is the production target (zero-copy, by Anza). Native (solana_program) is the readable reference target.
What's green today
Parser at 100% on 27 real-world programs. SPL Token, Token-2022, ATA, Memo, and System CPIs all emit real calls. The full 12-slot Metaplex Token Metadata catalog and MPL Core catalog emit real CPIs — no stubs. Pyth oracle reads (legacy and modern PriceUpdateV2 paths) transpile to hand-rolled byte deserialization with magic-header and feed-id cross-checks. Account constraints — init, init_if_needed, mut, has_one, close, seeds, bump, realloc — are all honored, as are #[derive(InitSpace)] and #[max_len].
The full support matrix and known gaps live in feature-matrix.md.
Security audit
anvil audit <input> is an optional companion that scans your Anchor source and the transpiled output side by side, then reports the parity between them: weaknesses carried from source, findings with their coverage in the emitted code, and — the tripwire — any finding that exists only on the output, meaning the transformation may have dropped a guarantee.
That analysis caught a real missing owner/discriminator check in the Pyth path (fixed in 0.8.1). Anvil works fully without it.
Scope. The audit is experimental and strongest on Anchor and Pinocchio code. Native scanning is noisier — runtime-ownership and CPI backstops are invisible to static analysis — so treat native findings as leads, not verdicts. See the audit trust model.
Public API
The public API is a small Bun + Express service. Every cargo invocation runs inside firejail / bwrap / unshare with prlimit caps and a stripped env — no secrets reach user code. Per-IP daily AI spend cap, build-sbf concurrency cap, and per-minute rate limit apply.
/parse /emit /lint /build /build/auto-fix
/build/differential /build/differential/auto-scenario
/ai/refine /ai/diagnose-differential
/evidence /demo /health /metrics/health returns the release SHA, sandbox kind, prompt version, and toolchain availability; /metrics returns refine cache hit-rate, accept-rate, build success/failure, and p50/p95/p99 build latency. Threat model: SECURITY.md.
Architecture
One typed IR feeds every consumer — no pass duplicates parsing.
Anchor source
│ tree-sitter
▼
Solana IR (Zod, 100+ body kinds)
│
├─► Pinocchio emit ─┐
├─► Native emit ────┤
│ ▼
│ Validator + lint + bench + diff
▼
Differential harness (LiteSVM byte-equal: data + lamports + owner)The same IR feeds the emitters, the lint / bench / snapshot / diff commands, the compare-targets view, and the AI refine validator. Full write-up: architecture.md.
Deep dives
Longer reference documents live in the repository: