anchor-idl-v0-to-v1
Automatically migrate Anchor IDL JSON files from v0 format to v1 format.
Published on the Codemod registry: app.codemod.com/registry/anchor-idl-v0-to-v1
What problem does this solve?
When Anchor (the Solana smart contract framework) released version 0.30, it changed the format of IDL files completely. IDL stands for Interface Definition Language. It is a JSON file that describes your smart contract — what instructions it has, what accounts it uses, what types it defines.
The old format is called v0. The new format is called v1. They are not compatible. If you built your program before Anchor 0.30, your IDL files are v0. All the new Anchor tooling expects v1.
The difference is not just a few renamed fields. The entire schema changed:
- Instruction and account names must be in
snake_case(Anchor v1 uses Rust's naming rules) - Every instruction now needs an 8-byte
discriminator(a hash that identifies it on-chain) - Every account and event also needs its own
discriminator - The top-level
nameandversionfields move inside ametadatablock - The program address must be at the top level as
address - The
accounts[]array inside instructions changed structure - Type references like
{ "defined": "MyType" }became{ "defined": { "name": "MyType" } } publicKeyas a type becomespubkey- The
events[]array no longer holds struct definitions — events now live intypes[] - PDA seeds have a new structure
isMutandisSigneron accounts becomewritableandsigner
Doing all of this by hand for one program is already tedious. For a team managing ten or twenty programs it can take days and it is very easy to make mistakes.
This codemod does it automatically in seconds.
How to run it
You do not need to install anything. Just point it at your project:
bash
It will find every IDL JSON file in your project (in target/idl/, idl/, src/idl/, src/artifacts/, and artifacts/ folders), check if it is a v0 file, and rewrite it in place as v1.
Files that are already v1 are skipped automatically. Nothing gets changed twice.
How it works
The tool is built on the Codemod platform. It runs as a two-step workflow:
Step 1 — Deterministic migration
A JavaScript module (migrate.mjs) reads each JSON file and applies all 12 documented transform rules mechanically. This step handles everything that has a known, fixed answer. It is deterministic — same input always gives same output, with no guessing.
The 12 rules it applies:
- Adds
metadata.spec = "0.1.0"to mark the file as v1 - Moves
nameandversioninto themetadatablock - Adds the program
addressat the top level - Converts all instruction, account, and field names to
snake_case - Adds 8-byte
discriminatorarrays to every instruction (via SHA-256 of"global:instruction_name") - Adds 8-byte
discriminatorarrays to every account (via SHA-256 of"account:AccountName") - Adds 8-byte
discriminatorarrays to every event (via SHA-256 of"event:EventName") - Rewrites
isMut/isSigneron instruction accounts towritable/signer - Converts
publicKeytype topubkey - Converts
{ "defined": "Name" }to{ "defined": { "name": "Name" } } - Moves account type definitions and event structs into the top-level
types[]array - Restructures PDA seeds to the new v1 seed format
It also deduplicates types[] — if an account name already exists in types[], the duplicate is dropped automatically.
Step 2 — AI cleanup (optional)
An AI step using GPT-4o reviews the output and handles the remaining ~10% of edge cases that require judgment. Things like unusual type combinations, programs built before Anchor 0.26 where discriminator hashing may differ, or custom metadata fields that need preserving.
The AI step is skipped if no LLM_API_KEY is set, which is fine for the vast majority of programs.
The migration logic in detail
The core logic lives entirely in migrate.mjs. This file is both a CLI tool and an importable library.
Discriminator generation
Anchor generates discriminators using SHA-256:
text
The first 8 bytes of the hash become the discriminator array. This codemod replicates that exact calculation using Node.js's built-in crypto module.
snake_case conversion
Anchor uses the Rust heck crate's to_snake_case() function internally. This codemod mirrors that exact behaviour:
text
Type conversion
Every type reference in args, fields, and return values is recursively converted:
| v0 | v1 |
|---|---|
"publicKey" | "pubkey" |
{ "defined": "Foo" } | { "defined": { "name": "Foo" } } |
{ "option": "publicKey" } | { "option": "pubkey" } |
{ "vec": { "defined": "Foo" } } | { "vec": { "defined": { "name": "Foo" } } } |
{ "definedWithTypeArgs": { "name": "Foo", "args": [...] } } | { "defined": { "name": "Foo", "generics": [...] } } |
{ "alias": <type> } (kind=alias) | { "kind": "type", "alias": <type> } |
Files in this repo
text
Test results
text
The test suite (test.mjs) covers:
- All 12 transform rules individually
- Correct discriminator values (verified against known on-chain values)
- snake_case conversion edge cases (acronyms, already-snake names, camelCase)
- Type conversion for all type shapes including nested generics
- PDA seed conversion for const / arg / account seed kinds
- Composite account groups (nested
accounts[]arrays) - Enum variants with named and tuple fields
- Docs preservation through all transform paths
- Event struct conversion with correct field handling
- Account type merging into
types[] - Deduplication when account names overlap with
types[] - File round-trip (read file → migrate → write file → re-read → validate)
- Skip guard: files already in v1 format are left unchanged
- Address override via CLI flag
isV1Idl()detection function
All four real-world fixtures produce byte-exact matches against their expected output.
Running the tests locally
bash
No dependencies to install. Everything runs on plain Node.js (v18+).
Using it as a library
migrate.mjs exports two functions you can use directly in your own scripts:
js
You can also pass a program address override as the second argument:
js
Using it as a CLI tool
bash
Background — why this was built
This project was built for the Boring AI hackathon on DoraHacks.
The hackathon's theme is "boring AI" — practical automation tools that solve real developer pain. Not flashy demos. Not chatbots. Just tools that save developers real time on real work.
Anchor IDL v0 to v1 migration is exactly that kind of problem. It is tedious, mechanical, and error-prone when done by hand. It is the kind of work that a codemod should do.
The migration is item #6 on the hackathon's pre-approved codemod list, which confirms it is a known pain point in the Solana ecosystem.
A full write-up of how it was built, including real-world test results, is published here:
Built a bot that migrates Solana smart contract files automatically
Technical decisions
Why a separate migrate.mjs instead of putting everything in scripts/codemod.ts?
The Codemod platform compiles scripts/codemod.ts with Rolldown, a bundler. During development, the codemod entry point had its own full copy of the migration logic. This led to two bugs: the codemod version was deduplicating types but the standalone library was not, and the codemod version was preserving event docs but the library was dropping them.
The fix was to make scripts/codemod.ts import from migrate.mjs directly. Single source of truth. Now both paths are identical.
Why not use an npm package for snake_case conversion?
The Rust heck crate has specific behaviour for consecutive uppercase letters (e.g. HTMLParser → html_parser) that differs from most JavaScript libraries. Replicating the exact 4-line implementation was safer and simpler than finding and auditing a third-party package.
Why SHA-256 from Node.js crypto instead of a separate library?
crypto is built into Node.js. No install, no supply chain risk, no version conflicts.
License
MIT