Quickstart
Build an indexer that decodes live Meteora DLMM swaps from Solana mainnet in about five minutes. No account or API key needed.
On this page
- Check your Rust version
- Create a project
- Add Carbon
- Write the pipeline
- Run it
- What you built
- See the pipeline's metrics
- Use your own RPC endpoint
- Troubleshooting
- rustc 1.
95. 0 is not supported by the following packages - Nothing prints for a minute or more
- Failed to fetch transaction … after 3 retries in the log
- the trait InstructionDecoder is not implemented for a decoder
- Next steps
You'll build a small Rust program that reads a Solana program's transactions, decodes each instruction into a typed Rust struct, and prints swaps as they happen. It uses Solana's public RPC endpoint, so you don't need an account or an API key.
You need: Rust 1.96.1 or newer, an internet connection, and about 3 GB of free disk space for the first build.
Check your Rust version#
rustc --versionrustc 1.99.0 (b940084d7 2026-09-28)Any version from 1.96.1 up works. If yours is older, run rustup update stable. If you don't have Rust, install it from rustup.rs.
Create a project#
cargo new carbon-quickstart
cd carbon-quickstartAdd Carbon#
Add the core crate, a datasource, a decoder for the program you want to read, and a metrics exporter. All Carbon crates in one project must have the same version.
cargo add carbon-core@2.0.0 carbon-rpc-transaction-crawler-datasource@2.0.0 carbon-meteora-dlmm-decoder@2.0.0 carbon-log-metrics@2.0.0
cargo add tokio --features full
cargo add env_logger logYour [dependencies] now look like this (patch versions of tokio, env_logger and log may be newer):
[dependencies]
carbon-core = "2.0.0"
carbon-log-metrics = "2.0.0"
carbon-meteora-dlmm-decoder = "2.0.0"
carbon-rpc-transaction-crawler-datasource = "2.0.0"
env_logger = "0.11.11"
log = "0.4.35"
tokio = { version = "1.54.1", features = ["full"] }Write the pipeline#
Replace src/main.rs with this program. It connects three parts: a datasource that fetches the program's transactions, a decoder that turns Meteora DLMM instructions into Rust types, and a processor, SwapLogger, that prints them.
use {
carbon_core::{
error::CarbonResult, instruction::InstructionProcessorInputType, pipeline::Pipeline,
processor::Processor,
},
carbon_log_metrics::LogMetrics,
carbon_meteora_dlmm_decoder::{
instructions::MeteoraDlmmInstruction, MeteoraDlmmDecoder, PROGRAM_ID,
},
carbon_rpc_transaction_crawler_datasource::{ConnectionConfig, Filters, RpcTransactionCrawler},
std::sync::Arc,
};
#[tokio::main]
async fn main() -> CarbonResult<()> {
env_logger::init();
// Solana's public endpoint works for this example. Set RPC_URL to use your own.
let rpc_url = std::env::var("RPC_URL")
.unwrap_or_else(|_| "https://api.mainnet-beta.solana.com".to_string());
// Fetches the program's recent transactions, newest first, then keeps polling for new ones.
let datasource = RpcTransactionCrawler::new(
rpc_url,
PROGRAM_ID,
ConnectionConfig::default(),
Filters::new(None, None, None),
None,
);
Pipeline::builder()
.datasource(datasource)
.metrics(Arc::new(LogMetrics::new()))
.instruction(MeteoraDlmmDecoder, SwapLogger)
.build()?
.run()
.await
}
struct SwapLogger;
impl Processor<InstructionProcessorInputType<'_, MeteoraDlmmInstruction>> for SwapLogger {
async fn process(
&mut self,
input: &InstructionProcessorInputType<'_, MeteoraDlmmInstruction>,
) -> CarbonResult<()> {
let signature = input.metadata.transaction_metadata.signature;
match input.decoded_instruction {
MeteoraDlmmInstruction::Swap { data, accounts, .. } => println!(
"swap pool={} amount_in={} min_amount_out={}",
accounts.lb_pair, data.amount_in, data.min_amount_out,
),
MeteoraDlmmInstruction::Swap2 { data, accounts, .. } => println!(
"swap2 pool={} amount_in={} min_amount_out={}",
accounts.lb_pair, data.amount_in, data.min_amount_out,
),
// Every other instruction is decoded too; print its name.
other => println!("{} in {signature}", variant_name(other)),
}
Ok(())
}
}
/// The variant name of a decoded instruction, e.g. "AddLiquidityByStrategy2".
fn variant_name(instruction: &MeteoraDlmmInstruction) -> String {
format!("{instruction:?}").split([' ', '{', '(']).next().unwrap_or_default().to_string()
}Run it#
cargo runThe first build compiles the Solana dependencies and takes one to three minutes. Then decoded instructions start printing, newest transactions first. On Solana's public endpoint the first line can take a minute or more, because it rate-limits the crawler; see Nothing prints for a minute or more.
swap2 pool=6e7V9eegCHw997T72MxgwwJipZ6GJyZF8NvjkzT1rvpN amount_in=84225603 min_amount_out=0
CpiEvent in 5oc7rbKmGEti7jEfvCt77cWWTG8gUvQn5breYsMwpA396gn3FJAozKjUvKE6Nqma3SHZbKg4kXSi8G9pzSfTL8cA
CpiEvent in 5oc7rbKmGEti7jEfvCt77cWWTG8gUvQn5breYsMwpA396gn3FJAozKjUvKE6Nqma3SHZbKg4kXSi8G9pzSfTL8cA
swap pool=GMgh4NtWrGaUf1RR2kcXD7LY1jou1qFAuSsQeKp5ow4a amount_in=14968688 min_amount_out=0
swap2 pool=4ju8phDxUU4WSJP1wWPLZ8R2KRCqwP3VZFXAqhT9cfVy amount_in=56765035 min_amount_out=1
…CpiEvent lines are the events Meteora emits through self-CPI after each swap; Carbon decodes them as instructions too. Press Ctrl+C to stop.
What you built#
- The datasource,
RpcTransactionCrawler, asks the RPC node for the program's signatures (getSignaturesForAddress), fetches each transaction (getTransaction), skips failed ones, and sends the rest into the pipeline. It keeps polling every five seconds for new transactions. - The decoder,
MeteoraDlmmDecoderfromcarbon-meteora-dlmm-decoder, matches each instruction's discriminator and returns aMeteoraDlmmInstructionwith typeddataand namedaccounts. It decodes instructions nested inside other programs' calls too, such as a Jupiter route that swaps through a DLMM pool. - The processor,
SwapLogger, implementsProcessorfor the decoder's output. Carbon callsprocessonce per decoded instruction, with the instruction's metadata (signature, slot, position in the transaction). - The pipeline wires them together and runs until you stop it. See how a pipeline runs.
See the pipeline's metrics#
LogMetrics writes throughput and queue depth to the log every five seconds. Turn the log on with RUST_LOG:
RUST_LOG=info cargo runEvery five seconds you get a summary line followed by each metric's value. An excerpt:
[2026-11-09T00:02:47Z INFO carbon_log_metrics] 00:00:50 (+5.006587213s) | 7 processed (100%), 7 successful, 0 failed (0%), 0 in queue
[2026-11-09T00:02:47Z INFO carbon_log_metrics] carbon_updates_received_total: 7
[2026-11-09T00:02:47Z INFO carbon_log_metrics] transaction_crawler_signatures_fetched_total: 1000
[2026-11-09T00:02:47Z INFO carbon_log_metrics] transaction_crawler_transactions_fetched_total: 7
…The metrics reference lists every metric.
Use your own RPC endpoint#
The public endpoint is rate limited, so the example fetches only a few transactions per second. Any Solana RPC provider's URL works in its place:
RPC_URL="https://your-rpc-provider.example/?api-key=…" cargo runFor production, stream with Geyser gRPC instead of polling: see Stream with Yellowstone gRPC. The processor doesn't change.
Troubleshooting#
rustc 1. 95. 0 is not supported by the following packages#
error: rustc 1.95.0 is not supported by the following packages:
carbon-core@2.0.0 requires rustc 1.96.1Your Rust toolchain is older than Carbon 2.0 needs. Run rustup update stable, then cargo run again.
Nothing prints for a minute or more#
The public RPC endpoint limits how many requests you can make, and the crawler waits and retries when it hits the limit. It also skips failed transactions, so a burst of failed bot transactions prints nothing. To see whether it's working, run with metrics:
RUST_LOG=info cargo runIf transaction_ grows while transaction_ stays low, the crawler has found transactions and is being rate limited while it fetches them. Output follows; use your own endpoint with RPC_URL for steady throughput.
Failed to fetch transaction … after 3 retries in the log#
The RPC node kept rejecting requests, usually with HTTP 429 (Too Many Requests). The crawler logs the error, skips that transaction and continues. Lower the request rate with a custom ConnectionConfig, or use your own endpoint.
the trait InstructionDecoder is not implemented for a decoder#
A Carbon 1 crate is mixed with Carbon 2, so two versions of carbon-core are compiled; the compiler adds the note there are multiple different versions of crate carbon_. Make every carbon-* dependency 2.0.0, then run cargo update. See Installation.
More problems and fixes are in Troubleshooting.