Skip to content

About

Reproducible IDL generation for native Pinocchio Solana programs. Annotate Rust source once, emit Codama JSON plus Anchor/Solscan-compatible IDLs, and fail CI when generated interfaces drift.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Latest commit

 

History

15 Commits

Folders and files

Repository files navigation

Pinocchio IDL

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.

Shape

  • pinocchio-idl-core: canonical IDL model and renderers.
  • pinocchio-idl-attrs: compile-time annotation macros with no runtime behavior.
  • pinocchio-idl: CLI for generate, check, and doctor.

Install

Today, install from Git:

cargo install --git https://github.com/oxy-Op/pinocchio-to-idl --package pinocchio-idl

Or run from a local checkout:

git clone https://github.com/oxy-Op/pinocchio-to-idl
cd pinocchio-to-idl
cargo run -p pinocchio-idl -- --help

When 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.

Quickstart

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.json

For a real Pinocchio program:

pinocchio-idl init-annotation-pass --crate-dir path/to/program

pinocchio-idl doctor \
  --crate-dir path/to/program \
  --json

Then 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.json

generate produces the IDL. check is the CI guard: it fails when the committed IDL no longer matches the annotated source.

Minimal Annotation Examples

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.

Commands

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.json

Fail 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.json

Render Anchor/Solscan-compatible JSON when needed:

cargo run -p pinocchio-idl -- generate \
  --crate-dir fixtures/simple-pinocchio \
  --format anchor \
  --out idl/simple.anchor.json

Inspect missing annotations or likely ABI-bearing Rust items:

cargo run -p pinocchio-idl -- doctor \
  --crate-dir fixtures/simple-pinocchio

Emit a machine-readable annotation report:

cargo run -p pinocchio-idl -- doctor \
  --crate-dir fixtures/simple-pinocchio \
  --json

Fail CI when doctor finds unresolved issues:

cargo run -p pinocchio-idl -- doctor \
  --crate-dir fixtures/simple-pinocchio \
  --deny-findings \
  --deny-candidates

Install annotation guidance into a target Pinocchio program:

cargo run -p pinocchio-idl -- init-annotation-pass \
  --crate-dir path/to/program

If 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.

Annotation Model

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.

Assisted Annotation Pass

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.

See docs/annotation-pass.md.

For a developer-facing explanation of every annotation shape, see docs/annotating.md.

Codama Integration

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.

Status

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.

Repository Topics

Suggested GitHub topics: solana, pinocchio, codama, idl, anchor-idl, solana-program, rust, web3-devtools, sdk-generation, native-solana.

About

Reproducible IDL generation for native Pinocchio Solana programs. Annotate Rust source once, emit Codama JSON plus Anchor/Solscan-compatible IDLs, and fail CI when generated interfaces drift.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Contributors

Languages