# shank-parse
[](https://crates.io/crates/shank-parse)
[](https://docs.rs/shank-parse)
[](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
| `<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).