# Macros (Carbon 2.0)

> The macros Carbon 2.0.0 exports: try_decode_instructions!, instruction_decoder_collection! and #[derive(InstructionType)], with their exact expansion and requirements.

Carbon exports three macros, used mostly by generated decoders and by code that combines several programs in one transaction pipe. This page documents what each one expects and what it generates in 2.0.0.

| Macro                                                                | Kind                     | Defined in           | Use it to                                                                |
| -------------------------------------------------------------------- | ------------------------ | -------------------- | ------------------------------------------------------------------------ |
| [`try_decode_instructions!`](#try_decode_instructions)               | declarative              | `carbon-macros`      | Write an `InstructionDecoder` that tries each instruction type in turn   |
| [`#[derive(InstructionType)]`](#deriveinstructiontype)               | derive                   | `carbon-proc-macros` | Generate a tag enum and `get_instruction_type()` for an instruction enum |
| [`instruction_decoder_collection!`](#instruction_decoder_collection) | function-like procedural | `carbon-proc-macros` | Generate an `InstructionDecoderCollection` over several decoders         |

All three are re-exported from the root of `carbon_core` when its default `macros` feature is on, so import them as `carbon_core::try_decode_instructions`, `carbon_core::InstructionType` and `carbon_core::instruction_decoder_collection`. You don't need to depend on `carbon-macros` or `carbon-proc-macros` directly. With `carbon-core = { version = "2.0.0", default-features = false }` the macros are not available.

Examples on this page use:

`Cargo.toml`:

```toml
[dependencies]
borsh = { version = "1.5.7", features = ["derive"] }
carbon-core = "2.0.0"
serde = { version = "1", features = ["derive"] }
solana-instruction = "3.4.1"
solana-pubkey = "4.2.1"
```

## `try_decode_instructions!`

Tries a list of instruction types against one instruction and returns the first that decodes.

```rust
try_decode_instructions!(
    $instruction:expr,
    $program_id:expr,
    $( $Enum::$Variant => $Type ),* $(,)?
)
```

**Details.** For each `Enum::Variant => Type` pair, in order, the macro expands to:

```rust
if let Some(data) = <Type>::decode(instruction.data.as_slice()) {
    if let Some(accounts) = <Type>::arrange_accounts(&instruction.accounts) {
        return Some(Enum::Variant { program_id: $program_id, data, accounts });
    }
}
```

and ends with `None`. Requirements:

| Requirement                                                                                                  | Why                                                                                                                                                                                                              |
| ------------------------------------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Use it as the tail expression of a function returning `Option<Enum>`                                         | It uses `return Some(…)` on a match.                                                                                                                                                                             |
| Each `Type` has an associated `fn decode(&[u8]) -> Option<Self>`                                             | Called as `<Type>::decode`. An inherent method works; `CarbonDeserialize::deserialize` does **not** (different name). The macro's own doc comment says `CarbonDeserialize` is required, which is wrong in 2.0.0. |
| Each `Type` implements [`ArrangeAccounts`](https://carbon.sevenlabs.org/reference/traits.md#arrangeaccounts) | Called as `<Type>::arrange_accounts`. The macro imports the trait itself.                                                                                                                                        |
| Each `Enum::Variant` is a struct variant with fields `program_id`, `data`, `accounts`                        | Built with those field names.                                                                                                                                                                                    |
| The crate `carbon_core` is reachable by that name                                                            | The expansion uses `carbon_core::deserialize::ArrangeAccounts`.                                                                                                                                                  |

An instruction whose data decodes but whose account list is too short is skipped, and the next pair is tried. Order matters when discriminators overlap. Generated decoders call this macro from `decode_instruction` after checking the program ID.

**Example.**

`src/vault.rs`:

```rust
use {
    carbon_core::{
        account_utils::next_account, deserialize::ArrangeAccounts,
        instruction::InstructionDecoder, try_decode_instructions,
    },
    solana_instruction::{AccountMeta, Instruction},
    solana_pubkey::Pubkey,
};

pub const PROGRAM_ID: Pubkey =
    Pubkey::from_str_const("Vau1t11111111111111111111111111111111111111");

#[derive(Debug, Clone, borsh::BorshDeserialize)]
pub struct Deposit {
    pub amount: u64,
}

#[derive(Debug, Clone)]
pub struct DepositAccounts {
    pub vault: Pubkey,
    pub user: Pubkey,
}

impl Deposit {
    pub fn decode(data: &[u8]) -> Option<Self> {
        let body = data.strip_prefix(&[0u8])?;
        borsh::from_slice(body).ok()
    }
}

impl ArrangeAccounts for Deposit {
    type ArrangedAccounts = DepositAccounts;

    fn arrange_accounts(accounts: &[AccountMeta]) -> Option<DepositAccounts> {
        let mut iter = accounts.iter();
        Some(DepositAccounts {
            vault: next_account(&mut iter)?,
            user: next_account(&mut iter)?,
        })
    }
}

#[derive(Debug, Clone, borsh::BorshDeserialize)]
pub struct Withdraw {
    pub amount: u64,
}

impl Withdraw {
    pub fn decode(data: &[u8]) -> Option<Self> {
        let body = data.strip_prefix(&[1u8])?;
        borsh::from_slice(body).ok()
    }
}

impl ArrangeAccounts for Withdraw {
    type ArrangedAccounts = DepositAccounts;

    fn arrange_accounts(accounts: &[AccountMeta]) -> Option<DepositAccounts> {
        Deposit::arrange_accounts(accounts)
    }
}

#[derive(Debug, Clone)]
pub enum VaultInstruction {
    Deposit { program_id: Pubkey, data: Deposit, accounts: DepositAccounts },
    Withdraw { program_id: Pubkey, data: Withdraw, accounts: DepositAccounts },
}

pub struct VaultDecoder;

impl InstructionDecoder<'_> for VaultDecoder {
    type InstructionType = VaultInstruction;

    fn decode_instruction(&self, instruction: &Instruction) -> Option<VaultInstruction> {
        if instruction.program_id != PROGRAM_ID {
            return None;
        }
        try_decode_instructions!(
            instruction,
            PROGRAM_ID,
            VaultInstruction::Deposit => Deposit,
            VaultInstruction::Withdraw => Withdraw,
        )
    }
}
```

**See also.** [`InstructionDecoder`](https://carbon.sevenlabs.org/reference/traits.md#instructiondecoder), [Custom decoder](https://carbon.sevenlabs.org/guides/custom-decoder.md), [source](https://github.com/sevenlabs-hq/carbon/blob/v2.0.0/crates/macros/src/try_decode_ixs.rs), [docs.rs](https://docs.rs/carbon-macros/2.0.0/carbon_macros/macro.try_decode_instructions.html).

## `#[derive(InstructionType)]`

Generates a tag enum with the same variant names and a `get_instruction_type` method.

```rust
#[derive(InstructionType)]
pub enum MyInstruction { /* variants */ }

// generates:
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
pub enum MyInstructionType { /* same variant names, no fields */ }

impl MyInstruction {
    pub fn get_instruction_type(&self) -> MyInstructionType;
}
```

**Details.** Works with unit, tuple and struct variants; fields are dropped in the tag enum. The tag enum is always `pub` and named `<Enum>Type`, and its derive uses the path `serde::Serialize`, so your crate must depend on `serde`. Generics on the source enum are not supported. Generated decoders in 2.0.0 don't use this derive, so `MeteoraDlmmInstructionType` and similar don't exist.

**Example.**

```rust
use carbon_core::InstructionType;

#[derive(Debug, Clone, InstructionType)]
pub enum CounterInstruction {
    Initialize,
    Increment(u64),
    Reset { to: u64 },
}

pub fn demo() {
    let ix = CounterInstruction::Increment(5);
    assert_eq!(ix.get_instruction_type(), CounterInstructionType::Increment);
}
```

**See also.** [`instruction_decoder_collection!`](#instruction_decoder_collection), [docs.rs](https://docs.rs/carbon-proc-macros/2.0.0/carbon_proc_macros/derive.InstructionType.html).

## `instruction_decoder_collection!`

Generates an enum over several programs' instructions and its `InstructionDecoderCollection` impl.

```rust
instruction_decoder_collection!(
    InstructionsEnum, InstructionTypesEnum, ProgramsEnum,
    // 4-part entry
    Variant => PROGRAM_ID_PATH => decoder_expr => InstructionType,
    // 3-part entry
    Variant => decoder_expr => InstructionType,
    …
);
```

**Details.** The macro generates three `pub` enums and one impl:

| Generated item                                                                    | Definition                                                                                                                                                                                                                                                                                                                                                                |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `InstructionsEnum`                                                                | `#[derive(Debug, Clone, Hash, serde::Serialize, serde::Deserialize, PartialEq, Eq)]`, one tuple variant `Variant(InstructionType)` per entry                                                                                                                                                                                                                              |
| `InstructionTypesEnum`                                                            | `#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]`, one variant `Variant(InstructionTypeType)` per entry, wrapping the tag from `#[derive(InstructionType)]`                                                                                                                                                                                                      |
| `ProgramsEnum`                                                                    | `#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]`, one unit variant per entry. Not used by anything; available for your code.                                                                                                                                                                                                                                    |
| `impl carbon_core::collection::InstructionDecoderCollection for InstructionsEnum` | `type InstructionType = InstructionTypesEnum`. `parse_instruction` matches `instruction.program_id` against every 4-part `PROGRAM_ID_PATH`; on a match it returns that decoder's result (or `None`, without trying others). When no 4-part path matches, it tries the 3-part decoders in declaration order. `get_type` calls `get_instruction_type()` on the inner value. |

Requirements, each of which is a compile error when missing:

1. Each `InstructionType` derives `Debug`, `Clone`, `PartialEq`, `Eq`, `Hash`, `serde::Serialize`, `serde::Deserialize` and [`InstructionType`](#deriveinstructiontype).
2. The `InstructionType` path's last segment is used as a bare name, and so is `<Name>Type`: both must be in scope (import them with `use`).
3. `PROGRAM_ID_PATH` must be a `const` path (it is used as a `match` pattern), not a `static` or an expression.
4. `decoder_expr` is an expression evaluated on every call (`MemoDecoder`, or `MyDecoder::new()`), and must implement `InstructionDecoder`.
5. Your crate depends on `serde` and `solana-instruction`, which the expansion names directly.

Generated decoders don't meet requirement 1: their instruction enums derive neither `Eq`, `Hash` nor `InstructionType`. Passing `MeteoraDlmmInstruction` fails with:

```text
error[E0277]: the trait bound `MeteoraDlmmInstruction: Eq` is not satisfied
error[E0277]: the trait bound `MeteoraDlmmInstruction: Hash` is not satisfied
error[E0425]: cannot find type `MeteoraDlmmInstructionType` in this scope
error[E0599]: no method named `get_instruction_type` found for reference `&MeteoraDlmmInstruction` in the current scope
```

For generated decoders, write the collection by hand as shown in [`InstructionDecoderCollection`](https://carbon.sevenlabs.org/reference/traits.md#instructiondecodercollection).

**Example.** Two hand-written decoders, one 4-part and one 3-part entry:

`src/main.rs`:

```rust
use {
    carbon_core::{instruction::InstructionDecoder, instruction_decoder_collection, InstructionType},
    serde::{Deserialize, Serialize},
    solana_instruction::Instruction,
    solana_pubkey::Pubkey,
};

pub const MEMO_PROGRAM_ID: Pubkey =
    Pubkey::from_str_const("MemoSq4gqABAXKb96qnH8TysNcWxMyWCqXgDLGmfcHr");
pub const NOOP_PROGRAM_ID: Pubkey =
    Pubkey::from_str_const("noopb9bkMVfRPU8AsbpTUg8AQkHtKwMYZiFUjNRtMmV");

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, InstructionType)]
pub enum MemoInstruction {
    Memo(String),
}

pub struct MemoDecoder;

impl InstructionDecoder<'_> for MemoDecoder {
    type InstructionType = MemoInstruction;

    fn decode_instruction(&self, ix: &Instruction) -> Option<MemoInstruction> {
        (ix.program_id == MEMO_PROGRAM_ID)
            .then(|| String::from_utf8(ix.data.clone()).ok())
            .flatten()
            .map(MemoInstruction::Memo)
    }
}

#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, InstructionType)]
pub enum NoopInstruction {
    Log(Vec<u8>),
}

pub struct NoopDecoder;

impl InstructionDecoder<'_> for NoopDecoder {
    type InstructionType = NoopInstruction;

    fn decode_instruction(&self, ix: &Instruction) -> Option<NoopInstruction> {
        (ix.program_id == NOOP_PROGRAM_ID).then(|| NoopInstruction::Log(ix.data.clone()))
    }
}

instruction_decoder_collection!(
    AllInstructions, AllInstructionTypes, AllPrograms,
    // 4-part: matched on the program ID first.
    Memo => MEMO_PROGRAM_ID => MemoDecoder => MemoInstruction,
    // 3-part: tried in order when no 4-part entry matches.
    Noop => NoopDecoder => NoopInstruction,
);
```

Add a `main` to the same file that parses one instruction for each program:

```rust
use carbon_core::collection::InstructionDecoderCollection;

fn main() {
    let memo = Instruction {
        program_id: MEMO_PROGRAM_ID,
        accounts: vec![],
        data: b"gm".to_vec(),
    };
    let noop = Instruction {
        program_id: NOOP_PROGRAM_ID,
        accounts: vec![],
        data: vec![1, 2, 3],
    };

    for ix in [memo, noop] {
        let parsed = AllInstructions::parse_instruction(&ix).unwrap();
        println!("{parsed:?} -> {:?}", parsed.get_type());
    }
}
```

```sh
cargo run
```

Output:

```text
Memo(Memo("gm")) -> Memo(Memo)
Noop(Log([1, 2, 3])) -> Noop(Log)
```

Register the collection on a pipeline with `Pipeline::builder().transaction::<AllInstructions, _>(processor)`.

**See also.** [Transactions](https://carbon.sevenlabs.org/concepts/transactions.md), [Multiple programs](https://carbon.sevenlabs.org/guides/multiple-programs.md), [source](https://github.com/sevenlabs-hq/carbon/blob/v2.0.0/crates/proc-macros/src/lib.rs), [docs.rs](https://docs.rs/carbon-proc-macros/2.0.0/carbon_proc_macros/macro.instruction_decoder_collection.html).

## Re-exported crates

`carbon_core` also re-exports two crates that generated code relies on.

```rust
pub use borsh;
#[doc(hidden)]
pub use log; // only with the `macros` feature
```

**Details.** `carbon_core::borsh` is borsh 1.x. Generated decoders derive `carbon_core::borsh::BorshDeserialize` with `use carbon_core::borsh;` in scope, so a decoder crate doesn't need its own `borsh` dependency for those derives. `carbon_core::log` is hidden from the docs and not part of the public API; depend on `log` yourself.

**Example.**

```rust
use carbon_core::borsh::{self, BorshDeserialize};

#[derive(BorshDeserialize)]
pub struct Header {
    pub version: u8,
}
```

**See also.** [Feature flags](https://carbon.sevenlabs.org/reference/feature-flags.md), [docs.rs](https://docs.rs/carbon-core/2.0.0/carbon_core/).

---

Carbon 2.0.0 documentation · Web: https://carbon.sevenlabs.org/reference/macros · Last updated 2026-11-09 · Index of all pages: https://carbon.sevenlabs.org/llms.txt
