Standalone static website for withdrawing Balancer positions from deprecated chains. Currently enabled: Mode, Fraxtal, Polygon zkEVM. Supports Balancer v2 and v3.
- Connect an injected wallet (MetaMask, Rabby, ...) or watch any address read-only
- Optionally configure a custom RPC URL per chain (persisted in localStorage)
- Scan for pool (BPT) and gauge (staked) positions via Multicall3 against a bundled pool list
- Unstake from gauges (
withdraw(amount, claim_rewards=true)) and claim rewards - Proportionally exit v2 pools (
Vault.exitPool) and v3 pools (Router.removeLiquidityProportional) - Exits are simulated first; minimum amounts = expected − slippage (default 1%). An opt-in emergency mode submits with zero minimums.
- Recovery-mode pools (incl. paused and linear pools) exit via the recovery path automatically;
expected amounts are computed client-side (
cashBalance × bptIn / virtualSupply) becauseBalancerQueries.queryExitreverts on paused pools while the real recovery exit succeeds.
The site is fully static: no backend, no subgraph — it only talks to the RPC you configure.
npm install
npm run dev # http://localhost:3001
npm run build # static output in dist/Dependencies: react, react-dom, viem. That's all.
Positions are scanned against per-chain pool/gauge lists committed in src/config/data/<chain>.json.
These lists are generated once (chains are deprecated; the lists don't change) by:
npm run discover -- --chain mode [--rpc <url>] [--to-block <n>]The script enumerates:
- v2 pools:
PoolCreatedevents from every pool factory (fromsrc/config/registry.json) - v3 pools:
PoolRegisteredevents from the v3 Vault (covers all v3 factories) - gauges:
GaugeCreatedevents from the child-chain gauge factories
and enriches everything (poolId, tokens, symbols, decimals, phantom-BPT detection) via Multicall3.
eth_getLogs is chunked adaptively and respects strict public RPCs (zkEVM caps at 1k blocks).
Pools missing from a list can always be added in the UI by pasting the pool address.
src/config/registry.json holds config for all Balancer chains (v2 vault + pool factories +
gauge factories + start blocks, v3 vault + router, BalancerQueries, Multicall3, default RPC).
It is machine-generated from sibling repos — never hand-edit it; regenerate instead:
node tools/extract-registry.mjs --repos <dir containing the 4 repos below>Sources: balancer-subgraph-v2/networks.json, balancer-subgraph-v3/networks.json,
gauges-subgraph/subgraph.<chain>.yaml, backend/config/<chain>.ts.
- Regenerate the registry if factory lists changed:
node tools/extract-registry.mjs - Set
"deprecated": truefor the chain insrc/config/registry.json(or mark it intools/extract-registry.mjsand regenerate) - Run discovery:
npm run discover -- --chain <key> - Smoke test the exits:
node tools/smoke-test.mjs --chain <key> - Build and deploy
Chains that are not deprecated are hidden in the UI but visible in dev mode
(npm run dev or append ?dev=1 to the URL). Base is included as a v3 test chain.
node tools/smoke-test.mjs --chain modeHeadless test against the live chain (no funds needed): uses each gauge contract as a pseudo-user
(gauges hold the staked BPT), simulates the exact exit calls the UI would send, and for
recovery-mode pools verifies the client-side math by simulating the real exitPool with
0.5%-tight minimums.
For end-to-end testing with real transactions, fork a chain with
anvil --fork-url <rpc>, set the UI's RPC to http://localhost:8545, and impersonate a holder.
- Exits always pay out wrapped native tokens (no auto-unwrap), matching vault registration.
- No USD pricing — deprecated chains have no reliable price source; amounts are token quantities.
- Linear pools (zkEVM) can only exit while in recovery mode (they all are, post-deprecation); the UI blocks them otherwise instead of implementing batch swaps.
- Nested/boosted pools (e.g. bb-o-USD on zkEVM) pay out the inner BPTs (linear pool tokens). Rescan after exiting — the inner BPTs show up as new positions and exit via the recovery path.
- v3 exits require a one-time BPT approval to the v3 Router (plain ERC20 approve, no permit2).
- Child-chain BAL rewards are distributed as regular gauge reward tokens and are included in
claim_rewards.