# AGENTS.md This file provides guidance to coding agents working in this repository. ## Project Overview `@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 `>=20` (CI uses 22; `@graphql-codegen/cli` 7.x requires Node 20.12+). ## Common Commands ```bash 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 script ``` Run a single test file: `pnpm test -- swap.test.ts` (or pass any vitest filter). ## Testing model 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 `forkBlockNumber` and a base port (separated by 100 to avoid collisions). When bumping fork blocks (e.g. to include a newly-deployed factory), edit `ANVIL_NETWORKS` there — there is a precedent commit `ce4456f8` for 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. — see `ANVIL_NETWORKS[x].rpcEnv`). If unset, a public Tenderly/DRPC fallback is used with a warning. - `vitest.config.mts` caps `maxForks: 3` — don't raise this casually, each fork is a full Anvil process making RPC calls. - `SKIP_GLOBAL_SETUP=true` tells 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. ## Architecture ### Protocol-version dispatch 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. ### Entity surface Each user-facing entity exposes the same two-step flow: 1. **`query(input, poolState, block?)`** — runs an on-chain `query*`/simulation against the V2 Queries contract or V3 Router, returning expected amounts. This is the price-impact-accurate path. 2. **`buildCall(input)`** — returns `{ to, callData, value }` ready to send. V3 often requires a `userData` field; V2 requires `sender`/`recipient`. The base dispatcher enforces this with `missingParameterError` / `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. ### Input validation `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`. ### Data providers `src/data/providers/`: - **`balancer-api/`** — GraphQL client for `https://api-v3.balancer.fi` (modules: `pool-state`, `nested-pool-state`, `boosted-pool-state`, `buffer-state`, `sorSwapPaths`). Generated types at `src/data/providers/balancer-api/generated/types.ts` — regenerated by `pnpm codegen` from `codegen.yml`; do not hand-edit. - **`onchain/`** — read pool state directly from chain via multicall when the API isn't used. - **`initPoolDataProvider.ts`** — bootstraps `PoolState` immediately after pool creation (before the API has indexed it). ### ABIs and contract addresses - `src/abi/v2/` and `src/abi/v3/` hold Viem-compatible ABIs. V3 has many more routers (`router`, `batchRouter`, `bufferRouter`, `compositeLiquidityRouter`, `unbalancedAddViaSwapRouter`) and pool factories. - Per-chain contract address maps: `src/utils/balancerV2Contracts.ts` and `src/utils/balancerV3Contracts.ts`. Constants/chain metadata in `src/utils/constants.ts` (includes `ChainId`, `API_CHAIN_NAMES`, `CHAINS`, `PERMIT2`, `NATIVE_ASSETS`). - To add or update deployments, run `pnpm update:deployments` (see `scripts/updateDeployments.ts`) rather than editing the address maps by hand. ### Build - `tsup.config.ts` produces ESM + CJS + `.d.ts` from `src/index.ts`, bundled, with sourcemaps. `__PACKAGE_VERSION__` is replaced at build time with the `package.json` version. - TS path alias: **`@/*` → `src/*`** (both in `tsconfig.json` and wired through `vite-tsconfig-paths` for tests). - Public API is re-exported from `src/index.ts` → `abi`, `data`, `entities`, `utils`, `types`. ## Conventions - **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 under `src/data/providers/balancer-api/generated/` are ignored. - **dpdm** enforces no circular deps (`pnpm lint` fails on them). - Errors go through typed factories in `src/utils/errors.ts` (`SDKError`, `protocolVersionError`, `missingParameterError`, `exceedingParameterError`, `inputValidationError`) — use these instead of raw `throw new Error`. - Releases use **changesets**. If your change touches `src/**`, add one — see `.agents/skills/changeset/SKILL.md` for the file format and bump-selection rules (write the file directly; `pnpm changeset` is interactive and agents can't drive it). The release workflow on `main` cuts version-bump PRs automatically; see `.github/workflows/release.yml`.