shank-parse 2.0.0

A proc-macro crate that generates Rust client code from Shank/Anchor IDL JSON files for Solana programs.
Documentation

shank-parse

Crates.io Docs.rs License: MIT

A proc-macro crate that generates type-safe Rust client code at compile time from Shank / Anchor IDL JSON files for Solana programs.


Features

  • Zero boilerplate — point the macro at an IDL file and get fully-typed instruction builders, account/type (de)serializers, and a program-ID constant.
  • Compile-time code generation — the IDL is read and transformed during cargo build; no runtime overhead.
  • Borsh-backed (de)serialization — every generated struct/enum derives BorshSerialize / BorshDeserialize, so nested structs, arrays of defined types, and Options all just work. borsh is re-exported by shank-parse, so your crate doesn't need to depend on it directly.
  • Panic-free generated code — generated functions never unwrap/expect/panic; fallible operations return Result<_, std::io::Error>.
  • Supports Shank & Anchor IDL format — works with any IDL that follows the common Shank/Anchor JSON schema.
  • Submodule layout — generated code is organized into instructions, accounts, and types submodules (mirroring the IDL sections).

Installation

[dependencies]
shank-parse = "2"
solana-sdk = "4"

Usage

Place your IDL JSON file anywhere inside your crate (e.g. idl/my_program.json) and invoke the macro once:

shank_parse::shank_parse!("idl/my_program.json");

The path is resolved relative to your crate's root (CARGO_MANIFEST_DIR).

Example — Counter program

Given idl/counter.json (a Shank IDL with an InitCounter instruction):

shank_parse::shank_parse!("idl/counter.json");

use counter::accounts::Counter;
use counter::instructions::{init_counter, InitCounterAccounts};
use counter::types::{CounterEvent, InitCounterArgs};
use counter::ID;

fn build_and_decode() -> std::io::Result<()> {
    // Program-ID constant derived from metadata.address in the IDL
    println!("Program ID: {ID}");

    // Build an InitCounter instruction. Each instruction takes the program id,
    // a typed `<Instruction>Accounts` struct, and (when present) its args.
    // Builders return `Result` — serialization errors are propagated, not panicked.
    let accounts = InitCounterAccounts { payer, counter, system_program };
    let ix = init_counter(&ID, &accounts, &InitCounterArgs { count: 0 })?;

    // Deserialize an account (trailing padding bytes are ignored).
    let counter_state = Counter::from_account_data(&account_data)?;
    println!("count = {}", counter_state.count);

    // Decode any IDL-defined type from borsh bytes — e.g. an event carried in a
    // `Program data: <base64>` log line. Bring your own base64 decoder, then
    // hand the raw bytes to the generated `try_from_slice`.
    for log in &logs {
        if let Some(b64) = log.strip_prefix("Program data: ") {
            if let Ok(bytes) = base64_decode(b64) {
                if let Ok(event) = CounterEvent::try_from_slice(&bytes) {
                    println!("{event:?}");
                }
            }
        }
    }
    Ok(())
}

Generated submodules

Submodule Contents
<program>::instructions Instruction builder functions (returning Result<Instruction, std::io::Error>) and their <Instruction>Accounts structs
<program>::accounts Account structs with a from_account_data(&[u8]) -> Result<Self, std::io::Error> deserializer (ignores trailing bytes)
<program>::types The IDL types section — every struct/enum defined there, each deriving borsh and with an inherent try_from_slice(&[u8]) -> Result<Self, std::io::Error>. Field-less enums also get from_u8 / to_u8.
<program>::errors pub const error codes
<program>::ID Pubkey constant from metadata.address in the IDL

Decoding values

Any type from the types section decodes from borsh bytes via try_from_slice. For "event"-style data carried in a Program data: <base64> (or Program log: instruction data: <base64>) log line, extract and base64-decode the payload, then:

  • For an enum, try_from_slice reads the leading u8 variant tag (the discriminant) and dispatches to the matching variant.
  • For a struct, try_from_slice decodes the fields directly (no discriminant).

IDL format

The macro expects a JSON file following the Shank/Anchor IDL schema:

{
  "version": "0.1.0",
  "name": "counter",
  "instructions": [...],
  "accounts": [...],
  "types": [...],
  "errors": [...],
  "metadata": { "address": "<base58 program id>" }
}

Workspace layout

shank-parse/
├── lib/          # shank-parse — the public-facing crate
└── macro/        # shank-parse-macro — the proc-macro implementation

License

MIT — see LICENSE.