# Carbon turns a Solana program IDL into a typed decoder and an indexer pipeline

> Carbon is a Rust framework for building Solana indexers and data pipelines, where datasources stream updates into a runtime, decoders turn raw account and transaction data into typed structures, and processors handle the decoded output in order. The distinguishing feature is that the decoders are generated from a program IDL, so the type you handle in a processor is derived from the on-chain interface rather than written by hand.

**sevenlabs-hq/carbon** — Carbon is an indexing framework on Solana.

- Repository: https://github.com/sevenlabs-hq/carbon
- Stars: 634 · Forks: 195
- Language: Rust
- License: MIT
- Published: 2026-10-21 · Updated: 2026-10-21 · Language: en
- Canonical page: https://hysenlabs.com/projects/sevenlabs-hq-carbon

## The pipeline shape: datasource, decoder, processor

Carbon is described as a Rust framework for building Solana indexers and data pipelines, and the README opens with the architecture in one sentence. It is built around a pipeline: datasources stream Solana updates into the runtime, decoders turn raw account and transaction data into typed Rust structures, and processors handle the decoded output in order to build data pipelines and indexers for your applications.

The explicit claim is that each part is modular, so you can swap RPC streams for Geyser streams, generated decoders for custom ones, or logging processors for Postgres, GraphQL, analytics, bots and other application-specific sinks.

That modularity is the argument for the framework's shape. A Solana indexer has three hard parts and they change for different reasons. The ingestion part changes when a provider changes its protocol or when you decide you need lower latency than public RPC allows. The decoding part changes when the program you are indexing changes its interface. The output part changes when your product needs a different destination. A framework that treats all three as swappable components means a provider migration does not touch your business logic.

The consequence is that the processor is where your code lives, and it is deliberately small. A processor implements a trait, receives a typed input, and returns a result. Everything above that, the stream plumbing, the decoding, the error handling and the metrics, is the framework's problem.

The repository is a large Rust workspace. The Cargo.toml lists the workspace members as crates, datasources, decoders, examples and metrics, and the top level of the repository adds a packages directory with a pnpm workspace for the code generation tooling, a scripts directory, and a rust-toolchain.toml.

## Generating a decoder from an IDL before writing any Rust

The first step in the quick start is not Rust at all. The README says to start with a decoder, using one of Carbon's existing program decoders, or generating one from your program IDL.

The command is a Node CLI, run through npx:

```sh
npx @sevenlabs-hq/carbon-cli parse \
  --idl ./idl.json \
  --out-dir ./my-program-decoder \
  --name my-program
```

The three arguments are the IDL file, the output directory for the generated crate, and the name. The capability list makes the same point in prose: generate decoder crates from Anchor or Codama IDLs.

This is the part that makes the framework different from a hand-written indexer. A typical hand-written Solana indexer parses account data with a deserialiser you maintain yourself, and when the program is upgraded you find out about the breaking change because the indexer stopped working. Here, the decoder crate is generated from the same IDL that describes the program, so the type your processor receives and the program's actual instruction set are derived from one artefact.

The CLI itself is a JavaScript package in a pnpm workspace, with the root package.json wiring turbo for build, dev, lint, test and clean, prettier for formatting, and TypeScript for type checking. The pnpm overrides pin a set of Codama packages to specific versions, which is the kind of detail that matters when the codegen output has to be reproducible.

The decoders directory in the repository holds the maintained ones. The README says the decoder crates cover widely used Solana programs across core SPL programs, DeFi, NFTs, infrastructure and more, and that they expose typed accounts, instructions, events, and optional serde, Postgres and GraphQL integrations.

## Wiring a datasource and a processor together

The quick start's middle step is the pipeline, and the code is short enough to read in full because the framework does the rest.

```rust
use carbon_core::{error::CarbonResult, pipeline::Pipeline};
use carbon_log_metrics::LogMetrics;
use carbon_my_program_decoder::{MyProgramDecoder, PROGRAM_ID};
use carbon_rpc_block_subscribe_datasource::{Filters, RpcBlockSubscribe};
use solana_client::rpc_config::{RpcBlockSubscribeConfig, RpcBlockSubscribeFilter};
use solana_transaction_status::{TransactionDetails, UiTransactionEncoding};

#[tokio::main]
async fn main() -> CarbonResult<()> {
    let datasource = RpcBlockSubscribe::new(
        "wss://api.mainnet-beta.solana.com".to_string(),
        Filters::new(
            RpcBlockSubscribeFilter::MentionsAccountOrProgram(PROGRAM_ID.to_string()),
            Some(RpcBlockSubscribeConfig {
                encoding: Some(UiTransactionEncoding::Base64),
                transaction_details: Some(TransactionDetails::Full),
                max_supported_transaction_version: Some(1),
                ..RpcBlockSubscribeConfig::default()
            }),
        ),
    );

    Pipeline::builder()
        .datasource(datasource)
        .metrics(std::sync::Arc::new(LogMetrics::new()))
        .instruction(MyProgramDecoder, MyProcessor)
        .build()?
        .run()
        .await
}
```

A few things are worth noting in that builder. The datasource is a value you construct, and the filter is a mention of the program's account or program ID, so the stream only carries what you care about. The metrics slot takes an Arc-wrapped LogMetrics, so the metrics backend is a choice rather than a global. And the instruction method takes the decoder and the processor together, which is the pairing that defines what the pipeline does.

The processor itself is a trait implementation over a typed input:

```rust
use carbon_core::{
    error::CarbonResult,
    instruction::InstructionProcessorInputType,
    processor::Processor,
};
use carbon_my_program_decoder::instructions::MyProgramInstruction;

struct MyProcessor;

impl Processor<InstructionProcessorInputType<'_, MyProgramInstruction>>
    for MyProcessor
{
    async fn process(
        &mut self,
        input: &InstructionProcessorInputType<'_, MyProgramInstruction>,
    ) -> CarbonResult<()> {
        log::info!("instruction: {:?}", input.decoded_instruction);
        Ok(())
    }
}
```

The type parameter ties the processor to one decoder's instruction type, and the async signature means a processor can await inside its handler without blocking the pipeline. The README's closing note is the summary of the whole design: the same pattern works with any decoder and any datasource.

## Datasources, and the versioning split inside the workspace

The datasource crates cover the common ways Solana data is consumed, and the list is really a list of latency and backfill trade-offs.

Geyser streams are for low-latency production indexers. Solana RPC is for simple setups and public RPC compatibility. Historical and snapshot sources are for backfills, range replay and loading current account state. Hosted provider APIs, with Helius given as the example, cover provider-specific streaming and historical access. And adapter datasources are for bringing your own message stream.

Those five categories map onto the capability list, which says you can build real-time indexers, historical backfills, snapshot loaders and hybrid data pipelines with the same processor model. That last one is the useful claim. A processor written against a typed instruction does not care whether the bytes came from a live subscription, a replayed range or a snapshot, which is what makes the hybrid mode possible.

The Cargo.toml reveals something that matters for adoption. Three workspace members are excluded, the jetstreamer-datasource, the jito-shredstream-grpc-datasource and the jetstreamer example, with a comment saying these providers remain on their existing Solana v3 stacks and are intentionally outside the v4 migration scope. The workspace version is 2.0.0, and the excluded datasources are pinned at 1.0.0 with a comment saying the legacy providers remain on Carbon 1.x until they migrate to the Solana v4 stack.

So the framework is mid-migration, and a project that needs Jetstreamer or Jito shredstream is on the 1.x line while everything else moves to 2.0.0. That is a real thing to check before you commit to the version of the framework you depend on, and the release tags tell the same story: v2.0.0 in September 2026, v1.0.0 in June 2026, and v0.12.0 before that.

The capability list also covers what the pipeline can carry: transactions, account updates, account deletions and block metadata, with instructions, nested CPIs, accounts and emitted events decoded into typed Rust structures.

## Sinks, metrics and the runnable examples

The output side of the pipeline is where a framework either earns its keep or adds ceremony, and Carbon's version is a set of processors plus two metrics backends.

The capability list says you can persist decoded data with typed Postgres rows or generic JSONB processors, and expose indexed data through GraphQL using the built-in Juniper and Axum helpers. Metrics can be exported through logs or Prometheus, and the metrics workspace holds carbon-log-metrics and carbon-prometheus-metrics as separate crates, so the choice is a dependency rather than a configuration flag.

The examples directory is the most informative part of the repository, because the list of directory names is a list of use cases: block-subscribe-rpc, custom-datasource, gpa-rpc, jetstreamer, postgres-graphql, snapshot-validator, transaction-crawler-rpc, versioned-decoders and yellowstone-grpc. That is a live Geyser subscription, a public RPC subscription, a backfill crawler, a validator snapshot loader, a Postgres-backed GraphQL API, a bring-your-own datasource, a versioned decoder set and a second gRPC provider, all built on the same processor model.

Running one is a cargo invocation from the repository root, and each example is a workspace crate:

```sh
cargo run --release -p block-subscribe-rpc-carbon-example
```

The versioned-decoders example is worth a second look for anyone with a long-lived indexer, because it addresses the problem that motivates decoders in the first place: a program that changes its instruction set, and an indexer that has to serve several versions of it at once.

The workspace also carries the usual Rust hygiene files, clippy.toml, rustfmt.toml, taplo.toml for TOML formatting, a .prettierrc and a .pre-commit.sh, and the JavaScript side is formatted with prettier through a pnpm workspace with turbo.

## Where Carbon is the wrong tool

Three limits, and the first is the most common mistake in adopting an indexer framework.

The first is that Carbon is for Solana programs, not for Solana in general. The whole framework is shaped around decoding program instructions, accounts and events into typed Rust structures, and around the account and instruction model of an on-chain program. If your job is to watch a token account balance, or to index transfers without a program in mind, you want a different tool, because the decoder generation workflow only makes sense when there is an IDL for the program you are watching.

The second is the version split inside the workspace, already mentioned, and it is more than a packaging detail. If your data source is Jetstreamer or Jito shredstream, you are on Carbon 1.x while the rest of the ecosystem moves to 2.0.0, and a framework with two live major versions is a framework whose abstractions you have to check twice. The alternative approach in that case is to use the provider's own client and decode the data yourself, which for a single provider is often less work than reconciling versions.

The third is that a generated decoder is only as good as the IDL. If the program you are indexing has no IDL, or has one that is out of date relative to the deployed program, the generated types will not match what the chain is actually doing, and the failure is a type mismatch at runtime rather than a compile error. The README does not discuss IDL provenance or staleness, so that is a question to ask of the program maintainers rather than of the framework.

The other alternative worth naming is not a Rust framework at all but a hosted indexing service, where the provider runs the pipeline and you query the result. The difference in approach is that Carbon moves the ingestion, decoding and storage work onto your infrastructure and your process, while a hosted service removes them entirely in exchange for a dependency, a cost and a query interface you do not control. For a team that has to own its data, that trade is the whole point of the framework, and for a team that just needs an index to read, it is a lot of Rust to write and operate.

## Conclusion

Adopt Carbon if you are indexing Solana programs and want typed decoding generated from an Anchor or Codama IDL, because the type in your processor comes from the on-chain interface and the processor model is the same for real-time streaming, backfills and snapshot loading. Do not adopt it if your workload is not a Solana indexer, since the whole framework is shaped around program events and instruction decoding, and do not assume every provider is on the same version, because the workspace manifest excludes the Jetstreamer and Jito shredstream datasources with a comment saying they remain on their existing stacks. Verify first that the datasource you need is on the version you are targeting, that the program you care about has an IDL you can hand to carbon-cli parse, that the processor you write matches the decoder's typed instruction shape, and that you can live with the Rust toolchain the workspace pins, since the manifest sets a specific rust-version and a rust-toolchain.toml is checked in.

## FAQ

### What is Carbon and what does it do for Solana indexers?

Carbon is a Rust framework for building Solana indexers and data pipelines. It is built around a pipeline where datasources stream Solana updates into the runtime, decoders turn raw account and transaction data into typed Rust structures, and processors handle the decoded output in order, and each part is modular so RPC streams can be swapped for Geyser streams and logging processors for Postgres or GraphQL sinks.

### How do I generate a decoder for my Solana program?

Run npx @sevenlabs-hq/carbon-cli parse with --idl pointing at your IDL file, --out-dir for the generated crate and --name for the crate name. Decoders can be generated from Anchor or Codama IDLs, and the capabilities list also covers decoding instructions, nested CPIs, accounts and emitted events into typed Rust structures.

### What datasources does Carbon support?

Geyser streams for low-latency production indexers, Solana RPC for simple setups and public RPC compatibility, historical and snapshot sources for backfills and range replay, hosted provider APIs such as Helius, and adapter datasources for bringing your own message stream.

### Can I write a processor that handles a specific instruction type?

Yes. A processor implements the Processor trait for an InstructionProcessorInputType bound to the decoder's instruction type, with an async process method that receives the typed input, so the handler is written against a type generated from your program IDL rather than against raw account bytes.

### Where do I persist and serve the indexed data in Carbon?

With processors: the capability list covers typed Postgres rows or generic JSONB processors for persistence, and exposing indexed data through GraphQL using the built-in Juniper and Axum helpers. Metrics can be exported through logs or through the carbon-prometheus-metrics crate, and the examples directory includes a postgres-graphql example.

## Sources

- [Issues](https://github.com/sevenlabs-hq/carbon/issues)
- [License: MIT](https://github.com/sevenlabs-hq/carbon/blob/main/LICENSE)
- [README](https://github.com/sevenlabs-hq/carbon/blob/main/README.md)
- [Releases](https://github.com/sevenlabs-hq/carbon/releases)
- [sevenlabs-hq/carbon on GitHub](https://github.com/sevenlabs-hq/carbon)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/sevenlabs-hq-carbon
