# Instructions (Carbon 2.0)

> InstructionMetadata, stack heights and absolute paths, nested instructions and CPIs, event CPI instructions, and decoding log events with decode_log_events.

A Solana transaction runs top-level instructions, and each of those can call other programs through cross-program invocation (CPI). Carbon decodes all of them, nested ones included, and tells your processor where each instruction sits. This page explains that position data, the nested instruction tree, and the two ways programs emit events.

## Instruction metadata

Every [instruction processor](https://carbon.sevenlabs.org/concepts/processors.md#instruction-pipes) receives an `InstructionMetadata`:

```rust
pub struct InstructionMetadata {
    pub transaction_metadata: Arc<TransactionMetadata>,
    pub stack_height: u32,
    pub index: u32,
    pub absolute_path: Vec<u8>,
}
```

| Field                  | Meaning                                                                                                                                                                                                                                                                                            |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `transaction_metadata` | The transaction this instruction belongs to: signature, slot, fee payer, message, execution meta (logs, balances, inner instructions), block time. Shared by every instruction of the transaction. See [Transactions](https://carbon.sevenlabs.org/concepts/transactions.md#transaction-metadata). |
| `stack_height`         | 1 for a top-level instruction, 2 for an instruction it called, and so on up to 5 (`MAX_INSTRUCTION_STACK_DEPTH`).                                                                                                                                                                                  |
| `index`                | The index of the top-level instruction this one belongs to. An inner instruction has the same `index` as its top-level ancestor.                                                                                                                                                                   |
| `absolute_path`        | The position at each level, starting with the top-level index. `[3]` is top-level instruction 3; `[3, 0, 2]` is the third instruction called by the first instruction called by top-level instruction 3.                                                                                           |

`absolute_path.len()` equals `stack_height`, and `absolute_path[0]` equals `index`. The pair (transaction signature, `absolute_path`) identifies an instruction uniquely, which is how [`DeduplicationFilter`](https://carbon.sevenlabs.org/concepts/filters.md#deduplicationfilter) recognizes repeats.

A Jupiter route that swaps through Meteora DLMM looks like this (simplified):

![Instructions nested by stack height](https://carbon.sevenlabs.org/diagrams/instruction-tree.svg)

**Diagram: Instructions nested by stack height.** A Jupiter route at absolute_path \[2] has stack_height 1. It calls Token: transfer at \[2, 0] and Meteora DLMM: swap at \[2, 3], both stack_height 2, followed by more inner instructions. The Meteora DLMM swap calls Token: transferChecked at \[2, 3, 0] and \[2, 3, 1] and emits its event CPI at \[2, 3, 2], all stack_height 3.

## How Carbon builds the tree

Solana records inner instructions in the transaction's metadata as one flat group per top-level instruction, each with its stack height. For every transaction, Carbon:

1. Walks the top-level instructions in order and, after each one, its group of inner instructions.
2. Derives each inner instruction's `absolute_path` from the sequence of stack heights.
3. Resolves account keys, including those loaded from address lookup tables, and builds a `solana_instruction::Instruction` for each.
4. Nests the list into `NestedInstructions`, a tree of `NestedInstruction` nodes.

An inner instruction without a stack height, or with one outside 2 to 5, can't be placed in the tree. Carbon logs a warning and drops it together with the rest of its group, so transactions whose metadata lacks stack heights reach your processors without inner instructions.

## Nested instructions

```rust
pub struct NestedInstruction {
    pub metadata: InstructionMetadata,
    pub instruction: solana_instruction::Instruction,
    pub inner_instructions: NestedInstructions,
}

pub struct NestedInstructions(pub Vec<NestedInstruction>);
```

`NestedInstructions` dereferences to `&[NestedInstruction]`, so you can index it, call `.iter()` and `.len()`, and loop over it. In an instruction processor, `input.nested_instructions` holds the direct children of the current instruction; each child's `inner_instructions` holds its own children. The children are raw instructions: decode them with any decoder by calling `decode_instruction(&child.instruction)`.

An instruction pipe runs its processor for every instruction its decoder recognizes, at any depth. A CPI therefore reaches you twice: once as its own processor call and once as a child of its caller. Pick one view when you count things, or you'll count CPIs twice.

## Event CPI instructions

Anchor's `emit_cpi!` publishes an event by having the program call itself with the event as instruction data. Generated decoders decode these self-calls as a `CpiEvent` variant, such as `MeteoraDlmmInstruction::CpiEvent { data: CpiEvent::Swap(event), .. }`. Because the event is a child of the instruction that emitted it, you can pair them: look for the `CpiEvent` among a swap's `nested_instructions` to get the swap's actual output amount.

## Log events

Anchor's `emit!` publishes an event as a `Program data: <base64>` line in the transaction's log. `InstructionMetadata::decode_log_events` returns the events logged by one instruction:

```rust
pub fn decode_log_events<T: CarbonDeserialize>(&self) -> Vec<T>
```

It follows the `Program … invoke [n]` and `Program … success` lines to find the log lines that belong to this instruction's `absolute_path` (excluding its CPIs, and accounting for precompile instructions, which don't log), base64-decodes them, and keeps those that `T::deserialize` accepts. It returns an empty list if the transaction has no log messages, and it can't recover events from logs the runtime truncated.

`T` must implement [`CarbonDeserialize`](https://carbon.sevenlabs.org/concepts/decoders.md#carbondeserialize). Generated event types don't, so wrap them. This newtype decodes Meteora DLMM's `SwapEvent` from log lines; its `deserialize` calls the generated `decode`, which checks the discriminator:

```rust
use {
    borsh::BorshDeserialize,
    carbon_core::deserialize::CarbonDeserialize,
    carbon_meteora_dlmm_decoder::events::swap::SwapEvent,
};

/// A `SwapEvent` emitted with Anchor's `emit!` (a `Program data:` log line).
#[derive(Debug, BorshDeserialize)]
pub struct LoggedSwap(pub SwapEvent);

impl CarbonDeserialize for LoggedSwap {
    const DISCRIMINATOR: &'static [u8] = &[81, 108, 227, 190, 205, 208, 10, 196];

    fn deserialize(data: &[u8]) -> Option<Self> {
        SwapEvent::decode(data).map(LoggedSwap)
    }
}
```

Then, in an instruction processor:

```rust
for LoggedSwap(event) in input.metadata.decode_log_events::<LoggedSwap>() {
    println!("logged swap: amount_out={}", event.amount_out);
}
```

Meteora DLMM itself emits through event CPI, so in practice you use this pattern for programs that log their events, such as Raydium CLMM.

## Example

This program crawls Meteora DLMM transactions from the public RPC endpoint. For each swap it prints the swap's position, then finds the swap's event CPI among its children and prints the output amount and fee from it.

```sh
cargo add carbon-core@2.0.0 carbon-meteora-dlmm-decoder@2.0.0 carbon-rpc-transaction-crawler-datasource@2.0.0
cargo add tokio --features full
cargo add env_logger
```

`src/main.rs`:

```rust
use {
    carbon_core::{
        error::CarbonResult,
        instruction::{InstructionDecoder, InstructionProcessorInputType},
        pipeline::Pipeline,
        processor::Processor,
    },
    carbon_meteora_dlmm_decoder::{
        instructions::{cpi_event::CpiEvent, MeteoraDlmmInstruction},
        MeteoraDlmmDecoder, PROGRAM_ID,
    },
    carbon_rpc_transaction_crawler_datasource::{ConnectionConfig, Filters, RpcTransactionCrawler},
};

struct SwapTree;

impl Processor<InstructionProcessorInputType<'_, MeteoraDlmmInstruction>> for SwapTree {
    async fn process(
        &mut self,
        input: &InstructionProcessorInputType<'_, MeteoraDlmmInstruction>,
    ) -> CarbonResult<()> {
        let MeteoraDlmmInstruction::Swap { data, .. } = input.decoded_instruction else {
            return Ok(());
        };
        let meta = input.metadata;
        println!(
            "swap {} path={:?} stack_height={} amount_in={}",
            meta.transaction_metadata.signature,
            meta.absolute_path,
            meta.stack_height,
            data.amount_in
        );

        // Children of this instruction: token transfers and the program's event CPI.
        for child in input.nested_instructions.iter() {
            if let Some(MeteoraDlmmInstruction::CpiEvent {
                data: CpiEvent::Swap(event),
                ..
            }) = MeteoraDlmmDecoder.decode_instruction(&child.instruction)
            {
                println!(
                    "  event path={:?} amount_out={} fee={}",
                    child.metadata.absolute_path, event.amount_out, event.fee
                );
            }
        }
        Ok(())
    }
}

#[tokio::main]
async fn main() -> CarbonResult<()> {
    env_logger::init();

    let crawler = RpcTransactionCrawler::new(
        "https://api.mainnet-beta.solana.com".to_string(),
        PROGRAM_ID,
        ConnectionConfig::default(),
        Filters::new(None, None, None),
        None,
    );

    Pipeline::builder()
        .datasource(crawler)
        .instruction(MeteoraDlmmDecoder, SwapTree)
        .build()?
        .run()
        .await
}
```

```sh
cargo run
```

Output:

```text
swap 4qSjUGPcKNNcEUxcK2uaTUYKoTrUBYCYEFLbZt8yqiWxrH7Z2E8nXCj4zMQjJ1TxNYztjWC83qg2WXKhSjUTc1gw path=[3, 0, 2] stack_height=3 amount_in=1679907
  event path=[3, 0, 2, 2] amount_out=19337053 fee=233
swap 24cQfJCNzD67i83gUk4jV8Z6sNz9TrPXZV8kdZjKK3sNeUHLXAxddb3JmKoETAEgsFrpJjWzyqg5qr2fuj8nzZXc path=[0, 0] stack_height=2 amount_in=4254885455
  event path=[0, 0, 2] amount_out=11300348 fee=65622
swap 4pr7pw96PGJSiXYcGT3Vman8v1rgbjEn8XASQmkdURRayQHsJZut88G9mwyzYhWH1XksEtBAdALj5dD13VYoRgM5 path=[2, 3] stack_height=2 amount_in=854429039
  event path=[2, 3, 2] amount_out=318436120 fee=349280
…
```

Most DLMM swaps arrive through an aggregator, so their paths have two or more levels. The public endpoint is rate limited; output can pause for a minute or more, and you may see `429` warnings in the log. Press `Ctrl+C` to stop.

## Next steps

- [Transactions](https://carbon.sevenlabs.org/concepts/transactions.md): See every instruction of a transaction in one processor.
- [Filters](https://carbon.sevenlabs.org/concepts/filters.md): Deduplicate instructions and restrict them by slot or source.
- [Processor inputs reference](https://carbon.sevenlabs.org/reference/processor-inputs.md): Every field of InstructionMetadata and the input types.

---

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