Anchor → Pinocchio, with proof. Paste an Anchor program in, get a cargo-buildable Pinocchio project out — plus a byte-equal gate that runs both inside a real VM and checks
data + lamports + owneragainst the Anchor original, so you know when a port is deploy-safe instead of trusting it. What's verified today is a growing corpus (below) — not a promise that every program transpiles cleanly.
anvilsol.xyz · npm · docs · security · audit trust model
The reason most Anchor programs stay on Anchor isn't that anyone prefers it for production — it's that hand-rewriting 2,000–10,000 lines of Rust to a leaner runtime carries unacceptable correctness risk. Anvil removes that risk:
# 1. Migrate
anvil-sol compile ./my-anchor-program --target pinocchio -o ./my-pinocchio
# 2. Prove it
anvil-sol differential ./my-anchor-program --scenario scenario.json
# ✓ BYTE-EQUAL — all N compared account(s) match.Step 2 is the part nobody else ships. It builds your Anchor source AND the Anvil-emitted Pinocchio version into separate .so files, runs the same instruction sequence against both inside LiteSVM, and asserts every account's data, lamports, AND owner byte-equal at the end. Anything else — wrong CPI account order, missing bump, off-by-one Borsh layout, account left assigned to the wrong program — fails the gate loudly. Event log payloads (emit!) are NOT compared today; the CLI refuses to run on emit!()-using sources unless you pass --ignore-events to acknowledge the gap. See docs/audit-trust-model.md.
Cargo green is necessary but not sufficient. This is the actual correctness signal.
150 byte-equal differential test files lock these emit shapes against the Anchor reference on every commit. Data + lamports + owner all byte-compared in a real VM — not just cargo build green, not just IDL match. The MPL Token Metadata + Pyth Receiver .so are bundled as test fixtures and loaded into LiteSVM via svm.addProgram so CPI shape correctness is also verified end-to-end. First multi-file real-world byte-equal: Helium circuit-breaker (8 instructions, 12 source files) — Anchor and Anvil-Pinocchio produce identical on-chain state under the same scenario. MPL byte-equal coverage 11/12: create_metadata_v3 (with full DataV2 support — creators, collection, uses), create_master_edition_v3, update_metadata_accounts_v2, set_and_verify_collection, freeze_delegated, thaw_delegated, approve_collection_authority, revoke_collection_authority, mint_new_edition_from_master_edition, sign_metadata, verify_collection. Full 12/12 MPL Core catalog: CreateV2, UpdateV2, TransferV1, BurnV1, CreateCollectionV2, AddPluginV1, RemovePluginV1, UpdatePluginV1, ApprovePluginAuthorityV1, RevokePluginAuthorityV1. Top DeFi cohort: marginfi-v2 (91 instructions, 1 error), raydium-clmm (34 instructions, 0 errors), klend (63 instructions, cargo build-sbf GREEN — first top Solana lending protocol fully compilable to Pinocchio). Composite #[derive(Accounts)] flatten shipped end-to-end with BYTE_EQUAL.
Externally-authored programs cloned verbatim from public repos. Anvil's emit produces post-scenario state byte-identical to the Anchor reference under the same scenario:
| Program | Source | Surface |
|---|---|---|
anchor-escrow-2025 |
mikemaccana/anchor-escrow-2025 | PDA + non-ATA token init + token::transfer |
coral-multisig |
coral-xyz/anchor test corpus | m-of-n signer enforcement |
coral-events |
coral-xyz/anchor test corpus | emit!() event log + multi-field borsh payload |
coral-composite |
coral-xyz/anchor test corpus | Composite #[derive(Accounts)] flatten |
coral-realloc |
coral-xyz/anchor test corpus | Vec resize with rent-delta accounting |
coral-overflow-checks |
coral-xyz/anchor test corpus | Overflow enforcement |
coral-duplicate-mutable |
coral-xyz/anchor test corpus | Mutable alias detection |
coral-pda-derivation |
coral-xyz/anchor test corpus | PDA derivation patterns |
coral-init-if-needed |
coral-xyz/anchor test corpus | init_if_needed constraint |
favorites |
solana-developers/program-examples | init_if_needed + String + Vec<String> (max_len) |
account-data |
solana-developers/program-examples | 3× String fields under #[max_len(50)] |
pda-rent-payer |
solana-developers/program-examples | Signer-seeded system_program::create_account |
program-examples counter |
solana-developers/program-examples | Basic PDA init + state mutation |
page-visits |
solana-developers/program-examples | Smallest possible PDA-init (5-byte struct) |
| Fixture | Surface |
|---|---|
counter |
Account init + state mutation |
vault |
PDA-as-vault + signer-seeded system_program::transfer |
has-one |
Runtime constraint enforcement (has_one = X) |
ata-mint |
ATA create + SPL mint_to CPI |
spl-transfer |
token::transfer CPI |
spl-burn |
token::burn CPI |
t22-transfer |
Token-2022 transfer_checked (mint decimals extraction) |
close |
close = receiver rent refund + reap |
set-authority |
Hand-rolled raw SPL set_authority on Pinocchio |
escrow |
PDA init + non-ATA token init (init token::* vault) + canonical vault PDA [b"vault", escrow] (substitution-attack-proof) + token::transfer |
marketplace |
NFT marketplace state shape (admin + fee_bps + treasury) |
event-emit |
emit!() discriminator + borsh payload via sol_log_data |
staking |
Full SPL pool init — canonical stake_vault + reward_vault PDAs, address = pool.reward_mint constraint, snapshotted reward rate per stake |
simple-staking |
SOL-only stake/claim with clock-pinned + emit! + msg/return-data triple parity |
amm |
Constant-product pool with LP mint + protocol-fee accumulator (post-audit shape) |
multisig |
m-of-n signer enforcement, executor-must-be-owner check |
realloc / realloc-grow |
Vec resize with rent-delta accounting |
vesting |
Schedule + cliff + claim math + close-with-empty-vault precondition |
Plus 22 more covering bumps_access, init_if_needed, cpi_custom, cpi_memo, sysvars, return data/err, msg logs, T22 extension family (NonTransferable, ImmutableOwner, DefaultAccountState, InterestBearingMint, TokenMetadata, TransferFee), MPL Token Metadata (create_metadata_v3 + create_master_edition_v3 + update_metadata_accounts_v2 byte-equal under a chained scenario via the staged mpl_token_metadata.so loaded into LiteSVM), and others. bun test api/tests/differential-*.test.ts runs the full set.
Plus 181 deterministic real-world cargo-build regression gates (MUST_PASS) from solana-developers/program-examples and the coral-xyz/anchor test corpus.
Built both as Anchor original and Anvil-emitted Pinocchio, deployed to solana-test-validator, run side-by-side. Best-case across 5 trials per side (controls for find_program_address bump-iteration variance).
| Instruction | Anchor CU | Anvil-Pinocchio CU | Saved |
|---|---|---|---|
counter::initialize(start_value=10) |
6,074 | 3,268 | 46% |
counter::increment(amount=5) |
2,753 | 1,801 | 35% |
vault::initialize |
9,384 | 4,893 | 48% |
vault::deposit(amount=1_000_000_000) |
6,726 | 4,674 | 31% |
vault::withdraw(amount=500_000_000) |
6,758 | 4,716 | 30% |
escrow::create_escrow(seed=42, deposit=250000, receive=500000) |
26,614 | 16,133 | 39% |
For SPL-heavy workloads (transfers, mints, burns), the savings are larger — Helius's hand-written p-token Pinocchio implementations measure 97-98% CU reduction vs SPL-Token-via-Anchor on those primitives, and Anvil's cpi_spl_* emit uses the same pinocchio_token builders. See docs/feature-matrix.md for the full breakdown.
Reproduce: solana-test-validator --reset --quiet & in one terminal, then bun scripts/measure-cu.ts in another.
What we don't claim:
- AI patches now have a default-on differential gate.
/build/auto-fixruns the byte-equal compare after each cargo-green iteration whenever the request body carriesdifferential: { anchorSource, scenario, ... }; patches that compile but diverge at runtime are flagged and fed back to the next refine call. The workbench's auto-fix card shows a green "✓ byte-equal verified" badge when the gate passes vs a yellow "⚠ diverged" badge when it doesn't. Pass?with_differential=0to skip the gate without restructuring the body. When the request omits thedifferentialbody entirely (no Anchor reference available), the workbench's persistent yellow banner reminds you to audit before deploy. - The CU table in the workbench is a heuristic estimator (constant-table per-construct sum). The measurement script above is the source of truth for absolute numbers.
- Quasar emit was deleted from the production path on 2026-05-05 (Blueshift hadn't shipped a stable 1.0). Pinocchio + Native are the supported targets.
Requires Bun ≥ 1.0.0 as the runtime. The CLI is published as anvil-sol and runs under Bun (not Node) — bun add -g anvil-sol or invoke with bunx anvil-sol …. The full repo also runs under Bun for tests + the dev server.
git clone https://github.com/Pratikkale26/Anvil && cd Anvil
bun install && cd cli && bun install && cd ..
# Transpile a bundled demo and cargo-build it. (v0.4+ writes the project
# only when the safe-by-default gate passes; pass --permissive to write
# stub-bearing emit anyway — explore mode only.)
bun cli/anvil.ts compile api/src/demo-programs/counter.rs --target native -o /tmp/counter-native
cd /tmp/counter-native && cargo build
# Or run the differential gate against a bundled scenario:
bun cli/anvil.ts differential api/src/demo-programs/counter.rs \
--scenario examples/differential/counter.json --fuzz 100
# → 100/100 byte-equal on randomized scalar argsOr use the public workbench: paste source at anvilsol.xyz, pick a target, download the bundle.
A handler from counter.rs:
pub fn increment(ctx: Context<Update>, amount: u64) -> Result<()> {
let counter = &mut ctx.accounts.counter;
counter.count = counter.count.checked_add(amount).ok_or(CounterError::Overflow)?;
Ok(())
}What Anvil emits for --target native (abbreviated):
pub fn increment(program_id: &Pubkey, accounts: &[AccountInfo], data: &[u8]) -> ProgramResult {
if accounts.len() < 2 { return Err(ProgramError::NotEnoughAccountKeys); }
let counter = &accounts[0];
let authority = &accounts[1];
if !authority.is_signer { return Err(ProgramError::MissingRequiredSignature); }
if !counter.is_writable { return Err(ProgramError::InvalidAccountData); }
if counter.owner != program_id { return Err(ProgramError::IncorrectProgramId); }
let amount = u64::from_le_bytes(data[..8].try_into()
.map_err(|_| ProgramError::InvalidInstructionData)?);
let (expected, _bump) = Pubkey::find_program_address(
&[b"counter", authority.key.as_ref()], program_id);
if expected != *counter.key { return Err(ProgramError::InvalidSeeds); }
let mut state = CounterAccount::read(&counter.data.borrow())?;
if state.authority != *authority.key {
return Err(ProgramError::InvalidAccountData);
}
state.count = state.count.checked_add(amount).ok_or(CounterError::Overflow)?;
CounterAccount::write(&mut counter.data.borrow_mut(), &state)?;
Ok(())
}Discriminator routing, signer / writable / owner checks, args decoding, PDA derivation, manual Borsh — all generated.
For the equivalent Pinocchio output and side-by-side comparison, see the workbench's "Compare targets" view.
Anchor source → tree-sitter → Solana IR (Zod, 100+ body kinds) → {Pinocchio, Native} emit → Validator
│
└──► Differential harness (LiteSVM byte-equal: data + lamports + owner)
Same IR feeds the emitters, the lint / bench / snapshot / diff CLI commands, the workbench's compare-targets view, and the AI refine validator. No pass duplicates parsing.
For detail: docs/architecture.md.
Pinocchio is the production target. Native is the reference.
Quick read: parser at 100% on 27 real-world programs; SPL Token + Token-2022 + ATA + Memo + System CPIs all green; full 12-slot Metaplex Token Metadata catalog emits real CPIs (no stubs); Pyth oracle reads (both legacy pyth_sdk_solana and modern pyth_solana_receiver_sdk PriceUpdateV2 paths) transpile to hand-rolled byte deserialization with magic-header + feed-id cross-check; account constraints (init, init_if_needed, mut, has_one, close, seeds, bump, realloc) all green; #[derive(InitSpace)] + #[max_len] honored.
Full matrix and known gaps: docs/feature-matrix.md.
anvil-sol compile <input> --target <pinocchio|native> [-o <dir>] [--strict]
anvil-sol differential <input> [--scenario s.json] [--anchor-so path.so]
anvil-sol parse <input> [--json]
anvil-sol validate <input> --target <target> [--json]
anvil-sol lint <input> --target <target> [--markdown]
anvil-sol bench <input> [--markdown]
anvil-sol snapshot <input> --save | --check
anvil-sol diff <before> <after> [--markdown]
--strict on compile refuses to write output when the validator finds errors or the emit contains TODO(manual) markers — gate this before deploy.
differential --scenario runs the byte-equal correctness gate against your own program. See docs/differential-testing.md for the JSON format.
The web playground at anvilsol.xyz:
- Paste / file / folder / GitHub repo ingestion.
- Live emit + CU heuristic per instruction.
- AI refine with revert-on-regression (Sonnet 4.6, $0.50 per-request cap, $2/IP/day cap).
- Verify Build — segmented Check / Build / Deploy:
- Check (
cargo check, ~3s, runs automatically on every emit). - Build (
cargo build, ~10–15s, catches linker + codegen). - Deploy (
cargo build-sbf, ~30s–2min, produces a deployable.so). - SSE-streamed cargo output, cancel-on-disconnect.
- Check (
- Project-bundle download (full Cargo project as
.tar).
/parse /emit /lint /build /build/auto-fix /build/differential /build/differential/auto-scenario /ai/refine /ai/diagnose-differential /evidence /demo /health /metrics. Every cargo invocation runs inside firejail / bwrap / unshare with prlimit caps and a stripped env (no ANTHROPIC_API_KEY-class secrets reach user code). Per-IP daily AI spend cap, per-IP build-sbf concurrency cap, per-minute rate limit.
/build/auto-fix accepts an additional differential body field with anchorSource + scenario. When that field is present, the byte-equal compare runs after each cargo-green iteration (default-on; pass ?with_differential=0 to skip). On DIVERGED, synthetic differential_diverge ValidationIssues feed back into the next refine call so patches converge toward both compile-AND-byte-equal. finalPayload.differentialVerdict carries the terminal verdict for badge consumption.
Threat model: SECURITY.md. Production deploy reqs are listed at the bottom.
/health returns release SHA + sandbox kind + prompt version + toolchain availability; /metrics returns refine cache hit rate, accept-rate per prompt version, build success/failure, p50/p95/p99 build latency, per-IP spend snapshot.
api/ Bun + Express service: parse, emit, validate, build, AI refine, sandbox
cli/ anvil-sol — npm CLI, ships api/src bundled via prepack
web/ Next.js workbench
docs/ Architecture, differential testing, feature matrix, migration guide
v0.4.0 — safe-by-default (--strict is the new CLI default; --permissive is the opt-out). See CHANGELOG.md for the BREAKING-change migration notes. Live at anvilsol.xyz, public API at anvil-prod-api-wff8f.ondigitalocean.app. 64/64 demo programs produce deployable .so via cargo build-sbf on Pinocchio. 150 byte-equal differential test files + 193 realworld cargo-green MUST_PASS entries + 100+ IR body statement kinds. Top DeFi: klend (63 instructions, cargo build-sbf GREEN). First multi-file real-world byte-equal: Helium circuit-breaker — Anchor and Pinocchio produce identical on-chain state. 1,255 commits, single developer.
Working notes for grant + migration: docs/migration-guide.md.
Apache 2.0. See LICENSE.
Issues + PRs welcome. Areas where help is most useful: new differential fixtures (the harness is designed for ~30-line additions — see docs/differential-testing.md), real-world Anchor programs that fail to transpile (file the source + the divergence), workspace/multi-crate Anchor projects.