# Contributing (Carbon 2.0)

> 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.

Carbon is MIT-licensed and developed in the open at [sevenlabs-hq/carbon](https://github.com/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`](https://github.com/sevenlabs-hq/carbon/blob/v2.0.0/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](https://carbon.sevenlabs.org/troubleshooting.md#build-fails-in-clang-sys-couldnt-find-any-valid-shared-libraries)).
- **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).

```sh
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`:

```sh
cargo test -p carbon-core
```

### Repository layout

| Directory      | Contents                                                                                                       |
| -------------- | -------------------------------------------------------------------------------------------------------------- |
| `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](https://carbon.sevenlabs.org/examples.md))                          |
| `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`](https://github.com/sevenlabs-hq/carbon/blob/v2.0.0/.github/workflows/check.yml) 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:

```sh
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`:

```sh
./.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.

> **Warning: scripts/cargo-clippy.sh doesn't pass at v2.0.0.** `CONTRIBUTING.md` suggests `./scripts/cargo-clippy.sh`, which adds `--deny` flags for `arithmetic_side_effects`, `manual_let_else`, `default_trait_access` and `used_underscore_binding`. At `v2.0.0`, `carbon-core` itself fails it with 12 errors. CI doesn't use the script; run the clippy command from the table above instead.

If you change the TypeScript packages, run what [`check-typescript.yml`](https://github.com/sevenlabs-hq/carbon/blob/v2.0.0/.github/workflows/check-typescript.yml) runs:

```sh
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](https://carbon.sevenlabs.org/guides/generate-a-decoder.md) explains the generated code; [the CLI reference](https://carbon.sevenlabs.org/reference/cli.md) lists every flag.

### Step 1: 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.

```sh
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
```

With pnpm, bun or yarn, replace `npx -y` with `pnpm dlx`, `bunx` or `yarn dlx`.

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:

```text
✔ Decoder generated
- Formatting generated code...
✔ Code formatted successfully

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

### Step 2: 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`:

```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.

### Step 3: 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`](https://carbon.sevenlabs.org/reference/test-utils.md), which is already a dev-dependency of the generated crate. Test at least one instruction per common variant and each account type. [Testing](https://carbon.sevenlabs.org/guides/testing.md) shows the pattern. At `v2.0.0`, only `decoders/token-2022-decoder/tests` exists, so new decoders with fixtures are welcome.

### Step 4: Check the crate

```sh
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](#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 `Update`s 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`:

```rust
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](https://carbon.sevenlabs.org/guides/custom-datasource.md) walks through a complete one, and [`examples/custom-datasource`](https://carbon.sevenlabs.org/examples.md#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](https://carbon.sevenlabs.org/concepts/metrics.md)).
- **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](#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](https://www.conventionalcommits.org/) 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](https://github.com/sevenlabs-hq/carbon/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](https://github.com/sevenlabs-hq/carbon/issues), 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.

---

Carbon 2.0.0 documentation · Web: https://carbon.sevenlabs.org/contributing · Last updated 2026-11-09 · Index of all pages: https://carbon.sevenlabs.org/llms.txt
