1. Resources

Examples

Every runnable example in the Carbon 2.0.0 repository, what it shows, what it needs, the command to run it and the output to expect.

On this page

The Carbon repository ships nine example pipelines in examples/. Each one is a small, complete indexer for one pattern. Use this page to pick one, check what it needs, and run it from a checkout of the v2.0.0 tag.

ExampleShowsDatasourceDecoderNeeds
versioned-decodersTwo decoder versions routed by slotMock, in the exampleTwo generated simple-dex decodersNothing
gpa-rpcOne-shot load of a program's accountsGpaDatasourcecarbon-marginfi-v2-decoderPublic RPC works
transaction-crawler-rpcBackfill a program's transaction historyRpcTransactionCrawlercarbon-meteora-dlmm-decoderPublic RPC works, slowly
custom-datasourceWriting your own DatasourceHttpPollDatasource, in the examplecarbon-jupiter-swap-decoderPublic RPC works, slowly
block-subscribe-rpcReal-time blocks over WebSocketRpcBlockSubscribecarbon-raydium-clmm-decoderAn RPC provider with blockSubscribe
yellowstone-grpcReal-time transactions over Geyser gRPCYellowstoneGrpcGeyserClient or LaserStreamGeyserClientcarbon-jupiter-swap-decoderA Yellowstone or LaserStream endpoint
postgres-graphqlPersist accounts to Postgres, serve them with GraphQLYellowstoneGrpcGeyserClientcarbon-token-program-decoderPostgres and a Yellowstone endpoint
snapshot-validatorLoad accounts from a validator snapshotSnapshotDatasourceA pass-through decoder in the examplelibclang, a snapshot, lots of disk
jetstreamerArchive backfill over Old FaithfulJetstreamerDatasource (Carbon 1)carbon-token-2022-decoderA Carbon 1 project; doesn't build in the 2.0 workspace

Run an example#

Every example except jetstreamer is a member of the Carbon workspace, so you build it from a checkout of the repository. You need Rust 1.96.1 (the checkout's rust-toolchain.toml pins it, and rustup installs it on first use) and a few GB of disk for the first build.

Terminal
git clone --branch v2.0.0 https://github.com/sevenlabs-hq/carbon.git
cd carbon/examples/gpa-rpc
RPC_URL=https://api.mainnet-beta.solana.com RUST_LOG=info cargo run --release -p gpa-rpc-carbon-example

Git prints a "detached HEAD" note after the clone; that's expected for a tag checkout. The first build compiles the Solana dependencies and takes a few minutes; later runs start in seconds.

Two things apply to every example:

  • Logging. Examples print through log and env_logger, so nothing appears unless RUST_LOG is set. RUST_LOG=info shows decoded items and the LogMetrics summary every five seconds; RUST_LOG=debug also shows the variants the processors log at debug level.
  • .env files. Each example calls dotenv::dotenv(), which reads a .env file from the current directory or one of its parents. Put the file in the example's directory and run cargo run from there. Running from the repository root doesn't pick up examples/<name>/.env. Setting the variables inline, as in the commands on this page, works from anywhere.

versioned-decoders#

Indexes a program that was upgraded with a breaking instruction layout change. A fictional DEX adds a fee_tier: u8 field to Swap at slot 500, transaction index 10. The example registers two generated decoders, carbon-simple-dex-decoder-v1 and -v2 (in decoder-v1/ and decoder-v2/, generated from the IDLs in idls/), each with instruction_with_filters and a SlotRangeFilter so every instruction reaches only the version that was live at its slot. One processor implements Processor for both instruction types and normalizes them to a shared struct.

The datasource is a mock inside src/main.rs that sends four synthetic transactions and returns, so the pipeline exits on its own. The pattern is explained in Versioned decoders.

Needs: nothing. No environment variables, no network.

Terminal
cd carbon/examples/versioned-decoders
RUST_LOG=info cargo run --release -p versioned-decoders-carbon-example
Output
[2026-11-08T23:48:10Z INFO  versioned_decoders_carbon_example] === Versioned Decoders Example ===
[2026-11-08T23:48:10Z INFO  versioned_decoders_carbon_example] Program upgrade at slot=500, tx_index=10
[2026-11-08T23:48:10Z INFO  carbon_core::pipeline] starting pipeline. num_datasources: 1, num_exporters: 0, num_account_pipes: 0, num_account_deletion_pipes: 0, num_instruction_pipes: 2, num_transaction_pipes: 0
[2026-11-08T23:48:10Z INFO  versioned_decoders_carbon_example] [V1] Swap | slot=499 tx_idx=Some(0) | amount_in=1000000 min_amount_out=990000
[2026-11-08T23:48:10Z INFO  versioned_decoders_carbon_example] [V1] Swap | slot=500 tx_idx=Some(9) | amount_in=2000000 min_amount_out=1980000
[2026-11-08T23:48:10Z INFO  versioned_decoders_carbon_example] [V2] Swap | slot=500 tx_idx=Some(10) | amount_in=3000000 min_amount_out=2970000 fee_tier=5
[2026-11-08T23:48:10Z INFO  versioned_decoders_carbon_example] [V2] Swap | slot=501 tx_idx=Some(0) | amount_in=4000000 min_amount_out=3960000 fee_tier=10
[2026-11-08T23:48:10Z INFO  carbon_core::pipeline] update_receiver closed, shutting down.
[2026-11-08T23:48:10Z INFO  carbon_core::pipeline] pipeline shutdown complete.
[2026-11-08T23:48:10Z INFO  versioned_decoders_carbon_example] Done.

The filters do the routing. The decoders alone can't tell the versions apart reliably: generated decoders ignore trailing bytes, so the V1 decoder also accepts V2 data and silently drops fee_tier.

gpa-rpc#

Loads every account owned by the Marginfi v2 program once, with a single getProgramAccounts call, decodes each into MarginfiV2Account, and exits when the datasource has sent everything. It logs banks and groups at info and user accounts at debug. src/variants.rs also has a Helius gPA v2 variant (paginated, with changed_since_slot for incremental syncs); switch by editing the let datasource = … line in main.rs. See Load account state.

Needs:

VariableRequiredUsed by
RPC_URLYes, for the default RPC variantGpaDatasource. The public endpoint works for Marginfi's size.
HELIUS_RPC_URLOnly for the Helius variantHeliusGpaV2Datasource. A Helius RPC URL with the API key in it.
Terminal
cd carbon/examples/gpa-rpc
RPC_URL=https://api.mainnet-beta.solana.com RUST_LOG=info cargo run --release -p gpa-rpc-carbon-example

Against the public endpoint the run took about 12 seconds and processed 171,340 accounts. The end of the output:

Output
[2026-11-08T23:48:31Z INFO  gpa_rpc_carbon_example] bank pubkey=J5mxC3hLXqmsJB4Nauf9YrtbNMSTHGz7FWfAmy1v6Fzr mint=So11111111111111111111111111111111111111112 group=4qp6Fx6tnZkY5Wropq9wUYgtFxXKwE6viZxFHg3rdAG8
[2026-11-08T23:48:31Z INFO  gpa_rpc_carbon_example] group pubkey=J9VZnaMGTELGCPsqMxk8aoyEGYcVzhorj48HvtDdEtc8 admin=J525txDGqSuPisS3NQwySwDEDExVhjJNUfx94PGQUNJR
[2026-11-08T23:48:31Z INFO  carbon_core::pipeline] update_receiver closed, shutting down.
[2026-11-08T23:48:31Z INFO  carbon_log_metrics] 00:00:11 (+1.82520643s) | 171340 processed (100%), 171340 successful, 0 failed (0%), 0 in queue
[2026-11-08T23:48:31Z INFO  carbon_log_metrics]   carbon_account_updates_processed_total: 171340
[2026-11-08T23:48:31Z INFO  carbon_log_metrics]   rpc_gpa_accounts_processed_total: 171340
[2026-11-08T23:48:31Z INFO  carbon_log_metrics]   rpc_gpa_fetch_duration_milliseconds: count=1
[2026-11-08T23:48:31Z INFO  carbon_core::pipeline] pipeline shutdown complete.
[2026-11-08T23:48:31Z INFO  gpa_rpc_carbon_example] snapshot complete

transaction-crawler-rpc#

Backfills Meteora DLMM activity by walking the program's signatures backwards from the newest (getSignaturesForAddress), fetching each transaction (getTransaction), and decoding it. It logs Swap, AddLiquidity and RemoveLiquidity at info and every other instruction at debug. After it reaches the oldest signature it can see, it keeps polling for new ones every five seconds. src/variants.rs also has a Helius GTFA variant that pages through a fixed slot window and then exits. See Backfill transactions.

Needs:

VariableRequiredUsed by
RPC_URLYes, for the default RPC variantRpcTransactionCrawler. The public endpoint works but is heavily rate limited.
UNTIL_SIGNATURENoStops the backward walk at this signature. The crawler still keeps polling for new transactions afterwards, so the pipeline doesn't exit.
HELIUS_RPC_URLOnly for the GTFA variantHeliusGtfaDatasource.
Terminal
cd carbon/examples/transaction-crawler-rpc
RPC_URL=https://api.mainnet-beta.solana.com RUST_LOG=info cargo run --release -p transaction-crawler-rpc-carbon-example

On the public endpoint, expect a handful of swaps per minute, with pauses. This excerpt is from a 60-second run that processed 17 transactions:

Output
[2026-11-08T23:48:38Z ERROR carbon_rpc_transaction_crawler_datasource] Failed to fetch signatures after 0 retries: Error { request: Some(GetSignaturesForAddress), kind: Reqwest(reqwest::Error { kind: Status(413, Some(ReasonPhrase(b"Request Entity Too Large"))), url: "https://api.mainnet-beta.solana.com/" }) }
[2026-11-08T23:48:42Z INFO  transaction_crawler_rpc_carbon_example] swap slot=467105207 sig=5FVRVLgohHkAMTsqb5M5vCKJ8xpWGuQddfMBtdaifGFeDtnHQ3TFDSBdZAsUFezqimgh2i2RB94c3qtchJScyvWz data=Swap { amount_in: 367843, min_amount_out: 0 }
[2026-11-08T23:48:42Z INFO  transaction_crawler_rpc_carbon_example] swap slot=467105207 sig=2cFZ9cJkYpQGtnyLi9TMDTFErAkxktDRdwnjiHwz2BAEYLTFLYGzGx86mmPpMbSuarJxZeeFR4BXKVUWpPiMZKfC data=Swap { amount_in: 91821913, min_amount_out: 7958851 }
[2026-11-08T23:48:46Z INFO  carbon_log_metrics] 00:00:10 (+5.00022008s) | 8 processed (100%), 8 successful, 0 failed (0%), 0 in queue
…
[2026-11-08T23:49:36Z INFO  carbon_log_metrics]   transaction_crawler_signatures_fetched_total: 1000
[2026-11-08T23:49:36Z INFO  carbon_log_metrics]   transaction_crawler_transactions_fetched_total: 26

The example sets RetryConfig::no_retry(), so a rejected getSignaturesForAddress call is logged once and retried on the next poll, as in the first line above. The crawler fetches a thousand signatures quickly and then waits on getTransaction, which the public endpoint limits hard: it answers HTTP 429 with retry-after: 10, and the Solana RPC client sleeps without logging anything at info. See Crawler prints nothing for seconds at a time.

custom-datasource#

A complete hand-written Datasource, HttpPollDatasource, that polls getSignaturesForAddress for the Jupiter v6 program every five seconds, fetches each transaction with getTransaction in Base64, converts the metadata with transaction_metadata_from_original_meta, and sends Update::Transactions. It shows the parts a real source needs: cancellation checks at every sleep, a watermark (until) so it doesn't re-send old transactions, backoff on failed calls, skipping failed transactions, and two custom Counter metrics registered in the global registry. The processor logs the discriminant of each decoded JupiterSwapInstruction.

Needs:

VariableRequiredUsed by
RPC_URLYesHttpPollDatasource. The public endpoint works, slowly.
Terminal
cd carbon/examples/custom-datasource
RPC_URL=https://api.mainnet-beta.solana.com RUST_LOG=info cargo run --release -p custom-datasource-carbon-example
Output
[2026-11-08T23:50:51Z INFO  custom_datasource_carbon_example] ix slot=467105803 sig=2r4uo4VNLHHy9KZQi4KjCFqQM4ugBJgc5tQgMS1RGjKrMzsfYoAFG9AkiGdcRL24YpY9zUziRMXMhmMZuDmhXXcx variant=Discriminant(11)
[2026-11-08T23:50:51Z INFO  custom_datasource_carbon_example] ix slot=467105803 sig=2r4uo4VNLHHy9KZQi4KjCFqQM4ugBJgc5tQgMS1RGjKrMzsfYoAFG9AkiGdcRL24YpY9zUziRMXMhmMZuDmhXXcx variant=Discriminant(9)
[2026-11-08T23:50:51Z INFO  custom_datasource_carbon_example] ix slot=467105803 sig=2r4uo4VNLHHy9KZQi4KjCFqQM4ugBJgc5tQgMS1RGjKrMzsfYoAFG9AkiGdcRL24YpY9zUziRMXMhmMZuDmhXXcx variant=Discriminant(17)
[2026-11-08T23:50:55Z INFO  carbon_log_metrics] 00:00:05 (+4.999159689s) | 2 processed (100%), 2 successful, 0 failed (0%), 0 in queue
…
[2026-11-08T23:51:49Z INFO  carbon_log_metrics]   custom_http_poll_transactions_fetched_total: 12
[2026-11-08T23:51:49Z INFO  carbon_log_metrics]   custom_http_poll_fetch_failures_total: 0

One transaction produces several lines because the processor runs for every Jupiter instruction in it, including inner instructions and the events Jupiter emits through self-CPI (nested instructions). It runs until you press Ctrl-C; the example uses ShutdownStrategy::Immediate.

block-subscribe-rpc#

Subscribes to confirmed blocks that mention the Raydium CLMM program over the RPC WebSocket method blockSubscribe, decodes Raydium CLMM instructions (Swap, SwapV2 and OpenPosition at info), and logs every block's header with a block_details processor. The RpcBlockSubscribeConfig it passes asks for Base64 encoding, full transaction details and max_supported_transaction_version: Some(1), which Carbon 2 requires. See Stream with RPC.

Needs: an RPC provider that enables blockSubscribe. Solana's public endpoint doesn't.

VariableRequiredUsed by
RPC_WS_URLIn practice, yesRpcBlockSubscribe. A wss:// URL. Falls back to wss://api.mainnet-beta.solana.com, which rejects the method.
Terminal
cd carbon/examples/block-subscribe-rpc
RPC_WS_URL="wss://your-provider.example/?api-key=…" RUST_LOG=info cargo run --release -p block-subscribe-rpc-carbon-example

With a provider that supports the method, block slot=… hash=… parent=… time=… lines interleave with decoded swap, swap_v2 and open_position lines. Without RPC_WS_URL, the run against the public endpoint looks like this and the process exits after about 40 seconds:

Output
[2026-11-08T23:51:57Z ERROR carbon_rpc_block_subscribe_datasource] Failed to subscribe to block updates: SubscribeFailed { reason: "Method not found (-32601)", message: "{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32601,\"message\":\"Method not found\"},\"id\":1}" }
…
[2026-11-08T23:52:37Z ERROR carbon_core::pipeline] error consuming datasource: Custom("Failed to subscribe after 10 attempts: subscribe failed: Method not found (-32601)")
[2026-11-08T23:52:37Z INFO  carbon_core::pipeline] update_receiver closed, shutting down.
[2026-11-08T23:52:37Z INFO  carbon_core::pipeline] pipeline shutdown complete.

yellowstone-grpc#

The standard real-time setup: a Yellowstone Geyser gRPC subscription to confirmed, successful transactions that include the Jupiter v6 program (account_required, vote: false, failed: false), decoded with JupiterSwapDecoder. It logs Route and SharedAccountsRoute at info. src/variants.rs has a Helius LaserStream variant built with LaserStreamClientConfig and replay enabled. main installs the aws_lc_rs rustls crypto provider before connecting. See Stream with Yellowstone gRPC and Stream with Helius LaserStream.

Needs: a Yellowstone gRPC endpoint (Triton, Helius, QuickNode, a self-hosted Geyser plugin) or a Helius LaserStream plan.

VariableRequiredUsed by
GEYSER_URLYes, for the default variantYellowstoneGrpcGeyserClient. Include the scheme and port, e.g. https://…:443.
X_TOKENIf your provider requires itSent as the x-token header.
LASERSTREAM_URLOnly for the LaserStream variantLaserStreamGeyserClient.
API_KEYOnly for the LaserStream variantYour Helius API key.
Terminal
cd carbon/examples/yellowstone-grpc
GEYSER_URL=https://your-yellowstone-endpoint:443 X_TOKEN=your-x-token RUST_LOG=info cargo run --release -p yellowstone-grpc-carbon-example

The example compiles at v2.0.0; it wasn't run for this page because it needs a paid endpoint. With a working endpoint, route slot=… sig=… data=Route { … } and shared_accounts_route … lines stream continuously between LogMetrics summaries until you press Ctrl-C. If GEYSER_URL is missing the example panics with GEYSER_URL must be set: NotPresent.

postgres-graphql#

Streams SPL Token account updates from Yellowstone, stores each decoded account as JSON in Postgres with PostgresJsonAccountProcessor<TokenProgramAccount> (Carbon's postgres feature), and serves a small GraphQL API over the same table with Carbon's graphql feature: a mints(limit) query and a mint(pubkey) lookup. The migration in migrations/001_init.sql creates the accounts table (__pubkey BYTEA, __slot BIGINT, data JSONB) and runs at startup. The GraphQL server starts before the pipeline, on /graphql, with a GraphiQL playground on /graphiql. See Postgres and GraphQL.

Needs: Postgres and a Yellowstone endpoint. The subscription covers every account owned by the Token program, which is a high-volume stream.

VariableRequiredUsed by
DATABASE_URLYessqlx connection pool.
GEYSER_URLYesYellowstoneGrpcGeyserClient.
X_TOKENIf your provider requires itYellowstone x-token.
BIND_ADDRNoGraphQL server address. Default 0.0.0.0:8080.

The bundled compose.yaml starts Postgres 16 with user, password and database carbon / carbon / carbon_example, published on host port 5433. The example's README uses port 5432 in DATABASE_URL; with the bundled database, use 5433:

Terminal
cd carbon/examples/postgres-graphql
docker compose up -d
DATABASE_URL=postgres://carbon:carbon@localhost:5433/carbon_example \
GEYSER_URL=https://your-yellowstone-endpoint:443 X_TOKEN=your-x-token \
RUST_LOG=info cargo run --release -p postgres-graphql-carbon-example

The first log line is graphiql at http://0.0.0.0:8080/graphiql. Open http://localhost:8080/graphiql and run:

GraphQL
{
  mints(limit: 5) {
    pubkey
    slot
    decimals
    supply
  }
}

The example compiles at v2.0.0; it wasn't run end to end for this page (no Postgres or Geyser endpoint was available). Without a reachable database it fails at startup with:

Output
Error: Custom("failed to connect to Postgres: pool timed out while waiting for an open connection")

snapshot-validator#

Loads accounts from a Solana validator snapshot, either downloaded or already extracted on disk, filtered by owner program or account address. A pass-through AccountDecoder hands each raw Account to the processor, which logs pubkey, slot, lamports, data size and owner; an account_deletions processor logs deletions. It uses ShutdownStrategy::ProcessPending and exits when the snapshot has been read. See Load a snapshot.

Needs:

  • libclang at build time: carbon-validator-snapshot-datasource depends on solana-ledger, which builds RocksDB with bindgen. Install libclang-dev on Debian and Ubuntu or llvm with Homebrew. It's the only example that needs it.
  • Disk and memory for a snapshot. The current mainnet full snapshot was about 126 GB on 2026-11-09. The remote source downloads genesis.tar.bz2 and the snapshot into a ledger/ directory under the current directory.
VariableRequiredUsed by
VALIDATOR_URLOne of these twoBase URL of an RPC node that serves snapshots, e.g. https://api.mainnet-beta.solana.com. Carbon appends /genesis.tar.bz2 and /snapshot.tar.bz2.
SNAPSHOT_PATHOne of these twoAn extracted ledger directory containing genesis.tar.bz2 and snapshots/.
PROGRAM_OWNERSAt least one of these twoComma-separated owner program IDs.
ACCOUNT_IDSAt least one of these twoComma-separated account addresses.
Terminal
cd carbon/examples/snapshot-validator
VALIDATOR_URL=https://api.mainnet-beta.solana.com \
PROGRAM_OWNERS=TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA \
RUST_LOG=info cargo run --release -p snapshot-validator-carbon-example

The example compiles at v2.0.0 with libclang installed; a full mainnet load wasn't run for this page. Loaded accounts print as account pubkey=… slot=… lamports=… data_size=… owner=…, followed by snapshot bootstrap complete. That last line also prints when loading failed, because the pipeline returns Ok after a datasource error; check the log for error consuming datasource (see Pipeline exits with status 0 after a datasource error).

jetstreamer#

Backfills Token-2022 activity over a slot range from Old Faithful through the Jetstreamer datasource, with server-side transaction filters and parallel workers, then exits.

This is a Carbon 1 / Solana 3 example. Jetstreamer doesn't support the Solana 4 crates yet, so carbon-jetstreamer-datasource was not published on the 2.0 line (crates.io has 1.0.0 only), and the workspace Cargo.toml excludes examples/jetstreamer. It doesn't build from a v2.0.0 checkout: from the repository root, cargo run -p jetstreamer-carbon-example fails with package(s) `jetstreamer-carbon-example` not found in workspace, and from its own directory with error inheriting `edition` from workspace root manifest's `workspace.package.edition` . To use it, copy the pattern into a project that depends on Carbon 1.0.0 crates throughout. Don't combine it with Carbon 2 crates (why).

It reads START_SLOT (default 415500000), END_SLOT (415931999), WORKER_COUNT (4) and PAGE_SIZE (100), all optional.

Next steps#

Your endpoints

Code samples use these instead of the placeholders, so you can copy and run them as they are. They’re stored only in this browser and never sent anywhere.