Skip to content

Contributing

How the repository is organized, how to build and test each piece, and the flow a change goes through.

Repository layout

programs/
  zenith-amm/     concentrated-liquidity AMM (Anchor)
  zenith-dlmm/    liquidity-book DEX (Anchor)
  zenith-camm/    constant-product AMM + yield (Anchor)
crates/
  zenith-math/    shared fixed-point math (pure Rust)
sdk/              @zenith/sdk (TypeScript) + scripts/ (devnet tooling)
app/              Vite + React frontend
tests/
  amm-integration/   on-chain integration tests (solana-program-test)
  dlmm-integration/  on-chain integration tests

The Cargo workspace's default-members deliberately excludes the RPC-heavy seed crate so cargo build / cargo test stay fast; run that explicitly when needed.

Build & test

Rust (programs + math)

bash
cargo build                 # host build, all default members
cargo test                  # unit + golden-vector tests
cargo fmt --all             # formatting (CI checks with --check)
cargo clippy -p zenith-math --all-targets -- -D warnings

Deployable programs and on-chain tests need the SBF build:

bash
cargo build-sbf --manifest-path programs/zenith-amm/Cargo.toml
cargo test -p amm-integration      # runs against the built .so

SDK

bash
cd sdk
npm run build       # tsup → dist/
npm run lint        # tsc --noEmit (covers src, tests, localnet)
npm test            # vitest unit + parity-vector suite
npm run test:localnet   # bankrun parity vs the real compiled program (build the .so first)

App

bash
cd app
npm run lint        # tsc --noEmit
npm run build       # vite production build
npm run dev         # local dev server

The app imports the SDK's built output, so build the SDK first.

Continuous integration

CI runs five jobs on every push:

JobChecks
sdklint, test, build
zenith-mathfmt, clippy -D warnings, test
zenith-ammfmt, build, test
zenith-dlmmfmt, build, test
zenith-cammfmt, build, test

The on-chain integration crates and the localnet parity test are not in CI (they need a built .so / a validator) — run them locally. The app has no CI job; verify it with npm run lint + npm run build.

Change flow

Work is done one issue at a time, on a branch off main:

  1. Branch off main.
  2. Implement, then run the relevant build/test/fmt/clippy for the surface you touched.
  3. Commit in atomic, conventional-style commits (fix:, feat:, test:, docs:, chore: …) — one logical change each.
  4. Open a PR, tagged with the milestone and engine labels.
  5. Review — money-path code (swaps, fees, liquidity) gets an adversarial review pass that actively tries to find a value-leaking or fund-locking bug before it merges.
  6. CI green, then rebase-merge. Confirm the merge succeeded before deleting the branch, then sync main.

Conventions

  • No floats in the money path. Mirror any new on-chain math in the SDK and pin it with a golden vector (see Math).
  • Keep quotes and settlement equal. If you change swap math, update the SDK quote and confirm the parity test still passes exactly.
  • Account layout changes ripple. A field added to a zero-copy account must be reflected in the SDK decoder (and its golden-byte test) and, if user-facing, the app.

Deploying to devnet

The three programs share fixed IDs (in each declare_id! and Anchor.toml). Two scripts manage deploys:

bash
# Build + deploy (or upgrade in place) all three programs on devnet. Idempotent —
# re-running upgrades under the same IDs.
./scripts/deploy-devnet.sh              # or: ./scripts/deploy-devnet.sh zenith_amm

# Assert the IDs agree across Anchor.toml, each declare_id!, the SDK, and the app
# manifests (gate this before a deploy / in CI).
./scripts/verify-program-ids.sh

The upgradeable program keypairs (target/deploy/<name>-keypair.json) are the only thing that pins an ID to an upgrade authority. They are not committed — back them up; losing one means that program can never be upgraded again. The deploy script refuses to run if a keypair's pubkey doesn't match the declared ID.

Deploys keep the program IDs stable (in-place upgrades), so the SDK constants and app manifests only change when a market is re-seeded, not when a program is redeployed.

Built on Solana devnet. Integer-exact math, no floats.