@zenith/sdk
The TypeScript SDK is how everything off-chain talks to Zenith. It does three things:
- Decode program accounts into typed objects.
- Quote swaps with the same exact math the programs use.
- Build transactions with the correct accounts, flags, and argument encoding.
It lives in sdk/ and is consumed by the app (and by the devnet scripts).
Install & build
The SDK ships compiled output in dist/. Build it before anything that imports it:
cd sdk
npm install
npm run build # tsup → dist/
npm test # vitest: decoders, math ports, quotes, buildersThe app depends on the built dist/, so re-run npm run build in sdk/ after changing SDK source.
Namespaces
One engine's API is top-level; the other two are namespaced, so imports read by engine:
import {
ZenithConnection, // AMM (top-level)
fetchPool, quoteSwap, buildSwap,
dlmm, // DLMM namespace
camm, // CAMM namespace
} from "@zenith/sdk";dlmm.fetchLbPair(...), dlmm.quoteSwap(...), camm.fetchPool(...), and so on mirror the AMM surface for their engines.
Quote → build → send (AMM)
import {
ZenithConnection, fetchPool, fetchConfig, quoteSwap, buildSwap,
swapTickArrays, buildTransactionFrom, SwapDirection, SwapMode,
} from "@zenith/sdk";
import { Connection } from "@solana/web3.js";
const connection = new Connection("https://api.devnet.solana.com", "confirmed");
const zc = new ZenithConnection(connection, { commitment: "confirmed" });
// 1. Read live state.
const pool = await fetchPool(zc, POOL);
const config = await fetchConfig(zc, CONFIG);
const slot = await connection.getSlot();
// 2. Quote — exact output, fee, and end state.
const quote = quoteSwap({
pool, config, slot,
direction: SwapDirection.BToA,
mode: SwapMode.ExactIn,
amount: 1_000_000n,
slippageBps: 50,
});
// 3. Build — hand the swap the tick arrays it will cross.
const built = buildSwap({
owner,
pool: POOL, config: CONFIG,
mintA: MINT_A, mintB: MINT_B,
direction: SwapDirection.BToA,
mode: SwapMode.ExactIn,
amount: quote.amountIn,
otherAmountThreshold: quote.otherAmountThreshold,
tickArrays: swapTickArrays(POOL, pool.currentTick, pool.tickSpacing, false),
});
// 4. Assemble and sign with a wallet.
const { transaction } = buildTransactionFrom({
payerKey: owner,
recentBlockhash: (await connection.getLatestBlockhash()).blockhash,
built: [built],
});Because the quote and the program share the same fixed-point math, the on-chain output equals quote.amountOut exactly.
What's in the box
- Decoders —
fetchPool/fetchConfig/fetchPosition/fetchTickArray(and thedlmm.*/camm.*equivalents) read the zero-copy account layouts by byte offset, guarded by discriminator and length. - PDAs —
configPda,poolPda,positionPda,tickArrayPda,vaultPda,poolAuthorityPda, … derive every program address. - Quotes —
quoteSwap(AMM walks the tick curve; DLMM walks bins; CAMM applies the constant-product formula), returning exact amounts, fee, and end state. - Builders — one
build*per instruction, producing the precise account order and argument encoding each handler expects. Helpers likeswapTickArraysandpositionTickArraysderive the tick arrays an AMM swap or liquidity change needs.
Tick arrays and swaps
An AMM swap must be handed the tick arrays its walk will cross, as remaining accounts. swapTickArrays(pool, currentTick, tickSpacing, aToB) returns the contiguous run starting from the current-price array in the travel direction. The arrays must already exist on-chain (buildInitTickArray allocates them); a liquidity change similarly threads its position's two boundary arrays via positionTickArrays.
Add / remove liquidity
Liquidity changes follow the same build-and-send shape as a swap. For the AMM, open a position, then add liquidity over its tick range (threading the two boundary tick arrays):
import { buildCreatePosition, buildAddLiquidity, positionTickArrays } from "@zenith/sdk";
// Open a position over [tickLower, tickUpper).
const open = buildCreatePosition({ owner, pool, nftMint, tickLower, tickUpper, tickSpacing });
// Add L, capped by the amounts you're willing to spend.
const arrays = positionTickArrays(pool, tickLower, tickUpper, tickSpacing);
const add = buildAddLiquidity({
owner, pool, position, nftMint, mintA, mintB,
tickLower, tickUpper, tickSpacing,
liquidityDelta, tokenAMax, tokenBMax,
tickArrays: arrays,
});Removing is the mirror — buildRemoveLiquidity / buildRemoveAllLiquidity with tokenAMin / tokenBMin floors. The DLMM (dlmm.buildAddLiquidityByStrategy) and CAMM (camm.buildAddLiquidity) namespaces expose the same pattern for their engines. Fees are claimed with buildClaimPositionFee (AMM) — or folded back in when the position is in compounding mode.
See Math for how the SDK's quotes are pinned to the programs, Integration for the Jupiter adapter, and Devnet for the live addresses to plug in.