> ## Documentation Index
> Fetch the complete documentation index at: https://visualsign.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Instruction decoding

> Decode Solana instructions from an Anchor IDL, the path most Solana presets take

There are two approaches for decoding Solana instruction data, depending on whether the target program has an Anchor IDL:

1. **IDL-based parsing** — automatic deserialization using an Anchor IDL JSON. This is the preferred path when an IDL is available.
2. **Manual byte decoding** — reading fields at known byte offsets. Use this for programs with custom binary serialization and no Anchor IDL.

## IDL-based parsing

### How it works

The [`solana-parser`](https://github.com/prasincs/solana-parser) crate reads an Anchor IDL JSON, matches the 8-byte discriminator at the start of the instruction data, and deserializes the Borsh-encoded arguments into a JSON map. No manual byte offsets needed.

The result is a `SolanaParsedInstructionData` struct containing:

* `instruction_name` — the matched instruction name from the IDL
* `program_call_args` — a map of argument names to deserialized values

### Built-in IDLs

`solana-parser` ships with embedded program IDLs that are compiled into the binary and used automatically. See the [`ProgramType` enum](https://github.com/prasincs/solana-parser/blob/8248d99e42ce8a56ad440ed9b2201607feb1a150/src/solana/structs.rs#L7-L20) for the current list.

You only need to supply an IDL if your program isn't already covered.

### IDL structure

A minimal Anchor IDL JSON looks like this:

```json theme={null}
{
  "address": "YourProgramAddress111111111111111111111111111",
  "metadata": { "name": "my_swap", "version": "0.1.0" },
  "instructions": [
    {
      "name": "swap",
      "discriminator": [248, 198, 158, 145, 225, 117, 135, 200],
      "accounts": [
        { "name": "user", "signer": true },
        { "name": "source_token_account", "writable": true },
        { "name": "destination_token_account", "writable": true },
        { "name": "pool", "writable": true }
      ],
      "args": [
        { "name": "amount_in", "type": "u64" },
        { "name": "min_amount_out", "type": "u64" },
        { "name": "slippage_bps", "type": "u16" }
      ]
    }
  ],
  "types": []
}
```

Key fields:

* **`discriminator`** — the first 8 bytes of the instruction data, used to identify which instruction is being called
* **`accounts`** — ordered list of accounts matching the transaction's account indices
* **`args`** — Borsh-deserialized from the bytes after the discriminator

### Loading a built-in IDL

```rust theme={null}
use solana_parser::{ProgramType, decode_idl_data, Idl};

fn get_idl(program_id: &str) -> Option<Idl> {
    ProgramType::from_program_id(program_id)
        .and_then(|pt| decode_idl_data(pt.idl_json()).ok())
}
```

`ProgramType::from_program_id()` returns `Some` if the program has a built-in IDL, then `decode_idl_data()` deserializes the JSON into an `Idl` struct.

### Embedding your own IDL

Use `include_str!` to compile the IDL JSON into the binary:

```rust theme={null}
use solana_parser::{decode_idl_data, Idl};

const MY_PROGRAM_IDL: &str = include_str!("idls/my_program.json");

fn get_my_idl() -> Option<Idl> {
    decode_idl_data(MY_PROGRAM_IDL).ok()
}
```

For runtime-supplied IDLs (e.g., loaded from configuration), use `CustomIdlConfig::from_json()`:

```rust theme={null}
use solana_parser::CustomIdlConfig;

let config = CustomIdlConfig::from_json(idl_json_string, true);
```

The `IdlRegistry` in `visualsign-solana` manages both built-in and custom IDLs, checking custom configs first and falling back to built-in `ProgramType` lookups.

### Parsing an instruction

```rust theme={null}
use solana_parser::{parse_instruction_with_idl, decode_idl_data, ProgramType};
use visualsign::errors::VisualSignError;

fn parse_jupiter_instruction(data: &[u8]) -> Result<(), VisualSignError> {
    let program_id = "JUP6LkbZbjS1jKKwapdHNy74zcZ3tLUZoi5QNyVTaV4";
    let idl = ProgramType::from_program_id(program_id)
        .and_then(|pt| decode_idl_data(pt.idl_json()).ok())
        .ok_or_else(|| VisualSignError::DecodeError("IDL not found".into()))?;

    let parsed = parse_instruction_with_idl(data, program_id, &idl)
        .map_err(|e| VisualSignError::DecodeError(format!("Failed to parse instruction: {e}")))?;

    match parsed.instruction_name.as_str() {
        "shared_accounts_route" => {
            let slippage_bps = parsed.program_call_args
                .get("slippage_bps")
                .and_then(|v| v.as_u64());
            // ... build visualization fields
        }
        _ => {
            // Unknown instruction — still has instruction_name from the IDL
        }
    }
    Ok(())
}
```

`parse_instruction_with_idl` returns a `SolanaParsedInstructionData` with the instruction name and all arguments deserialized from the Borsh-encoded data.

**Reference implementation:** [Jupiter swap preset](https://github.com/anchorageoss/visualsign-parser/tree/main/src/chain_parsers/visualsign-solana/src/presets/jupiter_swap/mod.rs)

## Manual byte decoding

### When to use

Use manual byte decoding for programs that don't have an Anchor IDL — programs with custom binary serialization where you need full control over how bytes are read.

### Pattern

Read a discriminator (the size varies per program — Anchor uses 8 bytes, but others may use 1, 2, or 4 bytes), match it to an instruction type, then parse fields at known byte offsets:

```rust theme={null}
#[repr(u16)]
enum MyInstructionKind {
    Create = 0,
    Transfer = 1,
}

fn parse_instruction(data: &[u8]) -> Result<MyInstruction, MyError> {
    if data.len() < 2 {
        return Err(MyError::DataTooShort("missing discriminator"));
    }

    let discriminator = u16::from_le_bytes([data[0], data[1]]);
    match discriminator {
        0 => parse_create(data),
        1 => parse_transfer(data),
        _ => Ok(MyInstruction::Unknown {
            discriminator,
            raw_data: data.to_vec(),
        }),
    }
}

fn parse_transfer(data: &[u8]) -> Result<MyInstruction, MyError> {
    if data.len() < 42 {
        return Err(MyError::DataTooShort("transfer data"));
    }

    let amount = u64::from_le_bytes(
        data[2..10].try_into()
            .map_err(|_| MyError::DataTooShort("transfer amount"))?,
    );
    let destination = &data[10..42]; // 32-byte pubkey
    // ...
}
```

Key considerations:

* Always validate data length before reading at an offset
* Use little-endian byte order (`from_le_bytes`) — this is standard for Solana
* Define a structured error type with context about which field failed to parse
* Include an `Unknown` variant to handle unrecognized discriminators gracefully

**Reference implementation:** [Swig wallet preset](https://github.com/anchorageoss/visualsign-parser/tree/main/src/chain_parsers/visualsign-solana/src/presets/swig_wallet/mod.rs)

## Extending solana-parser

For a more generic, reusable solution, you can contribute a built-in IDL to the [`solana-parser`](https://github.com/prasincs/solana-parser) crate. This makes the IDL available to all consumers of the crate without any extra configuration. This is also the planned approach for supporting Codama-generated IDLs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.