This file provides guidance to coding agents working in this repository.
@balancer/sdk — TypeScript SDK for the Balancer Protocol. Published to npm, consumed by the Balancer frontend and integrators. Supports Balancer V2, Balancer V3, and CowAmm pools across many EVM chains (see ChainId in src/utils/constants.ts).
Package manager: pnpm (packageManager pinned in package.json). Node >=18.
pnpm install # install deps
pnpm build # runs codegen + tsup (ESM+CJS to dist/)
pnpm codegen # GraphQL codegen from the Balancer API (required before build)
pnpm lint # dpdm (circular deps) + biome check
pnpm format # biome format --write
pnpm check-types # tsc --noEmit
pnpm test # vitest in watch mode
pnpm test:all # one-shot full suite
pnpm test:no-integration # skips *.integration.test.ts
pnpm test:no-v2 # skips test/v2/** (V3-only subset)
pnpm update:deployments # regenerates contract address maps in src/utils from a Balancer deployments source
pnpm example ./examples/swaps/swapV3.ts # run an example scriptRun a single test file: pnpm test -- swap.test.ts (or pass any vitest filter).
Tests hit real contracts on Anvil forks of each supported chain, not mocks. Setup lives in test/anvil/anvil-global-setup.ts:
- Each chain has a pinned
forkBlockNumberand a base port (separated by 100 to avoid collisions). When bumping fork blocks (e.g. to include a newly-deployed factory), editANVIL_NETWORKSthere — there is a precedent commitce4456f8for this pattern. startFork()creates a new Anvil instance per call on a unique port (basePort + VITEST_WORKER_ID + forkCounter*100); forks are not reused across tests.- RPC URLs come from env vars (
ETHEREUM_RPC_URL,POLYGON_RPC_URL,SEPOLIA_RPC_URL,ARBITRUM_ONE_RPC_URL, etc. — seeANVIL_NETWORKS[x].rpcEnv). If unset, a public Tenderly/DRPC fallback is used with a warning. vitest.config.mtscapsmaxForks: 3— don't raise this casually, each fork is a full Anvil process making RPC calls.SKIP_GLOBAL_SETUP=truetells the harness you'll run Anvil yourself (the fork config logs the command to use).- Foundry/Anvil must be installed locally (
curl -L https://foundry.paradigm.xyz | bash && foundryup).
CI runs test:no-integration — integration tests (*.integration.test.ts) are excluded from PR CI.
The central shape is PoolState (src/entities/types.ts) with protocolVersion: 1 | 2 | 3 where 1 = CowAmm, 2 = Balancer V2, 3 = Balancer V3. Every top-level entity (AddLiquidity, RemoveLiquidity, Swap, CreatePool, InitPool, boosted/nested variants, etc.) is a thin dispatcher that switches on protocolVersion and delegates to a *V2 / *V3 / *CowAmm implementation. When adding a feature, mirror this layout rather than branching inside a single class.
Each user-facing entity exposes the same two-step flow:
query(input, poolState, block?)— runs an on-chainquery*/simulation against the V2 Queries contract or V3 Router, returning expected amounts. This is the price-impact-accurate path.buildCall(input)— returns{ to, callData, value }ready to send. V3 often requires auserDatafield; V2 requiressender/recipient. The base dispatcher enforces this withmissingParameterError/exceedingParameterError.
V3 also exposes buildCallWithPermit2(input, permit2) (via Permit2 in src/entities/permit2Helper) and remove-liquidity has buildCallWithPermit(input, permit) (EIP-2612). Both guard on protocolVersion === 3.
See src/entities/addLiquidity/index.ts, src/entities/removeLiquidity/index.ts, src/entities/swap/index.ts for the canonical dispatcher pattern.
src/entities/inputValidator/inputValidator.ts maps PoolType → a InputValidatorBase subclass (Weighted / Stable / ComposableStable / Gyro / CowAmm / ReClamm / LiquidityBootstrapping / LiquidityBootstrappingFixedPrice). The top-level entity calls validate* before dispatching to a V2/V3 impl. Unknown pool types fall back to the base validator with a console.warn.
src/data/providers/:
balancer-api/— GraphQL client forhttps://api-v3.balancer.fi(modules:pool-state,nested-pool-state,boosted-pool-state,buffer-state,sorSwapPaths). Generated types atsrc/data/providers/balancer-api/generated/types.ts— regenerated bypnpm codegenfromcodegen.yml; do not hand-edit.onchain/— read pool state directly from chain via multicall when the API isn't used.initPoolDataProvider.ts— bootstrapsPoolStateimmediately after pool creation (before the API has indexed it).
src/abi/v2/andsrc/abi/v3/hold Viem-compatible ABIs. V3 has many more routers (router,batchRouter,bufferRouter,compositeLiquidityRouter,unbalancedAddViaSwapRouter,lBPMigrationRouter) and pool factories.- Per-chain contract address maps:
src/utils/balancerV2Contracts.tsandsrc/utils/balancerV3Contracts.ts. Constants/chain metadata insrc/utils/constants.ts(includesChainId,API_CHAIN_NAMES,CHAINS,PERMIT2,NATIVE_ASSETS). - To add or update deployments, run
pnpm update:deployments(seescripts/updateDeployments.ts) rather than editing the address maps by hand.
tsup.config.tsproduces ESM + CJS +.d.tsfromsrc/index.ts, bundled, with sourcemaps.__PACKAGE_VERSION__is replaced at build time with thepackage.jsonversion.- TS path alias:
@/*→src/*(both intsconfig.jsonand wired throughvite-tsconfig-pathsfor tests). - Public API is re-exported from
src/index.ts→abi,data,entities,utils,types.
- Biome (not ESLint/Prettier) is the formatter and linter. Config in
biome.json: single quotes, trailing commas, 4-space indent, 80-col line width, semicolons. Generated files undersrc/data/providers/balancer-api/generated/are ignored. - dpdm enforces no circular deps (
pnpm lintfails on them). - Errors go through typed factories in
src/utils/errors.ts(SDKError,protocolVersionError,missingParameterError,exceedingParameterError,inputValidationError) — use these instead of rawthrow new Error. - Releases use changesets. If your change touches
src/**, add one — see.agents/skills/changeset/SKILL.mdfor the file format and bump-selection rules (write the file directly;pnpm changesetis interactive and agents can't drive it). The release workflow onmaincuts version-bump PRs automatically; see.github/workflows/release.yml.