1. Resources

Contributing

Set up the Carbon repository, run the same checks as CI, add a decoder or a datasource, and open a pull request. Also how to improve these docs.

On this page

Carbon is MIT-licensed and developed in the open at sevenlabs-hq/carbon. This page covers working on the repository itself: setting it up, the checks a pull request must pass, and how to add a decoder or a datasource. It follows CONTRIBUTING.md at v2.0.0, with the commands checked against that tag.

Set up the repository#

You need:

  • Rust 1.96.1. The repository's rust-toolchain.toml pins it, so rustup installs and selects it the first time you run cargo in the checkout. Published crates declare the same MSRV in the workspace Cargo.toml and clippy.toml.
  • libclang, for carbon-validator-snapshot-datasource (RocksDB is built with bindgen). sudo apt-get install libclang-dev on Debian and Ubuntu, brew install llvm on macOS. Without it, building the whole workspace fails in clang-sys (details).
  • Node.js 20.18.0 or newer and pnpm 9.6.0, only if you work on the TypeScript packages in packages/ (the CLI, the Codama renderer and the version registry).
Terminal
git clone https://github.com/sevenlabs-hq/carbon.git
cd carbon
cargo build
cargo test

A full workspace build compiles every decoder and datasource with the Solana 4.2 crates, so the first one takes a while and needs several GB of disk. While you work on one crate, build and test only that crate with -p:

Terminal
cargo test -p carbon-core

Repository layout#

DirectoryContents
crates/carbon-core (pipeline, traits, filters, metrics), carbon-macros, carbon-proc-macros, carbon-test-utils
datasources/One crate per datasource, carbon-<name>-datasource
decoders/One crate per program decoder, carbon-<name>-decoder
metrics/carbon-log-metrics, carbon-prometheus-metrics
examples/Runnable example pipelines (see Examples)
packages/The TypeScript CLI (@sevenlabs-hq/carbon-cli), the Codama renderer and the version registry
misc/Vendored Jito protobufs (a git submodule), used only by the Carbon 1 ShredStream datasource
scripts/Formatting, linting, publishing and CLI version scripts

The workspace members are crates/*, datasources/*, decoders/*, examples/* and metrics/*. datasources/jetstreamer-datasource, datasources/jito-shredstream-grpc-datasource and examples/jetstreamer are excluded: they stay on Carbon 1 and Solana 3.

Run the checks#

Every pull request runs .github/workflows/check.yml on Rust 1.96.1. All jobs must pass before a PR can merge:

CheckCommand
Formatcargo fmt --check
Lintcargo clippy --locked --all-targets --all-features -- -D warnings
Cargo.toml orderingcargo sort -c -g (cargo-sort 2.0.2)
Unused dependenciescargo machete (cargo-machete 0.7.0)
Testscargo test --locked --all-targets --all-features
Published dependency resolutioncargo package -p carbon-test-utils --exclude-lockfile

Install the two extra Cargo tools at the versions CI uses:

Terminal
cargo install --locked cargo-sort@2.0.2
cargo install --locked cargo-machete@0.7.0

To run the same checks before every commit, link the pre-commit script as a git hook. It asks before linking, installs any missing tools, and runs cargo machete, cargo fmt -- --check, cargo sort -c -g, cargo test --all-targets --all-features and cargo clippy --all-targets --all-features -- -D warnings:

Terminal
./.pre-commit.sh

./scripts/cargo-fmt.sh runs cargo fmt --all to format everything. rustfmt.toml sets a few nightly-only options (wrap_comments, comment_width, imports_granularity, group_imports); stable rustfmt prints a warning for each and ignores them, which is expected.

If you change the TypeScript packages, run what check-typescript.yml runs:

Terminal
pnpm install --frozen-lockfile
pnpm build
pnpm test
pnpm format:check
pnpm type-check

Add a decoder#

A decoder crate turns one program's instructions, accounts and events into Rust types. Generate it from the program's IDL with the Carbon CLI rather than writing it by hand, so it matches the other decoders in decoders/. Generate a decoder explains the generated code; the CLI reference lists every flag.

Generate the crate into decoders/#

From the repository root, point --idl at an IDL file or at the program address (the CLI fetches the on-chain IDL from --url). --standalone false makes the generated Cargo.toml use the workspace's dependency versions instead of adding its own [workspace] section.

Terminal
npx -y @sevenlabs-hq/carbon-cli@2.0.0 parse \
  --idl <path-to-idl.json-or-program-address> \
  --url mainnet-beta \
  --standard anchor \
  --name my-program \
  --out-dir decoders/my-program-decoder \
  --standalone false \
  --package-version 2.0.0 \
  --description "My Program Decoder" \
  --keywords solana,decoder,my-program
Terminal
pnpm dlx @sevenlabs-hq/carbon-cli@2.0.0 parse \
  --idl <path-to-idl.json-or-program-address> \
  --url mainnet-beta \
  --standard anchor \
  --name my-program \
  --out-dir decoders/my-program-decoder \
  --standalone false \
  --package-version 2.0.0 \
  --description "My Program Decoder" \
  --keywords solana,decoder,my-program
Terminal
bunx @sevenlabs-hq/carbon-cli@2.0.0 parse \
  --idl <path-to-idl.json-or-program-address> \
  --url mainnet-beta \
  --standard anchor \
  --name my-program \
  --out-dir decoders/my-program-decoder \
  --standalone false \
  --package-version 2.0.0 \
  --description "My Program Decoder" \
  --keywords solana,decoder,my-program
Terminal
yarn dlx @sevenlabs-hq/carbon-cli@2.0.0 parse \
  --idl <path-to-idl.json-or-program-address> \
  --url mainnet-beta \
  --standard anchor \
  --name my-program \
  --out-dir decoders/my-program-decoder \
  --standalone false \
  --package-version 2.0.0 \
  --description "My Program Decoder" \
  --keywords solana,decoder,my-program

Use --standard codama for a Codama IDL. Postgres and GraphQL modules are generated by default, as in the existing decoders, behind the crate's postgres and graphql features. With --description, the CLI writes the license, readme and repository fields from the workspace and a README.md.

Output
✔ Decoder generated
- Formatting generated code...
✔ Code formatted successfully

✨ Decoder Generated Successfully! ✨
  • Output: …/carbon/decoders/my-program-decoder
  • Source: file (anchor)

Wire it into the workspace#

decoders/* is already a workspace member, so Cargo picks up the new crate. Add it to [workspace.dependencies] in the root Cargo.toml, in alphabetical order (cargo sort -c -g checks this):

Cargo.toml
carbon-my-program-decoder = { path = "decoders/my-program-decoder", version = "2.0.0" }

Add the crate name to the workspace_crates list in scripts/publish-crate.sh so it's published with the next release.

Add tests#

Capture real instructions and accounts from mainnet as JSON fixtures under tests/fixtures/ and replay them through the decoder with carbon-test-utils, which is already a dev-dependency of the generated crate. Test at least one instruction per common variant and each account type. Testing shows the pattern. At v2.0.0, only decoders/token-2022-decoder/tests exists, so new decoders with fixtures are welcome.

Check the crate#

Terminal
cargo fmt -p carbon-my-program-decoder --check
cargo clippy -p carbon-my-program-decoder --all-targets --all-features -- -D warnings
cargo test -p carbon-my-program-decoder --all-features

A freshly generated decoder passes all three. Run the full checks from Run the checks before you push.

When a program upgrades its IDL, regenerate its decoder with the same command (the CLI deletes the output directory before rendering unless you pass --no-clean) and review the diff for removed or reordered fields.

Add a datasource#

A datasource crate produces Updates for the pipeline. It lives in datasources/<name>-datasource/ with a Cargo.toml, a README.md and src/lib.rs, and implements the Datasource trait from carbon-core:

src/lib.rs
use {
    async_trait::async_trait,
    carbon_core::{
        datasource::{Datasource, DatasourceId, Update, UpdateType},
        error::CarbonResult,
    },
    tokio::sync::mpsc::Sender,
    tokio_util::sync::CancellationToken,
};

pub struct YourDatasource;

#[async_trait]
impl Datasource for YourDatasource {
    async fn consume(
        &self,
        id: DatasourceId,
        sender: Sender<(Update, DatasourceId)>,
        cancellation_token: CancellationToken,
    ) -> CarbonResult<()> {
        // Send updates with `sender.send((update, id.clone())).await`
        // until `cancellation_token` is cancelled.
        let _ = (id, sender);
        cancellation_token.cancelled().await;
        Ok(())
    }

    fn update_types(&self) -> Vec<UpdateType> {
        vec![UpdateType::Transaction]
    }
}

Custom datasource walks through a complete one, and examples/custom-datasource is a runnable reference. A datasource merged into Carbon should also:

  • Declare every update type it can emit in update_types: AccountUpdate, Transaction, AccountDeletion, BlockDetails.
  • Normalize accounts through AccountUpdate::into_update(), so zero-lamport accounts become Update::AccountDeletion like in every other datasource.
  • Request binary transactions with Transaction V1: UiTransactionEncoding::Base64 and max_supported_transaction_version: Some(1). Carbon can't rebuild a transaction from JSON.
  • Stop promptly on cancellation, including during sleeps and backoff, and return when the source is exhausted if it's finite.
  • Retry transient failures with backoff, and make the retry policy configurable.
  • Register its metrics with MetricsRegistry::global() so they appear in every exporter (Metrics).
  • Include tests with mocked data and a README that covers setup, configuration and tradeoffs.

Wire it in like a decoder: an entry in [workspace.dependencies], the crate name in scripts/publish-crate.sh, and a line in the datasource list in CONTRIBUTING.md.

Open a pull request#

  1. Fork the repository and create a branch from main.
  2. Make the change, with tests and updated READMEs and doc comments.
  3. Run the checks.
  4. Open a pull request against main. Describe what changed, why, and how you tested it; link related issues; mark breaking changes clearly.

Use conventional commit messages and PR titles, type(scope): description, with one of feat, fix, docs, style, refactor, test or chore:

Text
feat(decoders): add my-program decoder
fix(yellowstone-grpc-datasource): back off between failed subscribe attempts

Report bugs and request features in GitHub issues. By contributing, you agree that your contributions are licensed under the MIT License.

Improving these docs#

To fix or improve a page:

  • Rate the page. Answer Was this page helpful? at the bottom of each page, with an optional comment. It goes to the docs maintainers and needs no GitHub account.
  • Report a problem. The Report an issue link on each page opens a new issue in sevenlabs-hq/carbon, labelled documentation, with the page's URL filled in. Use it for anything wrong, missing or unclear, such as a command that doesn't run or an output that doesn't match.

If a page disagrees with Carbon's behaviour at v2.0.0, include the command you ran and its output.

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.