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](https://img.shields.io/crates/v/shank-parse.svg)](https://crates.io/crates/shank-parse)
[![Docs.rs](https://docs.rs/shank-parse/badge.svg)](https://docs.rs/shank-parse)
[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE)

A proc-macro crate that generates type-safe Rust client code at compile time from [Shank](https://github.com/metaplex-foundation/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 `Option`s 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

```toml
[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:

```rust
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):

```rust
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:

```json
{
  "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](LICENSE).