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. 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.pins it, sotoml rustupinstalls and selects it the first time you runcargoin the checkout. Published crates declare the same MSRV in the workspaceCargo.tomlandclippy.toml. - libclang, for
carbon-validator-snapshot-datasource(RocksDB is built withbindgen).sudo apt-get install libclang-devon Debian and Ubuntu,brew install llvmon macOS. Without it, building the whole workspace fails inclang-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).
git clone https://github.com/sevenlabs-hq/carbon.git
cd carbon
cargo build
cargo testA 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:
cargo test -p carbon-coreRepository layout#
| Directory | Contents |
|---|---|
crates/ | carbon-core (pipeline, traits, filters, metrics), carbon-macros, carbon-proc-macros, carbon-test-utils |
datasources/ | One crate per datasource, carbon-< |
decoders/ | One crate per program decoder, carbon-< |
metrics/ | carbon-log-metrics, carbon-prometheus-metrics |
examples/ | Runnable example pipelines (see Examples) |
packages/ | The TypeScript CLI (@sevenlabs-hq/), 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/, datasources/ and examples/ are excluded: they stay on Carbon 1 and Solana 3.
Run the checks#
Every pull request runs . on Rust 1.96.1. All jobs must pass before a PR can merge:
| Check | Command |
|---|---|
| Format | cargo fmt --check |
| Lint | cargo clippy --locked --all-targets --all-features -- -D warnings |
Cargo.toml ordering | cargo sort -c -g (cargo-sort 2.0.2) |
| Unused dependencies | cargo machete (cargo-machete 0.7.0) |
| Tests | cargo test --locked --all-targets --all-features |
| Published dependency resolution | cargo package -p carbon-test-utils --exclude-lockfile |
Install the two extra Cargo tools at the versions CI uses:
cargo install --locked cargo-sort@2.0.2
cargo install --locked cargo-machete@0.7.0To 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:
./.pre-commit.sh. runs cargo fmt --all to format everything. rustfmt. sets a few nightly-only options (wrap_, comment_, imports_, group_); stable rustfmt prints a warning for each and ignores them, which is expected.
If you change the TypeScript packages, run what check-typescript. runs:
pnpm install --frozen-lockfile
pnpm build
pnpm test
pnpm format:check
pnpm type-checkAdd 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.
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-programpnpm 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-programbunx @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-programyarn 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-programUse --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.
✔ 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. in the root Cargo.toml, in alphabetical order (cargo sort -c -g checks this):
carbon-my-program-decoder = { path = "decoders/my-program-decoder", version = "2.0.0" }Add the crate name to the workspace_ list in scripts/ so it's published with the next release.
Add tests#
Capture real instructions and accounts from mainnet as JSON fixtures under tests/ 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/ exists, so new decoders with fixtures are welcome.
Check the crate#
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-featuresA 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/ with a Cargo.toml, a README.md and src/lib.rs, and implements the Datasource trait from carbon-core:
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/ 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::, so zero-lamport accounts becomeinto_ update() Update::like in every other datasource.AccountDeletion - Request binary transactions with Transaction V1:
UiTransactionEncoding::andBase64 max_. Carbon can't rebuild a transaction from JSON.supported_ transaction_ version: Some(1) - 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::so they appear in every exporter (Metrics).global() - Include tests with mocked data and a README that covers setup, configuration and tradeoffs.
Wire it in like a decoder: an entry in [workspace., the crate name in scripts/, and a line in the datasource list in CONTRIBUTING..
Open a pull request#
- Fork the repository and create a branch from
main. - Make the change, with tests and updated READMEs and doc comments.
- Run the checks.
- 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:
feat(decoders): add my-program decoder
fix(yellowstone-grpc-datasource): back off between failed subscribe attemptsReport 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.