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 testsThe 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)
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 warningsDeployable programs and on-chain tests need the SBF build:
cargo build-sbf --manifest-path programs/zenith-amm/Cargo.toml
cargo test -p amm-integration # runs against the built .soSDK
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
cd app
npm run lint # tsc --noEmit
npm run build # vite production build
npm run dev # local dev serverThe app imports the SDK's built output, so build the SDK first.
Continuous integration
CI runs five jobs on every push:
| Job | Checks |
|---|---|
sdk | lint, test, build |
zenith-math | fmt, clippy -D warnings, test |
zenith-amm | fmt, build, test |
zenith-dlmm | fmt, build, test |
zenith-camm | fmt, 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:
- Branch off
main. - Implement, then run the relevant build/test/fmt/clippy for the surface you touched.
- Commit in atomic, conventional-style commits (
fix:,feat:,test:,docs:,chore:…) — one logical change each. - Open a PR, tagged with the milestone and engine labels.
- 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.
- 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:
# 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.shThe 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.