Reproducible IDL generation for native Pinocchio Solana programs.
This project is a source-to-IDL adapter: annotated Pinocchio Rust becomes a deterministic interface description that can be rendered as Codama JSON and Anchor/Solscan-compatible JSON.
It is meant for native-runtime teams that want Anchor-like interface artifacts without moving account validation, serialization, or dispatch into a framework.
The tool does not try to infer a program ABI from arbitrary Rust. It uses small annotations for the parts Rust cannot know: which structs are accounts/events/args, instruction discriminators, account order, PDA metadata, and event transport.
The annotation macros are compile-time hints only. They strip #[idl(...)] field attributes and return the original item, so they do not add runtime serialization, account validation, dispatch, or binary behavior.
pinocchio-idl-core: canonical IDL model and renderers.pinocchio-idl-attrs: compile-time annotation macros with no runtime behavior.pinocchio-idl: CLI forgenerate,check, anddoctor.
Today, install from Git:
cargo install --git https://github.com/oxy-Op/pinocchio-to-idl --package pinocchio-idlOr run from a local checkout:
git clone https://github.com/oxy-Op/pinocchio-to-idl
cd pinocchio-to-idl
cargo run -p pinocchio-idl -- --helpWhen using annotations inside a Pinocchio program, add the macro crate:
[dependencies]
pinocchio-idl-attrs = { git = "https://github.com/oxy-Op/pinocchio-to-idl" }The macros are compile-time hints only; they do not change the program binary behavior.
First, try the bundled fixture:
cargo run -p pinocchio-idl -- doctor \
--crate-dir fixtures/simple-pinocchio
cargo run -p pinocchio-idl -- generate \
--crate-dir fixtures/simple-pinocchio \
--name simple \
--program-id 11111111111111111111111111111111 \
--out idl/simple.codama.json
cargo run -p pinocchio-idl -- check \
--crate-dir fixtures/simple-pinocchio \
--name simple \
--program-id 11111111111111111111111111111111 \
--out idl/simple.codama.jsonFor a real Pinocchio program:
pinocchio-idl init-annotation-pass --crate-dir path/to/program
pinocchio-idl doctor \
--crate-dir path/to/program \
--jsonThen add sidecar annotations in the target program, usually src/idl.rs, and generate the IDL:
pinocchio-idl generate \
--crate-dir path/to/program \
--name <program_name> \
--program-id <program_id> \
--out idl/<program_name>.codama.json
pinocchio-idl check \
--crate-dir path/to/program \
--name <program_name> \
--program-id <program_id> \
--out idl/<program_name>.codama.jsongenerate produces the IDL. check is the CI guard: it fails when the committed IDL no longer matches the annotated source.
Most existing programs should start with a sidecar file. This keeps the IDL surface reviewable without touching processor logic.
// src/idl.rs
#![allow(dead_code)]
pub struct AccountInfo;
pub type RawPubkey = [u8; 32];
#[pinocchio_idl::args]
pub struct DepositArgs {
pub amount: u64,
}
#[pinocchio_idl::accounts]
pub struct DepositAccounts {
#[idl(signer, writable)]
pub payer: AccountInfo,
#[idl(writable, pda = "vault")]
pub vault: AccountInfo,
#[idl(default = "system_program")]
pub system_program: AccountInfo,
}
#[pinocchio_idl::instruction(2, accounts = DepositAccounts, args = DepositArgs)]
pub fn deposit() {}For a new program, inline annotations are shorter:
#[pinocchio_idl::args]
pub struct WithdrawArgs {
pub amount: u64,
}
#[pinocchio_idl::accounts]
pub struct WithdrawAccounts {
#[idl(signer)]
pub authority: AccountInfo,
#[idl(writable, pda = "vault")]
pub vault: AccountInfo,
}
#[pinocchio_idl::instruction(3, accounts = WithdrawAccounts, args = WithdrawArgs)]
pub fn process_withdraw(
program_id: &Pubkey,
accounts: &[AccountInfo],
data: &[u8],
) -> ProgramResult {
Ok(())
}For custom-packed native data, use one final raw byte tail:
#[pinocchio_idl::args]
pub struct ExecuteArgs {
pub mode: u8,
/// Native tail: compact instructions followed by auth payload.
#[idl(rest)]
pub instructions_and_auth_payload: Vec<u8>,
}See docs/annotating.md for the full annotation guide,
including account layouts, remaining accounts, raw byte tails, events, and
doctor behavior.
Generate Codama JSON, the default output:
cargo run -p pinocchio-idl -- generate \
--crate-dir fixtures/simple-pinocchio \
--name simple \
--program-id 11111111111111111111111111111111 \
--out idl/simple.codama.jsonFail CI when committed IDL drifts from source:
cargo run -p pinocchio-idl -- check \
--crate-dir fixtures/simple-pinocchio \
--name simple \
--program-id 11111111111111111111111111111111 \
--out idl/simple.codama.jsonRender Anchor/Solscan-compatible JSON when needed:
cargo run -p pinocchio-idl -- generate \
--crate-dir fixtures/simple-pinocchio \
--format anchor \
--out idl/simple.anchor.jsonInspect missing annotations or likely ABI-bearing Rust items:
cargo run -p pinocchio-idl -- doctor \
--crate-dir fixtures/simple-pinocchioEmit a machine-readable annotation report:
cargo run -p pinocchio-idl -- doctor \
--crate-dir fixtures/simple-pinocchio \
--jsonFail CI when doctor finds unresolved issues:
cargo run -p pinocchio-idl -- doctor \
--crate-dir fixtures/simple-pinocchio \
--deny-findings \
--deny-candidatesInstall annotation guidance into a target Pinocchio program:
cargo run -p pinocchio-idl -- init-annotation-pass \
--crate-dir path/to/programIf AGENTS.md or CLAUDE.md already exists, the command writes namespaced files such as AGENTS.pinocchio-idl.md. Add --append to also insert short references into the existing agent files.
Use macros to mark ABI-bearing items:
#[pinocchio_idl::account(...)]for fixed account layouts.#[pinocchio_idl::accounts]for explicit instruction account order.#[pinocchio_idl::args]for instruction args structs that need field hints.#[pinocchio_idl::instruction(...)]for instruction discriminators, account lists, and args.#[pinocchio_idl::event(...)]for log or self-CPI event payloads.
Use field hints for information Rust types do not encode by themselves:
#[idl(signer)],#[idl(writable)], and#[idl(optional)]on instruction accounts.#[idl(remaining)]for a placeholder representing native remaining accounts.#[idl(name = "...")]when the Rust sidecar name should differ from the public IDL name.#[idl(default = "system_program")]or#[idl(default = "<address>")]for accounts that clients can fill automatically.#[idl(pda = "...")]for PDA documentation.#[idl(pubkey)]and#[idl(pubkey_array)]for native[u8; 32]address fields.#[idl(type = "...")]for explicit wire-type overrides when a Rust alias or imported type would otherwise be ambiguous.#[idl(rest)]or#[idl(bytes_tail)]for one final raw dynamic byte tail.#[idl(type = "string")]for fixed-size UTF-8 byte buffers such as[u8; 32]or[[u8; 32]; N].
Plain args structs can stay unannotated when Rust already describes every field. Add #[pinocchio_idl::args] when the args fields need #[idl(...)] hints; it strips those helper attrs at compile time without exporting the args struct as a standalone IDL type.
Annotated accounts, types, and events also support name = "..." on the macro itself.
Normal Rust doc comments on annotated structs, fields, and instruction functions are carried into generated IDLs.
This is intentionally not Anchor-style runtime magic. The program still owns all account validation, PDA checks, CPI behavior, and serialization choices.
doctor --json emits structured findings and annotation candidates. init-annotation-pass writes local guidance into a target program repo for adding sidecar annotations in src/idl.rs, then running generate and check.
For a developer-facing explanation of every annotation shape, see docs/annotating.md.
The default output is Codama rootNode JSON, so it can be used directly as a Codama idl input. pinocchio-idl is the source adapter; Codama remains the renderer/client-generation layer.
See docs/codama-integration.md for the upstream/discoverability path.
Pre-1.0. The CLI supports deterministic extraction, Codama output, Anchor/Solscan-compatible output, doctor, and check drift detection.
The CLI is not published on crates.io yet. Git install is recommended until the annotation surface is tested against more real Pinocchio programs.
Known gaps:
- The extractor only understands the current annotation surface and common fixed-size Rust field shapes.
- Generated Codama clients are delegated to Codama renderers; this project does not generate SDKs directly.
- Event decoding support depends on downstream renderers and indexer conventions.
Suggested GitHub topics: solana, pinocchio, codama, idl, anchor-idl, solana-program, rust, web3-devtools, sdk-generation, native-solana.