# Balancer V3 > Learn, integrate, and build on a programmable AMM ## Build on Balancer AMMs built on the Balancer protocol benefit from the security of the Balancer vault. This enables developers to utilize the well-developed Balancer AMM ecosystem, while the vault's optimized architecture streamlines the development process and allows teams to concentrate on their product. We assist teams throughout this process, leveraging our extensive experience in bringing innovative AMMs to market. This includes addressing finer details such as aggregator integration, which is vital for any new AMM to capture the necessary organic flow to compete effectively. Our aim is to simplify the development process and facilitate the creation of novel AMMs with minimal friction. :::info When adding new tokens to Balancer pools, keep in mind the [Vault compatibility requirements](../partner-onboarding/onboarding-overview/core-pools.md#token-requirements). ::: When building on Balancer, there are three paths to explore: ## Data and Analytics overview Balancer offers data about the protocol, accessible through various sources. This data is helpful for understanding the protocol's performance, usage, and trends. One of the primary sources of data is our [API GraphQL](./data-and-analytics/balancer-api/balancer-api.md) endpoint. This endpoint provides real-time data, making it an excellent resource for those who need up-to-the-minute information about the protocol. In addition to the API, we also provide a [subgraph](./data-and-analytics/subgraph.md) that indexes the Balancer protocol. This subgraph offers a comprehensive view of the protocol's data, making it easy to analyze and understand the protocol's behavior over time. Lastly, various [Dune dashboards](/data-and-analytics/data-and-analytics/dune/overview.html) exist. These dashboards provide a visual representation of the protocol's data, making it easy to understand and interpret. They are a great resource for visual data analysis. ## Developer Reference For the most common use cases the APIs of interest will be the [Router](./contracts/router-api.md) or the [BatchRouter](./contracts/batch-router-api.md). We also provide a [JS/TS SDK](./sdk/README.md) to assist with interacting with the Balancer Protocol. Checkout the [Integrations Guides](../developer-reference/sdk/README.md) for Solidity and Javascript examples on common actions like adding/removing liquidity and making swaps. Individuals are encouraged to contact our developers via [Discord](https://discord.balancer.fi/) to request further assistance. A comprehensive catalog of all public Balancer repositories (82 total) organized by purpose and version. See [below](#repository-statistics) for statistics and specific guidance. *** ## Active Repositories ### Forkable Templates These repos are designed to be forked and used for new development by partners. They include the essentials for developing particular products, without the "noise" associated with the core repositories used by the Balancer team. #### [scaffold-balancer-v3](https://github.com/balancer/scaffold-balancer-v3) **Status:** Actively maintained (February 2025) • 104 stars • 103 forks A comprehensive starter kit for building on Balancer V3. Accelerates the process of creating custom pools and hooks contracts with a local fork environment and frontend playground. Includes example implementations of custom AMMs, dynamic swap fee hooks, lottery hooks, and exit fee hooks. Built on Scaffold-ETH 2 framework with integrated testing utilities and live examples at scaffold.balancer.fi. **Repository:** https\://github.com/balancer/scaffold-balancer-v3\ **Primary Use:** Creating and testing custom pools and hooks for Balancer V3 #### [custom-pool-v3](https://github.com/balancer/custom-pool-v3) **Status:** Active template (June 2025) • 1 star • 2 forks Example template for creating external custom pools for Balancer V3. Provides a foundation for developers to extend and create new pool types with novel invariants. **Repository:** https\://github.com/balancer/custom-pool-v3\ **Primary Use:** Building custom AMM pool types for V3 #### [weighted-stable-pool-v3](https://github.com/balancer/weighted-stable-pool-v3) **Status:** Active template (April 2025) • 0 stars • 1 fork Example template combining weighted and stable pool mechanics for Balancer V3. Demonstrates how to create hybrid pool types. **Repository:** https\://github.com/balancer/weighted-stable-pool-v3\ **Primary Use:** Building hybrid pool implementations for V3 #### [balancer-v3-sdk-examples](https://github.com/balancer/balancer-v3-sdk-examples) **Status:** Active template (April 2025) • 0 stars • 0 forks Collection of example scripts demonstrating how to use the Balancer SDK to perform pool operations using TypeScript for V3 pools. **Repository:** https\://github.com/balancer/balancer-v3-sdk-examples\ **Primary Use:** Learning SDK integration patterns for V3 #### [pool-operation-examples-v3](https://github.com/balancer/pool-operation-examples-v3) **Status:** Active template (March 2025) • 0 stars • 3 forks Example scripts showing how to perform various pool operations on Balancer V3, including swaps, adds, and removes with working code samples. **Repository:** https\://github.com/balancer/pool-operation-examples-v3\ **Primary Use:** Learning pool operation mechanics for V3 #### [balancer-v3-foundry-starter](https://github.com/balancer/balancer-v3-foundry-starter) **Status:** Active template (July 2025) • 0 stars • 1 fork Minimal Foundry-based starter template for Balancer V3 development with basic setup and configuration. **Repository:** https\://github.com/balancer/balancer-v3-foundry-starter\ **Primary Use:** Quick start for Foundry-based V3 projects #### [balancer-v2-foundry-starter](https://github.com/balancer/balancer-v2-foundry-starter) **Status:** Active template (February 2025) • 0 stars • 0 forks Minimal Foundry-based starter template for Balancer V2 development with basic setup and configuration. **Repository:** https\://github.com/balancer/balancer-v2-foundry-starter\ **Primary Use:** Quick start for Foundry-based V2 projects *** ### Core Repositories These are official Balancer Team repos, and reflect the current state of the codebase (which may be ahead of deployed code). #### Balancer V3 Core ##### [balancer-v3-monorepo](https://github.com/balancer/balancer-v3-monorepo) **Status:** Actively maintained (December 2024) • 120 stars • 98 forks • GPL-3.0 The main monorepo for Balancer V3 protocol containing the Vault, pool implementations, routers, and core infrastructure. This is the primary development repository for V3 with all smart contracts and testing infrastructure. **Repository:** https\://github.com/balancer/balancer-v3-monorepo\ **Primary Use:** Core V3 protocol development ##### [balancer-v3-erc4626-tests](https://github.com/balancer/balancer-v3-erc4626-tests) **Status:** Actively maintained (December 2024) • 3 stars • 2 forks Fork testing suite for ERC4626 token integrations with Balancer V3 pools, ensuring compatibility with yield-bearing token standards. **Repository:** https\://github.com/balancer/balancer-v3-erc4626-tests\ **Primary Use:** Testing ERC4626 compatibility ##### [docs-v3](https://github.com/balancer/docs-v3) **Status:** Actively maintained (December 2024) • 5 stars • 22 forks • Vue Official documentation for Balancer V3, including developer guides, concepts, integration instructions, and API references. **Repository:** https\://github.com/balancer/docs-v3\ **Primary Use:** V3 protocol documentation ##### [balancer-subgraph-v3](https://github.com/balancer/balancer-subgraph-v3) **Status:** Actively maintained (December 2024) • 3 stars • 9 forks • MIT The Graph protocol subgraph for indexing Balancer V3 data including pools, swaps, liquidity positions, and protocol analytics. **Repository:** https\://github.com/balancer/balancer-subgraph-v3\ **Primary Use:** V3 data indexing ##### [balancer-maths](https://github.com/balancer/balancer-maths) **Status:** Actively maintained (December 2024) • 10 stars • 5 forks Mathematical libraries and implementations for Balancer V3 pool calculations, including weighted math, stable math, and other invariant calculations with comprehensive test coverage. **Repository:** https\://github.com/balancer/balancer-maths\ **Primary Use:** Pool math reference and validation ##### [ReCLAMM](https://github.com/balancer/reclamm) **Status:** Actively maintained (December 2024) • 3 stars • 4 forks AutoRange Pools implementation for Balancer V3 — fungible concentrated liquidity pools that automatically adjust the price range according to market conditions. **Repository:** https\://github.com/balancer/reclamm\ **Primary Use:** Advanced concentrated liquidity features ##### [balancer-angstrom](https://github.com/balancer/balancer-angstrom) **Status:** Active development (October 2024) • 1 star • 2 forks Angstrom Router and Hook implementation for Balancer, providing preferential trading access to specific nodes for Angstrom pools within a given block. **Repository:** https\://github.com/balancer/balancer-angstrom\ **Primary Use:** Specialized routing for Angstrom integration ##### [pools-manager](https://github.com/balancer/pools-manager) **Status:** Active development (October 2024) • 0 stars • 1 fork Pool management utilities and contracts for Balancer V3, providing tools for pool administration and governance. **Repository:** https\://github.com/balancer/pools-manager\ **Primary Use:** Pool administration tools ##### [pool-math-simulator](https://github.com/balancer/pool-math-simulator) **Status:** Actively maintained (December 2024) • 0 stars • 1 fork Simulator for testing and validating pool mathematical operations and invariants, useful for pool developers to verify calculations. **Repository:** https\://github.com/balancer/pool-math-simulator\ **Primary Use:** Math validation and testing ##### [balancer-v3-dex-screener-api](https://github.com/balancer/balancer-v3-dex-screener-api) **Status:** Active (October 2024) • 1 star • 0 forks API implementation for DEX Screener integration with Balancer V3 pools, providing data endpoints for aggregators. **Repository:** https\://github.com/balancer/balancer-v3-dex-screener-api\ **Primary Use:** DEX aggregator integration #### Balancer V2 Core ##### [balancer-v2-monorepo](https://github.com/balancer/balancer-v2-monorepo) **Status:** Maintained (December 2024) • 349 stars • 416 forks • GPL-3.0 The main monorepo for Balancer V2 protocol, containing the Vault, pool implementations, and core contracts. Still actively maintained for V2 deployments, updates, and ongoing production use. **Repository:** https\://github.com/balancer/balancer-v2-monorepo\ **Primary Use:** V2 protocol maintenance and development ##### [balancer-subgraph-v2](https://github.com/balancer/balancer-subgraph-v2) **Status:** Maintained (March 2025) • 118 stars • 103 forks • MIT The Graph protocol subgraph for indexing Balancer V2 data including pools, swaps, transactions, users, and protocol analytics. **Repository:** https\://github.com/balancer/balancer-subgraph-v2\ **Primary Use:** V2 data indexing ##### [docs](https://github.com/balancer/docs) **Status:** Maintained (September 2024) • 46 stars • 118 forks • Vue Official documentation for Balancer V2, covering protocol concepts, pool types, integration guides, and developer resources. Note that new development on V2 is discouraged, in favor of V3. Note that BAL minting and the liquidity mining architecture and infrastructure will always be on V2. **Repository:** https\://github.com/balancer/docs\ **Primary Use:** V2 protocol documentation #### Smart Order Router & Routing ##### [balancer-sor](https://github.com/balancer/balancer-sor) **Status:** Active (February 2025) • 258 stars • 144 forks Smart Order Router for V2: off-chain linear optimization of routing orders across pools for best price execution. Supports multi-hop swaps, split routes, and price optimization across the entire pool ecosystem. **Repository:** https\://github.com/balancer/balancer-sor\ **Primary Use:** V2 trade routing optimization #### CoW Protocol Integration ##### [cow-amm](https://github.com/balancer/cow-amm) **Status:** Active (December 2024) • 12 stars • 14 forks • GPL-3.0 Balancer CoW AMM: an automated portfolio manager and liquidity provider that allows swaps to be executed via the CoW Protocol. Uses V1-based pools with CoW Protocol settlement, providing MEV protection and batch auction benefits. **Repository:** https\://github.com/balancer/cow-amm\ **Primary Use:** CoW Protocol pool integration ##### [cow-amm-subgraph](https://github.com/balancer/cow-amm-subgraph) **Status:** Active (December 2024) • 0 stars • 0 forks Subgraph for indexing Balancer CoW AMM data and tracking CoW Protocol settlements. **Repository:** https\://github.com/balancer/cow-amm-subgraph\ **Primary Use:** CoW AMM data indexing #### Frontend Applications ##### [frontend-monorepo](https://github.com/balancer/frontend-monorepo) **Status:** Actively maintained (December 2024) • 28 stars • 35 forks • MIT Main frontend monorepo containing the official Balancer web application and shared packages for user interfaces across the Balancer ecosystem. This is the current production frontend. **Repository:** https\://github.com/balancer/frontend-monorepo\ **Primary Use:** Official Balancer UI (current) ##### [pool-creator](https://github.com/balancer/pool-creator) **Status:** Actively maintained (October 2024) • 16 stars • 5 forks • MIT User-friendly interface for creating and initializing Balancer pools with guided workflows and step-by-step configuration. **Repository:** https\://github.com/balancer/pool-creator\ **Primary Use:** Pool creation UI ##### [marketing-site](https://github.com/balancer/marketing-site) **Status:** Active (July 2024) • 1 star • 6 forks • Vue Marketing and informational website for Balancer Protocol, separate from the main application interface. **Repository:** https\://github.com/balancer/marketing-site\ **Primary Use:** Marketing website #### SDK & Developer Tools ##### [b-sdk](https://github.com/balancer/b-sdk) **Status:** Actively maintained (December 2024) • 36 stars • 73 forks • MIT Current TypeScript SDK for interacting with Balancer protocol, providing high-level abstractions for pool operations, queries, and integrations across V2 and V3. **Repository:** https\://github.com/balancer/b-sdk\ **Primary Use:** TypeScript integration library (current) ##### [balpy](https://github.com/balancer/balpy) **Status:** Maintained (August 2024) • 66 stars • 45 forks • GPL-3.0 Python tools for interacting with Balancer Protocol V2, providing Pythonic interfaces for pool queries, swaps, and analytics. **Repository:** https\://github.com/balancer/balpy\ **Primary Use:** Python integration library ##### [code-review](https://github.com/balancer/code-review) **Status:** Actively maintained (December 2024) • 20 stars • 14 forks Code review tools and utilities for Balancer smart contract development, including linting and static analysis configurations. **Repository:** https\://github.com/balancer/code-review\ **Primary Use:** Development tooling and code quality #### Backend Services ##### [backend](https://github.com/balancer/backend) **Status:** Actively maintained (December 2024) • 46 stars • 22 forks • MIT Backend services for the Balancer protocol, including API endpoints, data aggregation, and off-chain computations for the UI. **Repository:** https\://github.com/balancer/backend\ **Primary Use:** API and backend services #### Deployment & Infrastructure ##### [balancer-deployments](https://github.com/balancer/balancer-deployments) **Status:** Actively maintained (December 2024) • 68 stars • 57 forks • GPL-3.0 Comprehensive collection of deployed contract addresses, ABIs, and deployment artifacts across all networks for both V2 and V3. Essential reference for integrators. **Repository:** https\://github.com/balancer/balancer-deployments\ **Primary Use:** Contract addresses and ABIs reference ##### [gauges-subgraph](https://github.com/balancer/gauges-subgraph) **Status:** Actively maintained (December 2024) • 3 stars • 18 forks Subgraph for tracking Balancer liquidity gauge data, including veBAL voting, gauge weights, and rewards distribution. **Repository:** https\://github.com/balancer/gauges-subgraph\ **Primary Use:** Gauge and incentive tracking ##### [data-checks](https://github.com/balancer/data-checks) **Status:** Active (May 2024) • 0 stars • 1 fork Data validation and integrity checking tools for Balancer infrastructure. **Repository:** https\://github.com/balancer/data-checks\ **Primary Use:** Data validation utilities #### Metadata & Configuration ##### [metadata](https://github.com/balancer/metadata) **Status:** Actively maintained (December 2024) • 2 stars • 28 forks Public metadata repository for Balancer protocol, including token lists, pool metadata, logos, and configuration data. **Repository:** https\://github.com/balancer/metadata\ **Primary Use:** Protocol metadata management ##### [tokenlists](https://github.com/balancer/tokenlists) **Status:** Actively maintained (December 2024) • 35 stars • 265 forks • MIT Curated token lists for the Balancer protocol, following the TokenLists standard for verified tokens across networks. **Repository:** https\://github.com/balancer/tokenlists\ **Primary Use:** Token list management ##### [blocklist](https://github.com/balancer/blocklist) **Status:** Maintained (July 2025) • 0 stars • 0 forks Addresses and entities blocked from interacting with Balancer frontends for compliance and security purposes. **Repository:** https\://github.com/balancer/blocklist\ **Primary Use:** Compliance management ##### [brand-assets](https://github.com/balancer/brand-assets) **Status:** Active (March 2024) • 5 stars • 9 forks Official Balancer brand assets including logos, colors, and design guidelines. **Repository:** https\://github.com/balancer/brand-assets\ **Primary Use:** Brand and marketing assets ##### [assets](https://github.com/balancer/assets) **Status:** Active (June 2023) • 27 stars • 82 forks Token and pool asset management, including icons and metadata for frontend display. **Repository:** https\://github.com/balancer/assets\ **Primary Use:** Token icons and visual assets #### Subgraphs & Data ##### [dune-spellbook](https://github.com/balancer/dune-spellbook) **Status:** Maintained (June 2024) • 7 stars • 1.4k forks • Other SQL views and spells for Dune Analytics to query Balancer protocol data. **Repository:** https\://github.com/balancer/dune-spellbook\ **Primary Use:** Dune Analytics integration #### Governance & DAO ##### [snapshot-spaces](https://github.com/balancer/snapshot-spaces) **Status:** Forked (May 2021) • 1 star • 2k forks • MIT Forked Snapshot Spaces configuration for Balancer governance voting. **Repository:** https\://github.com/balancer/snapshot-spaces\ **Primary Use:** Governance voting configuration #### Cross-chain & Bridges ##### [lz-v1-endpoint](https://github.com/balancer/lz-v1-endpoint) **Status:** Active (July 2024) • 0 stars • 547 forks LayerZero V1 endpoint contracts for BAL token bridge infrastructure enabling cross-chain BAL transfers. **Repository:** https\://github.com/balancer/lz-v1-endpoint\ **Primary Use:** BAL token bridging ##### [lz\_gauges](https://github.com/balancer/lz_gauges) **Status:** Active (August 2023) • 0 stars • 18 forks LayerZero-based gauge contracts for cross-chain liquidity incentives. **Repository:** https\://github.com/balancer/lz\_gauges\ **Primary Use:** Cross-chain gauges #### Utilities ##### [permit2](https://github.com/balancer/permit2) **Status:** Forked (March 2024) • 0 stars • 269 forks • MIT Forked Uniswap Permit2 contracts for next-generation token approvals mechanism. **Repository:** https\://github.com/balancer/permit2\ **Primary Use:** Token approval infrastructure *** ## Legacy Repositories These are repos associated with past versions of Balancer or discontinued products. ### Balancer V1 #### [balancer-core](https://github.com/balancer/balancer-core) **Status:** Legacy (June 2024) • 340 stars • 166 forks • GPL-3.0 Original Balancer V1 protocol implementation (Bronze release, 2020). Contains the core pool logic and factory contracts for the first version of Balancer. Emphasized code clarity for audit and verification. **Repository:** https\://github.com/balancer/balancer-core\ **Primary Use:** Historical reference - V1 (deprecated) **Note:** V1 launched in 2020. The protocol planned three releases (Bronze, Silver, Gold), though only Bronze was fully realized before V2 development began in 2021. #### [balancer-sor-v1](https://github.com/balancer/balancer-sor-v1) **Status:** Legacy (August 2021) • 3 stars • 6 forks Legacy Smart Order Router for Balancer V1 pools, providing routing optimization for V1. **Repository:** https\://github.com/balancer/balancer-sor-v1\ **Primary Use:** Historical reference - V1 SOR #### [balancer-subgraph](https://github.com/balancer/balancer-subgraph) **Status:** Legacy (December 2021) • 70 stars • 58 forks • MIT Original subgraph for Balancer V1, tracking pools, swaps, transactions, and users on V1. **Repository:** https\://github.com/balancer/balancer-subgraph\ **Primary Use:** Historical reference - V1 subgraph #### [frontend-v1](https://github.com/balancer/frontend-v1) **Status:** Legacy (October 2024) • 74 stars • 71 forks • GPL-3.0 V1 pool management interface for creating and managing V1 pools. **Repository:** https\://github.com/balancer/frontend-v1\ **Primary Use:** Historical reference - V1 UI #### [swap-frontend-v1](https://github.com/balancer/swap-frontend-v1) **Status:** Legacy (May 2021) • 21 stars • 41 forks • GPL-3.0 Original swap interface for Balancer V1 protocol. **Repository:** https\://github.com/balancer/swap-frontend-v1\ **Primary Use:** Historical reference - V1 swap UI #### [docs-v1](https://github.com/balancer/docs-v1) **Status:** Legacy (November 2021) • 13 stars • 37 forks Documentation for Balancer V1 protocol. **Repository:** https\://github.com/balancer/docs-v1\ **Primary Use:** Historical reference - V1 docs #### [balancer-registry](https://github.com/balancer/balancer-registry) **Status:** Legacy (February 2021) • 14 stars • 25 forks Registry contracts for V1 pool discovery and metadata. **Repository:** https\://github.com/balancer/balancer-registry\ **Primary Use:** Historical reference - V1 registry #### [exchange-proxy](https://github.com/balancer/exchange-proxy) **Status:** Legacy (November 2020) • 21 stars • 28 forks • GPL-3.0 Exchange proxy contract for V1 multi-hop trading. **Repository:** https\://github.com/balancer/exchange-proxy\ **Primary Use:** Historical reference - V1 proxy #### [configurable-rights-pool](https://github.com/balancer/configurable-rights-pool) **Status:** Legacy (October 2020) • 34 stars • 25 forks • GPL-3.0 Smart pool implementation with configurable rights for V1. **Repository:** https\://github.com/balancer/configurable-rights-pool\ **Primary Use:** Historical reference - V1 smart pools #### [bactions-proxy](https://github.com/balancer/bactions-proxy) **Status:** Legacy (May 2021) • 10 stars • 14 forks • GPL-3.0 Batch actions proxy for efficient V1 pool operations. **Repository:** https\://github.com/balancer/bactions-proxy\ **Primary Use:** Historical reference - V1 batch actions #### [balancer-examples](https://github.com/balancer/balancer-examples) **Status:** Template (June 2022) • 10 stars • 10 forks • Template badge Template repository with various examples of how to integrate with Balancer V2 by interacting with existing pools and designing custom pool types. Includes liquidity provision examples and pool interaction patterns. **Repository:** https\://github.com/balancer/balancer-examples\ **Primary Use:** V2 integration examples and templates #### [metastable-rate-providers](https://github.com/balancer/metastable-rate-providers) **Status:** Maintained (January 2025) • 1 star • 17 forks • GPL-3.0 Rate provider adaptors used by MetaStable pools to interface with various yield-bearing tokens and protocols. **Repository:** https\://github.com/balancer/metastable-rate-providers\ **Primary Use:** MetaStable pool rate provider integrations #### [frontend-v2](https://github.com/balancer/frontend-v2) **Status:** Maintained (August 2025) • 195 stars • 296 forks • MIT Previous version of the Balancer frontend application for V2 protocol. Now superseded by frontend-monorepo but still maintained for legacy support. **Repository:** https\://github.com/balancer/frontend-v2\ **Primary Use:** Legacy V2 UI ##### [balancer-sdk](https://github.com/balancer/balancer-sdk) **Status:** Maintained (November 2024) • 83 stars • 95 forks • MIT Previous SDK providing access to methods and utilities for interacting with Balancer V2 Protocol. Now being migrated to b-sdk. **Repository:** https\://github.com/balancer/balancer-sdk\ **Primary Use:** TypeScript integration library (legacy) ### Deprecated / Archived #### [docs-v2-archive](https://github.com/balancer/docs-v2-archive) **Status:** Archived (February 2023) • 7 stars • 13 forks Archived version of V2 documentation, superseded by current docs repository. **Repository:** https\://github.com/balancer/docs-v2-archive\ **Primary Use:** Historical V2 documentation #### [docs-developers](https://github.com/balancer/docs-developers) **Status:** Archived (January 2023) • 15 stars • 21 forks Archived developer documentation, now integrated into main docs. **Repository:** https\://github.com/balancer/docs-developers\ **Primary Use:** Historical developer docs #### [DeFi-Pulse-Adapters](https://github.com/balancer/DeFi-Pulse-Adapters) **Status:** Forked (June 2020) • 2 stars • 501 forks • AGPL-3.0 Forked DeFi Pulse adapters for tracking Balancer TVL metrics. **Repository:** https\://github.com/balancer/DeFi-Pulse-Adapters\ **Primary Use:** TVL tracking (historical) #### [sor-benchmark](https://github.com/balancer/sor-benchmark) **Status:** Archived (November 2023) • 1 star • 0 forks Benchmarking tools for testing and comparing Smart Order Router performance across different scenarios. **Repository:** https\://github.com/balancer/sor-benchmark\ **Primary Use:** SOR performance testing #### [frontend-e2e](https://github.com/balancer/frontend-e2e) **Status:** Archived (May 2023) • 2 stars • 4 forks End-to-end testing suite for frontend applications with automated test scenarios. **Repository:** https\://github.com/balancer/frontend-e2e\ **Primary Use:** Frontend testing automation #### [helpers](https://github.com/balancer/helpers) **Status:** Archived (June 2023) • 1 star • 1 fork Collection of helper contracts and JavaScript code for common data dependencies in Balancer development. **Repository:** https\://github.com/balancer/helpers\ **Primary Use:** Utility functions and helpers #### [balancer-api](https://github.com/balancer/balancer-api) **Status:** Deprecated (August 2024) • 17 stars • 15 forks • MIT • **DEPRECATED** Previous API implementation, now deprecated in favor of the current backend services. **Repository:** https\://github.com/balancer/balancer-api\ **Primary Use:** Legacy API (deprecated) #### [balancer-rewards-api](https://github.com/balancer/balancer-rewards-api) **Status:** Archived (October 2021) • 5 stars • 11 forks API for querying Balancer rewards and incentive distributions (historical). **Repository:** https\://github.com/balancer/balancer-rewards-api\ **Primary Use:** Historical rewards API #### [authorizer-subgraph](https://github.com/balancer/authorizer-subgraph) **Status:** Archived (March 2023) • 0 stars • 3 forks Subgraph for tracking Balancer protocol authorizer and permission management. **Repository:** https\://github.com/balancer/authorizer-subgraph\ **Primary Use:** Authorization tracking #### [subgraph-split-testing](https://github.com/balancer/subgraph-split-testing) **Status:** Archived (August 2022) • 1 star • 103 forks • MIT Experimental split version of the Balancer V2 subgraph separating core and analytical data. **Repository:** https\://github.com/balancer/subgraph-split-testing\ **Primary Use:** Subgraph architecture testing #### [dune\_queries](https://github.com/balancer/dune_queries) **Status:** Archived (September 2021) • 2 stars • 6 forks Collection of SQL queries for analyzing Balancer data on Dune Analytics. **Repository:** https\://github.com/balancer/dune\_queries\ **Primary Use:** Analytics queries #### [bal-mining-scripts](https://github.com/balancer/bal-mining-scripts) **Status:** Archived (December 2023) • 86 stars • 81 forks • GPL-3.0 Scripts for BAL token mining and liquidity mining reward calculations (historical program). **Repository:** https\://github.com/balancer/bal-mining-scripts\ **Primary Use:** Historical liquidity mining #### [erc20-redeemable](https://github.com/balancer/erc20-redeemable) **Status:** Archived (April 2023) • 81 stars • 38 forks • Vue Interface for redeeming ERC20 tokens, used for historical token distributions. **Repository:** https\://github.com/balancer/erc20-redeemable\ **Primary Use:** Token redemption UI #### [balancer-bounties](https://github.com/balancer/balancer-bounties) **Status:** Archived (September 2021) • 3 stars • 15 forks • Vue Previous bug bounty platform interface hosted at bounties.balancer.finance (now on Immunefi). **Repository:** https\://github.com/balancer/balancer-bounties\ **Primary Use:** Historical bounty platform #### [projects](https://github.com/balancer/projects) **Status:** Archived (August 2021) • 2 stars • 149 forks Project management and coordination repository. **Repository:** https\://github.com/balancer/projects\ **Primary Use:** Project tracking #### [opco-monorepo](https://github.com/balancer/opco-monorepo) **Status:** Archived (November 2022) • 0 stars • 0 forks Operating company monorepo for organizational infrastructure. **Repository:** https\://github.com/balancer/opco-monorepo\ **Primary Use:** Internal operations #### [linear-pools](https://github.com/balancer/linear-pools) **Status:** Archived (June 2023) • 7 stars • 6 forks • GPL-3.0 Linear pool implementations for yield-bearing tokens, allowing integration with lending protocols. **Repository:** https\://github.com/balancer/linear-pools\ **Primary Use:** Linear pool reference #### [scaffold-balancer](https://github.com/balancer/scaffold-balancer) **Status:** Archived (May 2023) • 5 stars • 7 forks • MIT Previous scaffold template for Balancer V2 development, superseded by scaffold-balancer-v3. **Repository:** https\://github.com/balancer/scaffold-balancer\ **Primary Use:** V2 scaffold (superseded) #### [ipfs-push](https://github.com/balancer/ipfs-push) **Status:** Archived (September 2021) • 3 stars • 11 forks Utilities for pushing content to IPFS for decentralized storage. **Repository:** https\://github.com/balancer/ipfs-push\ **Primary Use:** IPFS deployment tools #### [frontend-v3](https://github.com/balancer/frontend-v3) **Status:** Archived (October 2024) • 8 stars • 12 forks • MIT Deprecated frontend application that was moved into the frontend-monorepo. **Repository:** https\://github.com/balancer/frontend-v3\ **Primary Use:** Historical - superseded by frontend-monorepo #### [yield-tokens](https://github.com/balancer/yield-tokens) **Status:** Archived (August 2024) • 0 stars • 2 forks Service for getting yield-bearing tokens APRs. **Repository:** https\://github.com/balancer/yield-tokens\ **Primary Use:** Historical yield token APR service #### [b-sdk-api](https://github.com/balancer/b-sdk-api) **Status:** Archived (September 2023) • 0 stars • 0 forks Bridge service between Balancer API and b-sdk package. **Repository:** https\://github.com/balancer/b-sdk-api\ **Primary Use:** Historical API bridge #### [pebbles](https://github.com/balancer/pebbles) **Status:** Archived (January 2023) • 3 stars • 10 forks • HTML Experimental "grug stack" project. **Repository:** https\://github.com/balancer/pebbles\ **Primary Use:** Historical experimental project #### [balancer-exchange](https://github.com/balancer/balancer-exchange) **Status:** Archived (March 2021) • 95 stars • 102 forks • GPL-3.0 Exchange dapp for token swaps on Balancer V1/V2. **Repository:** https\://github.com/balancer/balancer-exchange\ **Primary Use:** Historical exchange interface #### [pool-management](https://github.com/balancer/pool-management) **Status:** Archived (August 2020) • 22 stars • 42 forks • GPL-3.0 Pool management dapp for creating and managing pools. **Repository:** https\://github.com/balancer/pool-management\ **Primary Use:** Historical pool management interface *** ## Repository Statistics ### By Version: * **V3 Repositories:** 10 core development + 7 templates = **17 total** * **V2 Repositories:** 3 core + SOR + 3 maintained legacy = **6 total** * **V1 Repositories:** **10 legacy** (deprecated, historical reference only) * **Shared/Infrastructure:** **32 repos** (frontend, SDK, backend, deployment, metadata, governance, cross-chain, utilities) * **Archived/Deprecated:** **26 repos** **Total: 82 public repositories** ### By Category: * **Forkable Templates:** 7 repositories (6 V3 + 1 V2) * **Core Protocol (V2 + V3):** 14 repositories (10 V3 + 3 V2 + 1 SOR) * **CoW Protocol Integration:** 2 repositories * **Frontend Applications:** 3 active + 2 archived = 5 repositories * **SDK & Developer Tools:** 3 active + 1 archived = 4 repositories * **Backend & API:** 1 active + 1 archived = 2 repositories * **Subgraphs & Analytics:** 3 repositories (v2, v3, dune) * **Deployment & Infrastructure:** 3 repositories * **Metadata & Assets:** 5 repositories * **Governance & DAO:** 1 repository * **Cross-chain & Bridges:** 2 repositories * **Utilities:** 2 repositories (permit2 + 1 archived) * **Legacy V1:** 10 repositories * **Legacy V2 (maintained but superseded):** 3 repositories * **Archived/Deprecated:** 26 repositories ### By Development Activity: * **Highly Active (Dec 2024 updates):** \~18 repositories * **Active (2024-2025 updates):** \~24 repositories * **Maintained but Legacy (V2/superseded):** 3 repositories * **V1 Legacy (no development):** 10 repositories * **Archived/Deprecated (no development):** 26 repositories *** ## Version Timeline * **V1:** Launched February 2020 (Bronze release) * Groundbreaking constant function market maker * Up to 8 tokens per pool with flexible weights * Now deprecated (no UI) * **V2:** Launched April 2021 * Introduced the Vault architecture * Protocol-level flash loans * Asset Managers for capital efficiency * Currently in production alongside V3 (BAL and LM architecture only) * **V3:** Launched December 2024 * Complexity moved out of the pools to the Vault (3 contracts using the Proxy pattern) * Hooks system for extensibility * Improved gas efficiency * Native yield support (ERC4626) * Enhanced router architecture * Currently the primary focus of development *** ## Notes for Integrators 1. **For New Projects:** Start with V3 using scaffold-balancer-v3 template 2. **For V2 Integration:** Use b-sdk or balancer-sdk with balancer-v2-monorepo as reference 3. **For Smart Contracts:** Reference balancer-deployments for current addresses across all networks 4. **For Data Queries:** Use appropriate subgraph (v2 or v3) or Dune Analytics integration 5. **For Pool Math:** balancer-maths provides reference implementations and test cases 6. **For Routing:** balancer-sor for V2, integrated router for V3 *** ## Template Strategy Templates (marked with template badge or in templates section) are specifically designed to be forked and customized by developers building on Balancer. They include: * Complete working examples * Testing infrastructure * Deployment scripts * Documentation * Frontend integration (where applicable) Core repositories are maintained by the Balancer team for protocol development and should be referenced but not directly forked for custom development. *** ## Monorepo Structure Both V2 and V3 use monorepo structures: * **V2 Monorepo:** Contains vault, multiple pool types, helpers, solidity utils * **V3 Monorepo:** Contains vault, pools, routers, hooks, utilities Each package within the monorepos is independently versioned and published to npm. *** ## Documentation Strategy Separate documentation repositories exist for each major version: * **V1:** docs-v1 (archived) * **V2:** docs (maintained at docs.balancer.fi) * **V3:** docs-v3 (active at docs-v3.balancer.fi) This reflects the architectural differences between versions and allows parallel documentation maintenance. *** *Last updated: December 2025*\ *Repository count: 82 public repositories*\ *Based on: GitHub organization scan and repository metadata* ## Integration Guides This sections contains useful guides for common actions such as adding/removing liquidity and making swaps using the SDK. Individuals are encouraged to contact our developers via [Discord](https://discord.balancer.fi/) to request further assistance. For more detailed technical documentation including interface APIs, contract ABIs and deployments please see [Developer References](../developer-reference/sdk/README.md). ### Tools Hub *** These tools have been developed by various teams contributing to the Balancer Ecosystem or wider DeFi through grants, as general contributors or entirely separate platforms. This hub serves as an open source repo of these tools created to help users and devs navigate, analyze and interact with the Balancer Ecosystem. This is continuously growing, so if you have something you think should be on this page, get in touch and add your project to the [open source repo here](https://github.com/balancer/docs/tree/main/docs/tools) for it to be added! ## Architecture ### Core components The Balancer protocol architecture comprises three primary components, each strategically designed to enhance flexibility and minimize the intricacies involved in constructing pools. By distributing responsibilities across these components, Balancer simplifies pool development, empowering builders to focus on innovation rather than grappling with complex code. * Router: Serves as the user's gateway to the protocol, offering straightforward interfaces for executing operations. (This includes the basic Router, BatchRouter, BufferRouter, and CompositeLiquidityRouter.) * Vault: Centralizes liquidity operations and manages accounting, streamlining the handling of token balances across multiple pools. * Pool: Exposes precise pool mathematics through invariant calculations, enabling developers to harness powerful functionalities without delving into intricate details. This design philosophy ensures that building pools on Balancer is intuitive and efficient, with the vault shouldering the burden of complexity, allowing pool creators to unleash their creativity without worrying about accounting details. The Vault is part of the core protocol, but users can build custom pools and routers. ### Simplified transaction flow The following diagram illustrates the usage of each component during a transaction. ![Router Vault interaction](/images/architecture-simplified.png) 1. The [Router](/docs/concepts/router/technical.html) acts as the primary interface for accessing the Balancer protocol, offering a user-friendly way to interact with the Balancer Vault. 2. The Router unlocks the Vault, which allows it to continue by recording all credits and debts generated by operations like adding liquidity, removing liquidity, and swaps. 3. The Vault processes the operations instructed by the Router, usually forwarding requests to pools. For example, in the case of a swap, the pool's invariant, which is defined in the pool contract, determines the quantity of tokens being either deposited (`EXACT_OUT` swap) in the Vault or withdrawn (`EXACT_IN` swap) from the Vault. Once the required token amounts have been calculated, these amounts are recorded as either credit or debt in the Vault. 4. The Vault returns back the operation results to the Router. 5. The Router settles debts and credits by sending tokens to the Vault to pay debts, and pulling tokens out of the Vault to clear out credits. The Vault verifies that the Router has correctly settled its accrued debts and credits properly. The transaction will only succeed if every single debit and credit has been accurately settled; otherwise, it will be reverted. ### Detailed overview ![Detailed Router Vault interaction](/images/architecture-detailed.png) 1. The Balancer Router is the main interface for interacting with Balancer, providing a user-friendly way to access functions and simplify interactions with the Vault. Any smart contract can serve as a Router, tailored to the specific use case. For instance, hooks and pools can "act" as routers and make calls on the Vault. It's also possible to create custom routers to combine Vault operations in novel ways (e.g., interact with a custom protocol). 2. The Router calls the Vault's `unlock` method and opens up the vault to record debts and credits based on liquidity or swap operations. This allows operations on the Vault to be combined atomically and still ensure correct accounting. 3. With the Vault unlocked, the Vault calls back into the Router. This is where arbitrary logic can be executed, including any combination of calls to Vault primitives, interacting with external protocols, etc. For example, in the case of a swap action, the Vault calls the Router's specific action hook implementation, such as `swapSingleTokenHook`, and passes the initial function payload from step 1 back to the Router to continue the regular transaction flow. 4. At this stage, any address gains authorization to invoke the Vault's functions that require the Vault to be unlocked identifier by the `onlyWhenUnlocked` modifier. This mechanism guarantees the accurate allocation of debt and credit: the Vault will track how many tokens of each kind are owed to it (debt), and how many tokens can be pulled out of it (credit). The inputs from step 1 are passed to the Vault's core functions, such as swap. 5. Each primitive operation performed on the Vault might trigger function calls on pools to calculate the result of the operation: * `onSwap` for swaps * `calculateInvariant` or `computeBalance` for liquidity management operations (add / remove), depending on the operation kind. These functions calculate the tokens that need to be deposited into or withdrawn from the Vault. Proportional liquidity management operations are an exception to this rule: the math is solved at the Vault level in this case. 6. The outcomes of these calculations are categorized as either debts or credits, which must be settled at a later stage. Additionally, in the case of liquidity management operations, the required amount of Balancer Pool Tokens (BPT) is minted or burned. 7. After debts and credits have been recorded and shared with the Router by the Vault, the execution flow is passed to the Router. This allows the Router to be aware of the amounts owed (both by the sender to the Vault, and vice-versa). It is important to mention that the Router contract has the ability to retrieve the current debts and credits associated with the Vault at any point during the execution by calling a specific function on the Vault. 8. The Router is responsible for settling the remaining debts and credits, which must be done for the transaction to succeed. If ETH or WETH is to be used in the transaction, the Router wraps or unwraps Ether right before settling WETH debts. 9. When the router is done with settlement, the Vault verifies that all credits and debts accrued during the operations have been settled as the final step. Once the verification is complete, the Vault is locked again. If all debt has been correctly settled, the transaction will succeed; otherwise, it will be reverted. On top of the basic workflow, pools can be extended with standalone hooks contracts that can be leveraged at different stages of the pool's lifecycle. These hooks contracts can be called either before or after a pool operation (i.e. step 5), depending on how the pool is configured during deployment. By utilizing hooks, developers can customize and enhance the functionality of pools, enabling the integration of features like oracles or time-weighted average market maker capabilities. See [hooks](./hooks.md) article for more detailed information. ## Balancer Pool Token Implementation Balancer Pool Tokens are tokens that represent ownership or shares in a Balancer pool. When users add liquidity to a Balancer pool by depositing tokens, they receive corresponding Balancer Pool Tokens in return. These tokens represent their proportional share of the liquidity pool. Balancer Pool Tokens are dynamic in nature, meaning their value can fluctuate based on changes in the composition of the pool. They represent ownership of the assets within the pool and entitle holders to a portion of the trading fees generated by the pool, proportional to their share. Users can redeem their Balancer Pool Tokens to withdraw their share of the underlying assets from the pool. ### BalancerPoolToken Contract The BalancerPoolToken contract adheres to the ERC20 token standard by incorporating the necessary methods and properties. **However, it does not directly manage the state of the token. Instead, it delegates these responsibilities to the Vault contract, which is an instance of [ERC20MultiToken](../vault/erc20-multi-token.md).** This design choice centralizes the accounting and management of tokens, facilitating atomic updates to critical pool states. Here's how the [`BalancerPoolToken`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol) contract achieves this: Inheritance: The [`BalancerPoolToken`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol) contract also inherits from `IERC20`, `IERC20Metadata`, `IERC20Permit`, `IRateProvider`, `EIP712`, `Nonces`, `ERC165` and our local `VaultGuard`. This means it has all the methods and properties required by the ERC20 standard. * Delegation: The BalancerPoolToken contract doesn't manage the token state itself. Instead, it delegates this responsibility to the Vault contract. For example, the totalSupply, balanceOf, transfer, allowance, approve, and transferFrom methods all call the corresponding methods on the Vault contract. * ERC20 Events: The BalancerPoolToken contract emits the Transfer and Approval events, which are required by the ERC20 standard. These events are emitted in the emitTransfer and emitApproval methods, which can only be called by the Vault contract on the pool contract. * ERC20Permit: The BalancerPoolToken contract also implements the ERC20 Permit extension, which allows approvals to be made via signatures. This is done in the permit method, which again delegates the approval to the Vault contract. * IRateProvider: Each pool can serve as a rate provider, with a natural "BPT Rate" = pool value (= invariant) / totalSupply. However, great care must be taken when using pool tokens this way, as rates may be unpredictable or manipulable. In general, since the rate calculation does not include a "rounding hint" (as does the base invariant calculation), if there is any significant or non-linear error in the invariant, the computed value of the invariant might go down when it should go up. Weighted Pool invariants have this property, so calling `getRate` on a Weighted Pool is unsafe, and reverts unconditionally. * EIP712, Nonces: These have to do with supporting permit2 and signatures, so that explicit token approvals can be avoided in many cases. * ERC165: This is not used in core pool types, but allows interface detection. See the [Ethereum docs](https://eips.ethereum.org/EIPS/eip-165). * VaultGuard: This simply stores a reference to the Vault and defines the `onlyVault` modifier for functions that can only be called by the Vault. By doing this, the BalancerPoolToken contract ensures that Balancer Pool Tokens (BPTs) are fully ERC20 compliant, while also allowing the Vault contract to have full control over BPT accounting. This design ensures atomic updates to critical pool state and supports composability, which is crucial for integration with other DeFi protocols. ### Composability As BPTs adhere to the ERC20 standard, they can seamlessly integrate as pool tokens in other pools. For instance, the BPT of an ERC4626 pool comprising wrapped versions of DAI, USDC, and USDT can be paired with tokens from new projects. This composability ensures the maintenance of deep and capital-efficient stable liquidity, while simultaneously creating efficient swap paths for the project token. ### Oracles If Chainlink price feeds are available for all tokens, Weighted and Stable Pool BPTs can be priced in USD terms using the corresponding LP Oracle contracts: [WeightedLPOracle](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/oracles/contracts/WeightedLPOracle.sol) and [StableLPOracle](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/oracles/contracts/StableLPOracle.sol), which implement [this interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/oracles/ILPOracleBase.sol) as well as the `AggregatorV3Interface` Chainlink price feed interface. This allows users to call `latestRoundData` on the oracle contract to get an accurate, non-manipulable BPT price, just as if it were a Chainlink oracle itself. There is also an oracle for Gyro E-CLP pools: [EclpLPOracle](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/oracles/contracts/EclpLPOracle.sol). Note that it would be possible to generalize these contracts to support other kinds of price feeds; the only function of the price oracle is to fetch the current market prices. The pricing algorithm and all logic is contained in the LP oracle code (and mostly all in the base contracts). The most common use for these contracts is enabling Balancer BPT to be used as collateral on lending platforms. See the [BPT as Collateral](./bpt-oracles/bpt-oracles.md) docs for more details. As described on that page, great care must be taken when using BPT in this way, outside Balancer. Since transient operations allow an "infinite BPT mint" during a transaction, without safeguards lending protocols could potentially be manipulated through huge BPT deposits during a batch transaction. Current protocols impose deposit limits, which mitigate this risk. If new protocol lack such caps natively, the most recent versions of oracles enable an immutable flag which, if set on deployment, makes all TVL-related calls revert when the Vault is unlocked. This ensures that the BPT are "real," and not transient. It would still be possible to flashloan BPT from another source, but the maximum amount would be limited to the actual liquidity available. There is also a wrapped form of BPT [WrappedBalancerPoolToken](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/WrappedBalancerPoolToken.sol), which can only be minted while the Vault is locked, similarly preventing the use of transient BPT. The oracle use case is well covered by the combination of safeguards in the lending protocol and the oracle contract flag; WrappedBPT would not be needed there, but is available in case other use cases arise. ## Concentrated Liquidity ### Introduction In traditional automated market makers (AMMs), liquidity providers (LPs) deposit tokens across the entire possible price range: i.e., from $0 to ∞ for ETH/USDC. Yet in practice, most trading happens within a narrow price range that shifts relatively slowly along with the market price. That means most of the liquidity sits idle, earning no fees for LPs. Concentrated liquidity cuts off the "long tail" of increasingly unlikely price points, and lets LPs allocate their liquidity to a restricted range. When the market price is within that range (say, $1,500 - $2,500), the liquidity is "deeper" there, which has positive effects for both retail traders and LPs. Traders get the benefit of trading in a pool with effectively higher liquidity, enabling larger trades with less slippage. For the same reason, LPs earn more fees per dollar invested from the increased trade volume. Imagine liquidity as the “water level” in a pool. Providing liquidity over the full range is like filling the whole pool uniformly: all LPs participate equally, with the total value (and fee revenue) effectively the same for each LP, regardless of price. Concentrated liquidity is like adding dividers to section the pool into shallow and deep areas. Instead of just dumping their liquidity into one big Olympic-sized space, they can effectively pour it into just one part of the pool, so that the “water” is much deeper there: deep enough for even the whales to trade freely. And since the fees are also proportional to the “height” of the water, those who contributed to the “deep end” earn a higher portion of the total fees. To quantify the degree of concentration (sometimes called the "gain"), we must first define a reference range. Theoretically the "full range" would be zero to infinity, but we cannot actually use that, as neither the pool math nor the analytical math formula can handle zeroes or infinities. For the ETH price, the all-time recorded low was around 43 cents, and the all-time recorded high was over $4,800. So a good "reference range" would be $0.50 to \~10x the ATH, or $50,000. This gives a full range ratio of 50,000 / 0.5 = 100,000. The ratio of this reference range to a smaller concentrated range (in log space) provides a way to quantify the degree of concentration. $concentration = \frac{ln(ReferenceRangeRatio)}{ln(ConcentratedRangeRatio)}$ ​ Restricting the range to $1,000 - $4,000, we have: $concentration = \frac{ln(100,000)}{ln(4,000/1,000)}$ \~ 8.3x ![Concentrated liquidity illustration](/images/eth-concentrated.gif) Of course, these benefits come at a cost. If the price moves outside the defined range, LPs allocated to that range stop earning fees. That might mean all of them or only a subset, depending on how concentrated liquidity is implemented on a particular protocol. For pools with a single defined range shared by all LPs, trading through that pool might be halted. For example, say an LP opens a position in the pool described above when ETH is trading at $2,000, using 50/50 ETH/USDC. If the ETH price dropped below the lower bound of the range (say, to $1,200), that LP's position would be 100% ETH - which was "bought high" between $1,500 and $2,000. Conversely, if the ETH price rose to $3,000, an LP liquidating that position would get 100% USDC - which was "sold low" between $2,000 and $2,500. ![Range trading fees illustration](/images/liquidity-range-fees.gif) The concentrated liquidity approach improves capital efficiency (more fees for less capital), and lets pool designers (and in some cases the LPs themselves) tailor their strategies. But it also introduces the need for active position management - particularly with volatile assets or narrow price ranges. In some protocols, LPs can adjust their positions directly. In others, they might need to migrate between pools. AMM design generally - and concentrated liquidity implementation in particular - is a hot topic in DeFi, and has inspired much innovation. The following sections trace the part of that history most relevant to the development of "fungible" concentrated liquidity solutions on Balancer. ### Non-fungible (NFT-based) Concentrated Liquidity #### Rise of the Unicorn There are two main approaches to implementing concentrated liquidity. The first was pioneered by Uniswap v3 in 2021. This is the most flexible approach in some ways, and certainly allows the most granular control over liquidity. With this approach, each LP individually decides the price range over which they would like to provide liquidity, and receives an NFT token with these configuration details. LPs can adjust their positions over time to stay "in range" and continue earning fees. Since most pools are supplied by many LPs with diverse price ranges, they retain some liquidity even during large price swings in volatile markets. Of course, there is a natural trade-off here between potential revenue and position maintenance. The narrower the price range, the higher the fees. However, a narrow range also means the price is more likely to move beyond it, requiring more frequent position adjustments. Otherwise, frequent "no fee" periods could make the position underperform those with wider ranges. Uniswap uses a "tick" system to manage the granularity of liquidity provision. To ensure smooth pool operation (and well-behaved math), user-defined price ranges cannot be arbitrarily narrow. A tick corresponds to one "basis point" (i.e., a price range where the difference between the bounds is 0.01%). These are like the distance marks along the bottom of a swimming pool. Continuing the analogy from above, the liquidity "dividers" can only be placed on those marks. Uniswap pools also have discrete fees, and to keep fee allocation fairly uniform, higher fees mean wider tick spacing. At the lowest fee tier (0.01%), a tick simply equals one basis point, and liquidity can be provided at that level of granularity. At the highest tier (1%), the resolution is 100 basis points. ![Non-fungible CL illustration](/images/cl-illustration.png) Though we at Balancer, noting the importance and prominence of concentrated liquidity in Defi, (very) briefly considered trying to support this sort of liquidity in the v3 Vault, we quickly realized that this was incompatible with our long-term goals. Balancer is optimized for fungibility, with a focus on native support for yield-bearing tokens and "long-tail" liquidity, so we stayed in our swim lane — between the ticks, as it were. In the NFT-based concentrated liquidity space, the Unicorn stands alone. ### Fungible (LP-token-based) Concentrated Liquidity #### Concentrated Liquidity on Balancer - Gyroscope Pools [Gyro pools](../explore-available-balancer-pools/gyroscope-pool/README.md) take a different approach to concentrated liquidity. First, it is “fungible." Instead of NFTs, users receive regular AMM LP tokens. This makes positions composable, and plays well with the rest of Balancer and other AMMs. It is a sort of specialization of Uniswap v3, which gives up the generality and precision of the tick system in exchange for a simpler pool architecture. 2-CLP pools are a bit like a Uniswap v3 pool where everyone added liquidity in exactly the same range (and couldn’t update it). Since the alpha and beta parameters cannot be changed after deployment, there is no position management within pools; users wishing to reallocate must withdraw from one pool and deposit to another in a different range. Accordingly, the price ranges tend to be somewhat wider and require less frequent attention, in exchange for somewhat lower (but still significant) benefits of concentration. E-CLPs are likewise efficient liquidity concentrators, so named because their price curve is an ellipse, technically formed by transforming a “constant circle” with stretch (lambda), rotation (phi), and displacement (alpha, beta) parameters. Like 2-CLPS, these parameters are fixed on deployment. Used for stable assets (such as Gyroscope’s own GYD), they “stretch” the flat part of the curve around a price peg, but not necessarily symmetrically. This allows very precise control or “focus” around the target price. When using with yield-bearing assets and rate providers, the precision can be increased even further, helping mitigate LVR (loss vs. rebalancing), and automating liquidity management. Performance can be further enhanced with “re-hype” E-CLPS (using auto-rehypothecation - basically, depositing underlying assets supplied to E-CLPs on lending markets). The latest versions allow shifting some parameters, so that they are no longer fixed per pool. See details [here](https://docs.gyro.finance/pools/rehype-clps.html). ![Non-fungible CL illustration](/images/rehype.png) #### AutoRange Pools [AutoRange Pools](../explore-available-balancer-pools/reclamm-pool/reclamm-pool.md) are the next logical step. To summarize the discussion above: Uniswap pools offer highly granular concentrated liquidity positions, but they are non-fungible and must be actively managed by the LP. There is a direct trade-off between capital efficiency and ease of management. Gyro pools offer fungible concentrated liquidity over moderate ranges, but the parameters are fixed, so management of volatile assets may involve liquidity migration. AutoRange Pools offer fungible concentrated liquidity similar to 2-CLPs - but as the name implies, the parameters are not fixed. Essentially, the pools manage the liquidity on behalf of the LPs, with no user intervention required. Triggered by swaps or liquidity operations, the pool can adjust the price range automatically to keep itself “in range” (i.e., maintain the price within the liquidity bounds), whichever way the market moves. The pool creator can set the initial price range, as well as the margin - the “sensitivity” of the pool - which determines how quickly the pool responds to market price changes. Generally, the higher the volatility, the lower the margin, which makes the pool less sensitive to price changes and more gas-efficient. (If necessary, these can even be changed after deployment - but only slowly, to prevent manipulation.) The field is always advancing. There are now "ALMs" (automated liquidity managers), third party services that can relieve users of position maintenance. For instance, Arrakis Pro works on Uniswap V3 and V4, but has much broader application. The focused goal of AutoRange Pools is to remove the burden of active user management, without sacrificing capital efficiency: a true "fire-and-forget" concentrated liquidity position native to Balancer. And in contrast to some "strategy" solutions, it is completely transparent. ## Hooks \:::info Get Started To get started building your own hook check out the [guide](/build/build-a-hook/extend-existing-pool-type.md). \::: Hooks introduce a framework to extend existing pool types at various key points throughout the pool’s lifecycle. Hooks can execute actions during pool operation and also compute a dynamic swap fee. Potential applications for hooks include: * LVR (Loss versus Rebalancing) reduction * Dynamic Fees * Automatic Gauge locking * Sell or Buy limits ### Hook Contracts Hooks are implemented as standalone contracts that can have their own internal logic and state. One hook contract can facilitate many pools (and pool types). The hook system is flexible and allows developers to implement custom logic at the following points of the pool lifecycle: * `onRegister` * `onBeforeInitialize` * `onAfterInitialize` * `onBeforeAddLiquidity` * `onAfterAddLiquidity` * `onBeforeRemoveLiquidity` * `onAfterRemoveLiquidity` * `onBeforeSwap` * `onAfterSwap` * `onComputeDynamicSwapFeePercentage` Refer to the [Pool hooks API](/developer-reference/contracts/hooks-api.html) page for full function references. The swap, liquidity, and dynamic swap fee hooks are reentrant (i.e., you can call additional Vault operations during them, such as a swap after adding liquidity). Each Hook contract must implement the `getHookFlags` function which returns a `HookFlags` indicating which hooks are supported: ```solidity /** * @notice Return the set of hooks implemented by the contract. * @return hookFlags Flags indicating which hooks the contract supports */ function getHookFlags() external returns (HookFlags memory hookFlags); ``` ```solidity /** * @dev `enableHookAdjustedAmounts` must be true for all contracts that modify the `amountCalculated` * in after hooks. Otherwise, the Vault will ignore any "hookAdjusted" amounts. Setting any "shouldCall" * flags to true will cause the Vault to call the corresponding hook during operations. */ struct HookFlags { bool enableHookAdjustedAmounts; bool shouldCallBeforeInitialize; bool shouldCallAfterInitialize; bool shouldCallComputeDynamicSwapFee; bool shouldCallBeforeSwap; bool shouldCallAfterSwap; bool shouldCallBeforeAddLiquidity; bool shouldCallAfterAddLiquidity; bool shouldCallBeforeRemoveLiquidity; bool shouldCallAfterRemoveLiquidity; } ``` This decision is final and cannot be changed for a pool once it is registered, as each pool's hook configuration is stored in the Vault and set at pool registration time. During pool registration, the Vault calls into the Hooks contract and [retrieves](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L569-L573) the `HookFlags`. \:::info Hooks and reentrancy It is possible to reenter the Vault as part of a hook execution, as only the internal functions for each operation are reentrancy protected (e.g., `_swap`, `_addLiquidity` and `_removeLiquidity`). \::: ### How Pools and Hooks Are Connected When a new pool is registered a hook contract address can be passed to "link" the pool and the hook (use the zero address if there is no hook). This configuration is immutable and cannot change after the pool is registered. ![Vault-Pool-Hooks relation](/images/hooks.png) The architecture shows that a hooks contract is a standalone contract, which can be used by multiple pools of the same type (WeightedPools) but also multiple pools of different pool types (WeightedPools, StablePools). The address of the hook is passed to the pool registration. ```solidity function registerPool( address pool, ... address poolHooksContract, ) external; ``` \::: info If you want your Hooks contract to be used, you must implement `onRegister` as the Vault calls it during the [pool registration](https://github.com/balancer/balancer-v3-monorepo/blob/49553c0546121f7725e0b024b240d6e722f02538/pkg/vault/contracts/VaultExtension.sol#L175). The intention of `onRegister` is for the developer to verify that the pool should be allowed to use the hooks contract. \::: Afterwards the pool is linked to the hook via the `_hooksContracts` mapping, shown below. ```solidity // The hooks contracts associated with each pool. mapping(address pool => IHooks hooksContract) internal _hooksContracts; ``` ### Adjusted amounts - using hooks to change `amountCalculated`. Remember that pool liquidity operations like `swap`, `addLiquidity` and `removeLiquidity` signal to the Vault the entries on the credit and debt tab. These entries can either be calculated as part of custom pool implementations or pools in combination with hooks. Both have the capability to determine the amount of credits and debts the vault adds to the tab. The reason hooks also have this capability is to change `amountCalculated` for existing pool types from established factories. This allows for more fine-grained pool tuning capabilities in `after` hooks. ![Vault-Pool-Hooks relation](/images/hook-delta.png) \::: info When `enableHookAdjustedAmounts == true`, hooks are able to modify the result of a liquidity or swap operation by implementing an after hook. For simplicity, the vault only supports modifying the calculated part of the operation. As such, when a hook supports adjusted amounts, it can not support unbalanced liquidity operations as this would introduce instances where the amount calculated is the input amount (`EXACT_OUT`). \::: A detailed view of what an `after` hook for a given liquidity operation can change is displayed below: \| Operation | Hook cannot change | Hook *can* change | \| -------- | ------- | ------- | \| addLiquidityProportional | uint256\[] amountsIn | exactBptAmountOut | \| addLiquidityUnbalanced | *not supported* | *not supported* | \| addLiquiditySingleTokenExactOut | *not supported* | *not supported* | \| addLiquidityCustom | *not supported* | *not supported* | \| removeLiquidityProportional | uint256 exactBptAmountIn | uint256\[] amountsOut| \| removeLiquiditySingleTokenExactIn | *not supported* | *not supported* | \| removeLiquiditySingleTokenExactOut | *not supported* | *not supported* | \| removeLiquidityCustom | *not supported* | *not supported* | \| swapSingleTokenExactIn | uint256 exactAmountIn | uint256 amountOut | \| swapSingleTokenExactOut | uint256 exactAmountOut | uint256 amountIn | ### Hook examples If you want to get started with developing your own hooks contract, check out the [developing a hooks contract](/build/build-a-hook/extend-existing-pool-type.html) page. Various hook examples are shown there. Additionally the monorepo displays more ideas on how to approach hook development. ## Introduction Welcome to the Balancer Explained section, where we delve deep into the design and operation of Balancer v3. This section is perfect for anyone who wants a comprehensive understanding of the protocol's architecture, mechanics, and underlying principles. Whether you're curious about the inner workings or looking to grasp the finer details, you've come to the right place. If you're eager to jump into development and start building with Balancer, we've got you covered as well! Check out our [Integration Guides](../../integration-guides/README.md) for step-by-step instructions, explore the [Building An AMM](../../build/README.md) section for creating your own automated market maker, or consult the [Developer Reference](../../developer-reference/README.md) docs for comprehensive technical details. \::: info Please note these docs cover V3. For anything V2 related please visit [here](https://docs-v2.balancer.fi). \::: ### What is Balancer? Balancer is a decentralized automated market maker (AMM) protocol built on Ethereum with a clear focus on fungible and yield-bearing liquidity. Balancer's success is intrinsically linked to the success of protocols and products built on the platform. Balancer v3’s architecture focuses on simplicity, flexibility, and extensibility at its core. The v3 vault more formally defines the requirements of a custom pool, shifting core design patterns out of the pool and into the vault. Balancer Pools are smart contracts that define how traders can swap between tokens on Balancer Protocol, and the [architecture](./architecture.md) of Balancer Protocol empowers anyone to create custom pool types. What makes Balancer Pools unique from those of other protocols is their unparalleled flexibility. With the introduction of [Hooks](./hooks.md) and [Dynamic Swap Fees](/concepts/vault/swap-fee.html#dynamic-swap-fee), the degree of customization is boundless. Several custom pools have already been developed by external protocols like [Gyroscope](https://www.gyro.finance/) and [Xave](https://www.xave.co/). You can follow [this guide](../../build/build-an-amm/create-custom-amm-with-novel-invariant.md) to create your own custom pools. Balancer has already developed, audited and deployed a variety of pool types showcasing diverse functionalities. These pools are readily accessible for existing use cases without requiring permission, and the accompanying code serves as a valuable resource for custom pool development. For more details see the [Exploring Available Balancer Pools](../explore-available-balancer-pools/) section. ### Helpful Articles to Learn More * [What are automated market makers?](https://chain.link/education-hub/what-is-an-automated-market-maker-amm) * [What is Balancer v3?](https://medium.com/balancer-protocol/balancer-v3-the-future-of-amm-innovation-f8f856040122) * [Vision for Balancer v3](https://forum.balancer.fi/t/balancer-v3-my-thoughts-for-the-future-of-balancer/5801) ### Pool Creator Fee Introducing the Pool Creator Fee—a groundbreaking feature within the Balancer Protocol that revolutionizes the way developers engage with liquidity pools. With this innovative concept, developers of specific pools have the opportunity to earn a share of the swap fee and yield fee as revenue, incentivizing the creation of successful and thriving pools. In the following section, we delve into the details of this exciting feature, exploring how it works and its implications for pool creators. ### Implementation Whenever swap fees or yield fees are charged within the Balancer Protocol they are distributed among: * Liquidity Providers * The Balancer Protocol * Pool Creator ### Enabling the pool creator fee. The pool creator sets the `poolCreator` address during pool registration, which cannot be changed afterwards. By default, both the pool creator swap fee percentage and the pool creator yield fee percentage start at 0% and can be set later. \::: info If you are not yet sure when you want to start collecting a pool creator fee, or how high it should be, you can defer this decision until later, as long as you pass `poolCreator` address on pool registration. \::: #### Collecting Fees The accrued creator fees can only be claimed by the `poolCreator`, as the function to claim `withdrawPoolCreatorFees` is permissioned. ```solidity /** * @notice Withdraw collected pool creator fees for a given pool. This is a permissioned function. * @dev Sends swap and yield pool creator fees to the recipient. * @param pool The pool on which fees were collected * @param recipient Address to send the tokens */ function withdrawPoolCreatorFees(address pool, address recipient) external onlyPoolCreator(pool); ``` This collects the entire amount of accrued creator fees. It is not possible to claim creator swap or yield fees separately. Note that there is also a permissionless version, without a recipient, that sends the fees to the registered pool creator. If this address is a contract, that means anyone can send tokens to that contract at any time, so you must ensure they can be withdrawn! ```solidity /** * @notice Withdraw collected pool creator fees for a given pool. * @dev Sends swap and yield pool creator fees to the registered poolCreator. * @param pool The pool on which fees were collected */ function withdrawPoolCreatorFees(address pool) external; ``` #### Tracking accrued fees The aggregate pool creator fees (sum of pool creator swap and yield fees) can be fetched from the [ProtocolFeeController.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/ProtocolFeeController.sol) ```solidity /** * @notice Returns the amount of each pool token allocated to the pool creator for withdrawal. * @dev Includes both swap and yield fees. * @param pool The address of the pool on which fees were collected * @param feeAmounts The total amounts of each token available for withdrawal, sorted in token registration order */ function getPoolCreatorFeeAmounts(address pool) external view returns (uint256[] memory feeAmounts); ``` #### Setting The Fee Appropriately Developers must carefully consider their decisions regarding the creator fee, as increasing this fee reduces the portion of swap and yield fees allocated to Liquidity Providers. While higher creator fees may increase revenue for the creator, they can also diminish incentives for Liquidity Providers to participate, potentially resulting in reduced liquidity and overall fee generation within the pool. The pool creator can set the fees by calling the [`ProtocolFeeController`'s](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/ProtocolFeeController.sol) `setPoolCreatorYieldFeePercentage` and `setPoolCreatorSwapFeePercentage`. The maximum `poolCreatorFeePercentage` for both types is 100% (`1e18`). Note that this percentage is net protocol fees, which are paid first. As described above, a 100% fee would leave nothing for LPs. And in practice, depending on the configuration of other fees, it might revert (e.g., exact out calculations divide by "1-fee"). ## Pool Role Permissions During pool registration `PoolRoleAccounts` are set immutably. These addresses have permission to change certain pool settings: ```solidity struct PoolRoleAccounts { address pauseManager; address swapFeeManager; address poolCreator; } ``` * pauseManager: Can pause or unpause a pool. When a pool is paused, all state-changing operations will revert, and putting the pool in recovery mode (if not already done) becomes permissionless * swapFeeManager: Can set static swap fees for a pool * poolCreator: Can set the [pool creator fee](./pool-creator-fee.md) Passing the zero address grants Balancer Governance permission by default. \::: info Pause Permission Balancer Governance can always pause/unpause pools, even if there is a pause manager. However, governance cannot set swap fees if there is a swap manager, or pool creator fees if there is a pool creator. \::: , ## Rate Providers ### Overview Rate Providers are contracts that provide an exchange rate between two assets. These exchange rates can come from any on-chain source, whether that may be an oracle, a ratio of queryable balances, or another calculation. Rate Providers implement a `getRate()` function that returns an exchange rate. ### Use Cases You can use `rateProvider`s for all, some, or none of the assets in your pool. If you are not using a rateProvider for an asset, you must pass the zero address (`0x0000000000000000000000000000000000000000`), which will result in a rate of 1. #### All Assets You will want to use `rateProvider`s for all assets in your pool when each asset has its own price that is independent of all the other assets' prices. If we have tokens A, B, and C and only have price feeds with respect to USD, then we would want all assets to have price feeds. When internally calculating relative prices, the USD would cancel out, giving us prices for A:B, A:C, B:C, and their inverses. #### Some Assets You will want to use `rateProvider`s for some assets in your pool when you have rates that directly convert between the assets. If we have tokens A and B and a rate provider that gives the price of A with respect to B, then the `rateProvider` corresponding to token A would get the A:B price feed, and the `rateProvider` corresponding to token B would be the zero address. #### None of the Assets You will have no `rateProvider`s in your pool when your tokens are price-pegged to each other. For example, a pool with `USDC`, `USDT`, and `DAI` would have all `rateProvider`s set to the zero address since the exchange rate between those tokens is 1. ### Examples #### Direct Balance Query Wrapping rebasing tokens, such as `stETH`, makes them compatible with Balancer, but knowing the exchange rate between the underlying rebasing token and the wrapped token is necessary to facilitate StableSwap swaps. As such, the `wstETH` `rateProvider` has a `getRate()` function that calls `wstETH`'s own `stEthPerToken()` function. [See the contract here](https://github.com/balancer-labs/metastable-rate-providers/blob/master/contracts/WstETHRateProvider.sol). #### Oracles Using oracles for price feeds is a simple way to determine an exchange rate. There are two example contracts for how to use Chainlink as a price source: [`ChainlinkRegistryRateProvider`](https://github.com/balancer/metastable-rate-providers/blob/master/contracts/ChainlinkRegistryRateProvider.sol) and [`ChainlinkRateProvider`](https://github.com/balancer/metastable-rate-providers/blob/master/contracts/ChainlinkRateProvider.sol). ##### [`ChainlinkRegistryRateProvider`](https://github.com/balancer/metastable-rate-providers/blob/master/contracts/ChainlinkRegistryRateProvider.sol) This contract makes use of Chainlink's registry contract so it can handle Chainlink migrating to a new price feed for a given asset pair. Though there are increased gas costs for this, it's a tradeoff for ensuring the pool doesn't get stuck on an abandoned price feed. While this is an unlikely scenario, it doesn't hurt to be careful. ##### [`ChainlinkRateProvider`](https://github.com/balancer/metastable-rate-providers/blob/master/contracts/ChainlinkRateProvider.sol) If you're running on a network where Chainlink doesn't have a registry and you think the risk of a deprecated price feed is low enough, then you can use the rateProvider that directly queries a given Chainlink oracle. ### Implementation The Balancer Vault stores each token's rate in the `PoolData` struct. ```solidity struct PoolData { PoolConfigBits poolConfigBits; IERC20[] tokens; TokenInfo[] tokenInfo; uint256[] balancesRaw; uint256[] balancesLiveScaled18; uint256[] tokenRates; uint256[] decimalScalingFactors; } ``` Each time a `swap`, `addLiquidity` or `removeLiquidity` operation is performed, the token's rate is read to guarantee accurate results. The `tokenRates` from `PoolData` are internally updated through the `reloadBalancesAndRates` function, which is called as needed by all `swap`, `addLiquidity`, and `removeLiquidity` operations. As mentioned in [token scaling](/concepts/vault/token-scaling.html), data from the external Rate Provider contract is read and stored. ### Pool Types Let's explore some of the diverse pool types within the Balancer Protocol, each tailored to specific use cases. * [Weighted Pools](./weighted-pool/weighted-pool.md): Weighted Pools are highly versatile and configurable pools. They are ideal for general cases and enable users to build pools with different token counts and weightings. * [80/20 Pool](./weighted-pool/80-20-pool.md): An optimized version of the Weighted Pool with set weights of 80%/20%. The perfect fit for achieving liquidity of governance tokens. * [Stable Pools](./stable-pool/stable-pool.md): Stable Pools are optimal for assets expected to consistently trade at near parity or with a known exchange rate. * [Boosted Pool](./boosted-pool.md): Boosted Pools are designed to allow for greater capital efficiency, deeper liquidity, and increased yield for Liquidity Providers. * [Liquidity Bootstrapping Pools](./liquidity-bootstrapping-pool.md): Liquidity Bootstrapping Pools (LBPs) are pools that can dynamically change token weighting. LBPs create sell pressure and fair market advantages. Used very successfully for fair token launches. * [Gyroscope Pools](./gyroscope-pool/README.md): Fungible concentrated liquidity pools. Providing liquidity over a fixed price range gives liquidity providers better capital efficiency, and traders better execution prices. * [AutoRange Pools](./reclamm-pool/reclamm-pool.md): Fungible concentrated liquidity pools that automatically adjust the price range according to market conditions. A "fire-and-forget" solution to maintenance-free concentrated liquidity provision. Every pool type is associated with its own Factory contract, facilitating the creation of new pools. Experienced developers can find the factory deployment addresses through [this resource](../../developer-reference/contracts/deployment-addresses/mainnet.md). For further assistance, individuals are encouraged to contact our developers via [Discord](https://discord.balancer.fi/). ## Boosted Pools ### Overview Boosted Pools are a type of liquidity pool in which some or all of the tokens can generate yield. The yield-bearing tokens can constitute anywhere from 25% to 100% of the pool's total tokens. While the operation of Boosted Pools is not restricted to a specific trading algorithm, they often employ [Stable Math](/concepts/explore-available-balancer-pools/stable-pool/stable-math.html). This is due to the effectiveness of Stable Math in handling assets that are highly correlated. #### Advantages of Boosted pools The main advantage of boosted pools in the Balancer ecosystem are: ##### 100% yield bearing possibility Balancer v3 boosted pools allow for 100% of an LP position to be considered boosted, meaning held in a yield-bearing token. For example StablePools could contain yield-bearing assets from lending protocols such as Aave's yield bearing DAI (`aDAI`), Aave yield-bearing USDC (`aUSDC`), Aave yield-bearing USDT (`aUSDT`) or a yield-bearing Morpho Vault such as Steakhouse USDC (`steakUSDC`). The Balancer BatchRouter can handle user requests to trade among all these assets in a seamless and gas efficient manner (e.g., depositing and withdrawing underlying tokens DAI, USDC and USDT). The front end or aggregator can construct a batch swap with everything the router needs to perform the operations, including interacting with liquidity buffers (see below), and only wrapping or unwrapping when absolutely necessary. ##### Gas efficient swaps between boosted pool's base assets Boosted pools leverage the Vault's [liquidity buffers](/concepts/vault/buffer.html#erc4626-liquidity-buffers) concept to facilitate gas-efficient swaps between boosted pools. This allows LPs to maintain 100% boosted pool positions and still earn swap fees from base-asset to base-asset trades. ##### Boosted pools are "plug and play" for ERC4626 Vaults Any token that complies with the ERC4626 standard can easily become an asset within a boosted pool and can be swapped on Balancer gas efficiently while keeping swap prices competitive due to [liquidity buffers](/concepts/vault/buffer.html#erc4626-liquidity-buffers). ##### Great way for a DAO to facilitate liquidity for their token product Boosted pools are an excellent method for introducing LSTs and yield-bearing tokens to the market. As a DAO, you can fund [liquidity buffers](/concepts/vault/buffer.html#erc4626-liquidity-buffers) with yield-bearing assets and base assets, allowing your users to benefit from the advantages of 100% boosted pools. This also provides the same "entry" price for your LST or yield-bearing token that a user would get when entering natively. ## Governance Balancer is governed by veBAL token holders who vote on protocol changes through Snapshot. On-chain operations are executed by Balancer Onchain Limited through its service provider MAXYZ. This section documents all governance components and processes. ### Quick Start **Want to participate in governance?** 1. Acquire BAL tokens and WETH 2. Deposit into the [80/20 BAL/WETH pool](https://app.balancer.fi/#/ethereum/pool/0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014) to receive BPT 3. Lock your BPT on the [veBAL page](https://app.balancer.fi/#/ethereum/vebal) (1 week to 1 year) 4. Vote on [Snapshot proposals](https://snapshot.org/#/balancer.eth) and [gauge weights](https://app.balancer.fi/#/ethereum/vebal) **Want to create a proposal?** 1. Draft an RFC on the [Balancer Forum](https://forum.balancer.fi/c/governance/7) 2. Engage with the community for feedback 3. Work with the Operator team to prepare a transaction payload 4. Submit to Snapshot (requires 200k veBAL delegation) See the [Governance Process](./process.md) for detailed steps and timeline requirements. *** Various components of Balancer Governance are described in brief below. Click on the headings for more details on each topic. ### [Corporate Structure](./corporate-structure) As of [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859), Balancer operates through a formal corporate structure with Balancer Onchain Limited serving as the central hub for all on-chain operations. This structure provides legal clarity, operational efficiency, and proper risk management while maintaining alignment with decentralized governance. ### [Treasury Council](./corporate-structure#treasury-council) The Treasury Council oversees the Treasury Safe and administers the self-insurance fund. Established by BIP-882, it ensures ecosystem interests are protected by overseeing distributions, reviewing corporate resolutions, and ensuring alignment with Balancer governance decisions. ### [veBAL](veBAL) [veBAL](https://app.balancer.fi/#/ethereum/vebal) is a time-locked, non transferable derivative of the [80/20 BAL/ETH BPT on Mainnet](https://app.balancer.fi/#/ethereum/pool/0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014). veBAL holders, also called Balancer Governors, vote on proposals relevant to the protocol. These proposals are wide, ranging from which pools to enable BAL incentivize for to how treasury funds are allocated and managed. ### [BAL Token](./bal-token) The BAL token is the primary component of veBAL. Due to the fact that veBAL allows for swapping between BAL and ETH, BAL liquidity scales with Governance. Due to the fact that veBAL liquidity is locked, the market can easily understand how BAL liquidity depth will scale over time by analyzing the unlock schedule of veBAL. ### [Protocol Fee Operations](./protocol-fees) Protocol fees are collected from swaps, yield-bearing assets, and flash loans. This page describes how fees flow through Balancer's safe infrastructure—from collection via burners (V3) or Mimic (V2), through the Protocol Fees Multisig, to distribution among veBAL holders, core pool incentives, and the DAO. For fee percentages and distribution splits, see the [Protocol Fee Model](../protocol-fee-model/protocol-fee-model.md). ### [Governance Process](process.md) Changes to the Balancer Protocol and allocations of community funds are made through a governance process, which includes: A proposal, a prepared transaction payload for on-chain execution via Safe, and a snapshot vote validating the proposal with a quorum that currently stands at least 2 million veBAL voting. ### [Snapshot](./snapshot) Snapshot, a spinoff of Balancer, is an off-chain gasless multi-governance client with easy to verify and hard to contest results. Balancer Governance Votes take place on Snapshot. ### [Multisig](./multisig) Balancer operates through a hierarchical safe system with clear separation of responsibilities. Top-level safes handle governance and treasury management, while operational multisigs execute day-to-day operations. The multisigs do NOT have decision making power - their role is to enact on-chain the decisions BAL holders make via off-chain voting. ### [Emergency subDAO](./emergency) The Emergency subDAO is a 3-of-7 multisig (as per [BIP-883](https://forum.balancer.fi/t/bip-883-emergency-safe-governance-improvements-q4-2025/6865)) with bounded authority to protect the protocol by killing gauges, pausing pools, and managing pool factories in emergency situations. ## BAL Token ### Overview Balancer Governance Token (BAL) is the core token behind the Balancer protocol. Alignment between governance token holders and protocol stakeholders is crucial for successful decentralized governance, and BAL tokens are the vehicle to drive this alignment. [veBAL](veBAL) is an extension of BAL and is used for voting in decentralized governance. ### Contract Address \| Network | BAL Token Address | \| :------------ | :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | \| Ethereum | [0xba100000625a3754423978a60c9317c58a424e3d](https://etherscan.io/address/0xba100000625a3754423978a60c9317c58a424e3d) | \| Polygon PoS | [0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3](https://polygonscan.com/address/0x9a71012b13ca4d3d0cdc72a177df3ef03b0e76a3) | \| Polygon zkEVM | [0x120eF59b80774F02211563834d8E3b72cb1649d6](https://zkevm.polygonscan.com//address/0x120ef59b80774f02211563834d8e3b72cb1649d6) | \| Arbitrum | [0x040d1EdC9569d4Bab2D15287Dc5A4F10F56a56B8](https://arbiscan.io/address/0x040d1EdC9569d4Bab2D15287Dc5A4F10F56a56B8) | \| Optimism | [0xFE8B128bA8C78aabC59d4c64cEE7fF28e9379921](https://optimistic.etherscan.io/token/0xfe8b128ba8c78aabc59d4c64cee7ff28e9379921) | \| Base | [0x4158734D47Fc9692176B5085E0F52ee0Da5d47F1](https://basescan.org/address/0x4158734d47fc9692176b5085e0f52ee0da5d47f1) | \| Avalanche | [0xE15bCB9E0EA69e6aB9FA080c4c4A5632896298C3](https://snowtrace.io/token/0xE15bCB9E0EA69e6aB9FA080c4c4A5632896298C3?chainId=43114) | \| Gnosis | [0x7eF541E2a22058048904fE5744f9c7E4C57AF717](https://gnosisscan.io/address/0x7ef541e2a22058048904fe5744f9c7e4c57af717) | ### Supply & Inflation Schedule The maximum total supply of BAL tokens enforced at the smart contract is 100M; however, this does NOT necessarily mean that this cap will ever be reached. BAL token holders have the authority to decide if the distribution should end before hitting the cap. At the start of BAL token incentives 145,000 BAL were minted every week. In Q1 2022, [veBAL was introduced](https://forum.balancer.fi/t/introducing-vebal-tokenomics/2512) to replace fixed weekly emissions. veBAL includes an annual reduction in emissions rate such that the program should run until around 2050, leaving a total supply of around 96/100 million tokens upon completion and assuming no other governance is enacted to mint part of the remaining unallocated supply. TOTAL Tokens to be emitted by veBAL: 47,520,700 The current emissions rate can be understood by looking at the [Balancer Token Admin Contract](https://etherscan.io/address/0xf302f9f50958c5593770fdf4d4812309ff77414f#readContract) You can see the emission rate (getInflationRate = 0.239 BAL/second = 145k BAL/week) And the RATE\_REDUCTION\_COEFFICIENT 1.189 = 2^(1/4) This is applied to the inflation rate every year so as to achieve the emission schedule approved by governance This results in the following emissions schedule: \::: chart BAL Supply and Emissions ```json { "type": "line", "data": { "labels": [], "datasets": [ { "label": "BAL Emitted Weekly", "yAxisID": "weekly", "data": [ { "x": "2022", "y": "145000" }, { "x": "2023", "y": "121929.98021178861" }, { "x": "2024", "y": "102530.4832720494" }, { "x": "2025", "y": "86217.51583769728" }, { "x": "2026", "y": "72500.00000000001" }, { "x": "2027", "y": "60964.99010589432" }, { "x": "2028", "y": "51265.24163602471" }, { "x": "2029", "y": "43108.75791884865" }, { "x": "2030", "y": "36250.00000000001" }, { "x": "2031", "y": "30482.49505294716" }, { "x": "2032", "y": "25632.620818012354" }, { "x": "2033", "y": "21554.378959424324" }, { "x": "2034", "y": "18125.000000000004" }, { "x": "2035", "y": "15241.24752647358" }, { "x": "2036", "y": "12816.310409006177" }, { "x": "2037", "y": "10777.189479712162" }, { "x": "2038", "y": "9062.500000000002" }, { "x": "2039", "y": "7620.62376323679" }, { "x": "2040", "y": "6408.1552045030885" }, { "x": "2041", "y": "5388.594739856081" }, { "x": "2042", "y": "4531.250000000001" }, { "x": "2043", "y": "3810.311881618395" }, { "x": "2044", "y": "3204.0776022515442" }, { "x": "2045", "y": "2694.2973699280406" }, { "x": "2046", "y": "2265.6250000000005" }, { "x": "2047", "y": "1905.1559408091975" }, { "x": "2048", "y": "1602.0388011257721" }, { "x": "2049", "y": "1347.1486849640203" }, { "x": "2050", "y": "1132.8125000000002" } ] }, { "label": "Total BAL Supply", "yAxisID": "annual", "data": [ { "x": "2022", "y": "48485000" }, { "x": "2023", "y": "56045714.28571428" }, { "x": "2024", "y": "62403491.825328976" }, { "x": "2025", "y": "67749724.16737156" }, { "x": "2026", "y": "72245351.77890863" }, { "x": "2027", "y": "76025708.92176577" }, { "x": "2028", "y": "79204597.69157313" }, { "x": "2029", "y": "81877713.86259441" }, { "x": "2030", "y": "84125527.66836295" }, { "x": "2031", "y": "86015706.23979151" }, { "x": "2032", "y": "87605150.62469518" }, { "x": "2033", "y": "88941708.71020582" }, { "x": "2034", "y": "90065615.6130901" }, { "x": "2035", "y": "91010704.89880438" }, { "x": "2036", "y": "91805427.09125622" }, { "x": "2037", "y": "92473706.13401154" }, { "x": "2038", "y": "93035659.58545367" }, { "x": "2039", "y": "93508204.22831082" }, { "x": "2040", "y": "93905565.32453674" }, { "x": "2041", "y": "94239704.84591441" }, { "x": "2042", "y": "94520681.57163547" }, { "x": "2043", "y": "94756953.89306404" }, { "x": "2044", "y": "94955634.441177" }, { "x": "2045", "y": "95122704.20186582" }, { "x": "2046", "y": "95263192.56472635" }, { "x": "2047", "y": "95381328.72544064" }, { "x": "2048", "y": "95480668.99949712" }, { "x": "2049", "y": "95564203.87984154" }, { "x": "2050", "y": "95634448.0612718" } ] } ] }, "options": { "scales": { "weekly": { "min": 0, "position": "left", "scaleLabel": { "display": true, "labelString": "BAL Emitted Weekly" }, "title": { "display": true, "text": "BAL Emitted Weekly" } }, "annual": { "min": 0, "position": "right", "scaleLabel": { "display": true, "labelString": "Total BAL Supply" }, "title": { "display": true, "text": "Total BAL Supply" } } }, "plugins": { "legend": { "display": true } } } } ``` \::: ### Distribution This chart outlines the allocation amounts. More details below. \| BAL Recipient/Fund | Original Allocation | State as of Oct 2023 | \| :------------------------------------------------------------------------------------------------------------------------------------------------------------------ | :------------------ | :---------------------------------------------------------------------------------------------------------------------------- | \| Liquidity Providers - [allocated by the community](https://snapshot.org/#/balancer.eth/proposal/0xc93aa02ea7153a53d124189567ba19aa28663c499cdbfa60fe9bf35bf574d2a7) | up to 65M | \~25M | \| Founders, Options, Advisors, Investors | 22.5M | Fully vested | \| Treasury Safe (managed by [Treasury Council](./corporate-structure.md#treasury-council)) | 5M | [balance](https://etherscan.io/token/0xba100000625a3754423978a60c9317c58a424e3d?a=0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) | \| Balancer Labs Fundraising Fund | 5M | [balance](https://etherscan.io/token/0xba100000625a3754423978a60c9317c58a424e3d?a=0xb129f73f1afd3a49c701241f374db17ae63b20eb) | \| Balancer Labs Contributors Incentives Program | 2.5M | [balance](https://etherscan.io/token/0xba100000625a3754423978a60c9317c58a424e3d?a=0xcdcebf1f28678eb4a1478403ba7f34c94f7ddbc5) | \::: chart BAL Token Distribution ```json { "type": "pie", "data": { "labels": [ "Community", "Founders, Options, Advisors, Investors", "Ecosystem", "Fundraising" ], "datasets": [ { "label": "%", "data": [65, 25, 5, 5], "backgroundColor": [ "rgba(255, 99, 132, 0.2)", "rgba(54, 162, 235, 0.2)", "rgba(255, 206, 86, 0.2)", "rgba(75, 192, 192, 0.2)" ], "borderColor": [ "rgba(255, 99, 132, 1)", "rgba(54, 162, 235, 1)", "rgba(255, 206, 86, 1)", "rgba(75, 192, 192, 1)" ], "borderWidth": 1 } ] }, "options": {} } ``` \::: ##### Liquidity Providers Emissions to liquidity providers are decided by a combination of the emissions schedule described above, and [veBAL](veBAL) voting to determine the allocations that flow to each authorized pool. ##### Founders, Options, Advisors, Investors 25M tokens were allocated to founders, options, advisors, and investors, all subject to vesting periods. ##### Treasury Safe 5M were originally allocated for the Ecosystem Fund. This fund is deployed to attract and incentivize strategic partners who will help the Balancer ecosystem grow and thrive. veBAL holders ultimately decide how this fund is used. As of [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859), the main treasury is held in the Treasury Safe, managed by the [Treasury Council](./corporate-structure.md#treasury-council). This includes all assets and DeFi strategies. Treasury Safe: [0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89](https://app.safe.global/home?safe=eth:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) The DAO Multisig retains administrative permissions: [0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f](https://app.safe.global/home?safe=eth:0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f) ##### Balancer Labs Fundraising Fund 5M were originally allocated for the Balancer Labs Fundraising Fund. This fund will be used for future fundraising rounds to support Balancer Labs' operations and growth. BAL tokens will never be sold to retail users. As part of the transition to the [Operating Framework Enacted by Governance in Q2 2022](https://forum.balancer.fi/t/bip-1-operating-framework-for-balancer-dao/3237), 1.9M BAL was [transferred](https://etherscan.io/tx/0xaa29cd251cdb024c415b0e13f67a0ca74fe5abc3de9a9fedd1ae26fd39be4025) from the Fundraising Fund to the DAO Multisig to supplement the community controlled supply. For full transparency, the seed series price of one BAL token was $0.60. [Fundraising Fund](https://etherscan.io/address/0xB129F73f1AFd3A49C701241F374dB17AE63B20Eb) ## Corporate Structure ### Overview As of [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859), the Balancer ecosystem operates through a formal corporate structure that provides legal clarity, operational efficiency, and proper risk management while maintaining alignment with decentralized governance. This corporate structure is mirrored on-chain through a hierarchical safe system, where each legal entity is represented by a corresponding Safe multisig. This ensures that the off-chain legal responsibilities and on-chain operational capabilities are aligned. This structure was established following [BIP-863](https://forum.balancer.fi/t/bip-863-incorporation-of-a-legal-structure-for-balancer-treasury-and-finance-operations/6623), which approved the incorporation of a legal structure for Balancer treasury and finance operations. ### Corporate Hierarchy and On-Chain Representation The following diagram illustrates how the corporate structure is reflected on-chain through the hierarchical safe system: ![Safe Setup BIP-882](./images/safe_setup_bip882.png) Each legal entity in the corporate hierarchy has a corresponding Safe that serves as its on-chain representation, enabling the entity to hold assets, execute transactions, and fulfill its operational mandate. #### Balancer Foundation The Balancer Foundation serves as the ultimate custodian of the Balancer ecosystem's interests. **Responsibilities:** * Owns and oversees Balancer OpCo Limited * Receives dividends from Balancer OpCo Limited * Holds ecosystem reserves **On-Chain Representation:** Treasury Safe **Safe Address:** [0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89](https://app.safe.global/home?safe=eth:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) **Controlled By:** Foundation Board of Directors on behalf of the Balancer DAO #### Balancer OpCo Limited Balancer OpCo Limited is a wholly-owned subsidiary of the Balancer Foundation. **Responsibilities:** * Receives grants for operational funding * Distributes dividends to the Balancer Foundation (Treasury Safe) * Owns Balancer Onchain Limited **On-Chain Representation:** Balancer OpCo Ltd Safe **Safe Address:** [0x3B8910F378034FD6E103Df958863e5c684072693](https://app.safe.global/home?safe=eth:0x3B8910F378034FD6E103Df958863e5c684072693) **Controlled By:** Foundation Directors (3/4 threshold) #### Balancer Onchain Limited Balancer Onchain Limited is a BVI company wholly owned by Balancer OpCo Limited. It serves as the central hub for all on-chain operations. **Responsibilities:** * Collect and distribute protocol-generated fees * Manage and execute token reward distributions * Implement governance proposals by executing payloads * Distribute revenue to Balancer OpCo Limited via dividends * Subcontract or perform other operations as designated by its Directors, the Balancer Foundation board, or DAO Governance **On-Chain Representation:** Balancer Onchain Ltd Safe **Safe Address:** [0x16b0056636Fcc85f92C49cD49a24bc519d4A1941](https://app.safe.global/home?safe=eth:0x16b0056636Fcc85f92C49cD49a24bc519d4A1941) **Controlled By:** Foundation Directors (3/4 threshold) #### DAO Multi-sig The DAO Multi-sig maintains DAO administrative permissions and represents the broader Balancer community governance. **Responsibilities:** * DAO administrative permissions * Funding BIPs * Gauge killing * veBAL allowlisting **Safe Address:** [0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f](https://app.safe.global/home?safe=eth:0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f) **Controlled By:** DAO Signers (6/11 threshold) ### Operational Safes Beyond the corporate entity safes, several operational safes exist to manage specific functions: #### BizDev Safe Manages third-party incentives and partnership funds. **Safe Address:** [0xF3B4829C8B9E2910C2396538F49a12b0c2475a7e](https://app.safe.global/home?safe=eth:0xF3B4829C8B9E2910C2396538F49a12b0c2475a7e) **Controlled By:** BizDev Team (3/5 threshold) #### Operator Safe Executes day-to-day on-chain operations through third-party service provider [MAXYZ](https://forum.balancer.fi/t/maxyz-flawless-onchain-execution/6851). **Safe Address:** [0xBeF27037bC6311b96635E5e9Af3A73EBF6Ca8878](https://app.safe.global/home?safe=eth:0xBeF27037bC6311b96635E5e9Af3A73EBF6Ca8878) (deployed on all networks where Balancer contracts are in use) **Controlled By:** MAXYZ Operator (3/5 threshold) \::: info Operator Exchangeability The Operator can be exchanged for another service provider if needed. Balancer Onchain Ltd Safe maintains control over the operational multisigs and can replace the operator through the 1/2 threshold configuration. \::: #### Operational Multisig Configuration All operational multisigs (fee collection, liquidity mining, gauge management) use a standardized **1/2 threshold** configuration with: * Balancer Onchain Ltd Safe * Operator Safe This ensures efficient execution with proper oversight—Balancer Onchain Limited maintains control while delegating execution to the Operator, and either party can execute necessary operations. ### Treasury Council The Treasury Council was established by BIP-882 to ensure ecosystem interests are protected. It deprecates and replaces the previous Ecosystem Council (from [BIP-658](https://forum.balancer.fi/t/bip-658-ecosystem-council-review-and-self-insurance-cover/5856)). #### Authority The Treasury Council has the authority to: * Object to any Corporate Resolutions or activities not deemed in the ecosystem's best interests * Oversee distributions, liquidations, or other material actions proposed by Directors * Ensure alignment with Balancer governance resolutions * Administer the self-insurance fund #### Members \| Member | Address | \|--------|---------| \| 0xDanko | `0x122AFb4667C5f80e45721a42C7c81e9140C62FA4` | \| Xeonus | `0xaa5af0dd9c52c773d36cdbc509a0b2a1ded4c196` | \| danielmk | `0x606681E47afC7869482660eCD61bd45B53523D83` | \| mendesfabio | `0x90347b9CC81a4a28aAc74E8B134040d5ce2eaB6D` | \| solarcurve | `0x512fce9B07Ce64590849115EE6B32fd40eC0f5F3` | \| gosuto | `0x11e450c72c2258ec792d5f64a263ecb18e8c0f06` | \| notsoformal | `0xd17a9f089862351af82fa782435fac0f9e17786c` | The Treasury Council controls the Treasury Safe with a **5/7 threshold**. ### Self-Insurance Fund Through previous governance resolutions ([BIP-14](https://forum.balancer.fi/t/bip-14-funding-proposal-for-the-balancer-foundation/3316), [BIP-198](https://forum.balancer.fi/t/bip-198-creation-of-ecosystem-council-and-amended-self-insurance-fund/4409), [BIP-658](https://forum.balancer.fi/t/bip-658-ecosystem-council-review-and-self-insurance-cover/5856)), a self-insurance fund of $1,250,000 USD was established (held in the Treasury Safe in BAL tokens). #### Coverage BIP-882 ratified the expansion of this fund's scope to protect: * Balancer Labs * Balancer Foundation * Balancer OpCo Limited * Balancer Onchain Limited * Underlying service providers #### Disbursement Requirements Disbursements from the self-insurance fund require: 1. Verification by independent legal counsel 2. Confirmation that costs are directly related to Balancer work 3. Confirmation that costs are not eligible under other insurance coverage 4. Direct movement from the Treasury Safe to recipients through Balancer Foundation ### Emergency Procedures * The Treasury Council maintains override capability on critical infrastructure * The self-insurance fund remains available for unforeseen complications * Balancer Onchain Ltd Safe can intervene in emergency situations or swap out the operator if needed * The [Emergency subDAO](./emergency.md) retains bounded authority for protocol protection ### References * [BIP-882: Transitioning Onchain Operations to Balancer Onchain Limited](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859) * [BIP-863: Legal Structure for Onchain Operations](https://forum.balancer.fi/t/bip-863-incorporation-of-a-legal-structure-for-balancer-treasury-and-finance-operations/6623) * [BIP-873: Balancer Ecosystem Roadmap Proposal and Funding](https://forum.balancer.fi/t/bip-873-balancer-ecosystem-roadmap-proposal-and-funding/6668) ## Emergency subDAO ### Concept The [Emergency DAO](https://dao.curve.fi/emergencymembers) is an idea pioneered by Curve that empowers a small group to "kill" pools and gauges in the event of malicious activity and/or potential loss of funds. The subDAO is further authorized to pause pools when needed. The Balancer emergency subDAO was established after the following [vote](https://vote.balancer.fi/#/proposal/0x63fab7ab9ef5b9579dabb82058b8ea309e39c766d435438b55fff8db7c1f69fd). ### Relationship to Balancer Onchain Limited As of [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859), the Emergency subDAO operates alongside the [Balancer Onchain Limited](./corporate-structure.md) structure. In emergency situations: * The Emergency subDAO retains its bounded authority to kill gauges, pause pools, and protect the protocol * The Treasury Council maintains override capability on critical infrastructure * The Balancer Onchain Ltd Safe can intervene in emergency situations or swap out the operator if needed * The self-insurance fund remains available for unforeseen complications ### Members The Balancer Emergency subDAO is a **3-of-7 multisig** with the following members as appointed by [this vote](https://forum.balancer.fi/t/form-the-emergency-subdao/3197): \| Person | Address | \|:------------|:---------------------------------------------| \| Fabio | `0x90347b9CC81a4a28aAc74E8B134040d5ce2eaB6D` | \| Zen Dragon | `0x7c2eA10D3e5922ba3bBBafa39Dc0677353D2AF17` | \| Juani | `0xDA07B188daE2ee63B2eC61Ee4cdB9673C03d2293` | \| Hypernative | `0x202B1AA0d702898CA474aB6ED31d53BA309308D9` | \| Franz | `0x89c7D6ABA9Cd18D8A93571E583EEAc58Da75acE6` | \| Daniel | `0x606681E47afC7869482660eCD61bd45B53523D83` | \| Xeonus | `0x7019Be4E4eB74cA5F61224FeAf687d2b43998516` | ### Multisigs The Balancer Emergency subDAO operates through the following multisigs which are authorized to perform emergency actions \| Network | Address | \|:----------|:-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| \| Ethereum | [0xA29F61256e948F3FB707b4b3B138C5cCb9EF9888](https://app.safe.global/home?safe=eth:0xA29F61256e948F3FB707b4b3B138C5cCb9EF9888) | \| Polygon | [0x3c58668054c299bE836a0bBB028Bee3aD4724846](https://app.safe.global/home?safe=matic:0x3c58668054c299bE836a0bBB028Bee3aD4724846) | \| Arbitrum | [0xf404C5a0c02397f0908A3524fc5eb84e68Bbe60D](https://app.safe.global/home?safe=arb1:0xf404C5a0c02397f0908A3524fc5eb84e68Bbe60D) | \| Optimism | [0xd4c87b33afcE39F1E3F4aF1ce8fFFF7241d9128B](https://app.safe.global/home?safe=oeth:0xd4c87b33afcE39F1E3F4aF1ce8fFFF7241d9128B) | \| Gnosis | [0xd6110A7756080a4e3BCF4e7EBBCA8E8aDFBC9962](https://app.safe.global/home?safe=gno:0xd6110A7756080a4e3BCF4e7EBBCA8E8aDFBC9962) | \| Avalanche | [0x308f8d3536261C32c97D2f85ddc357f5cCdF33F0](https://app.safe.global/transactions/queue?safe=avax:0x308f8d3536261C32c97D2f85ddc357f5cCdF33F0) | \| zkEVM | [0x79b131498355daa2cC740936fcb9A7dF76A86223](https://app.safe.global/home?safe=zkevm:0x79b131498355daa2cC740936fcb9A7dF76A86223) | \| Base | [0x183C55A0dc7A7Da0f3581997e764D85Fd9E9f63a](https://app.safe.global/transactions/queue?safe=base:0x183C55A0dc7A7Da0f3581997e764D85Fd9E9f63a) | \| Fraxtal | [0xC66d0Ba27b8309D27cCa70064dfb40b73DB6de9E](https://safe.mainnet.frax.com/home?safe=fraxtal:0xC66d0Ba27b8309D27cCa70064dfb40b73DB6de9E) | \| Mode | [0x66C4b8Ba38a7B57495b7D0581f25784E629516c2](https://safe.optimism.io/home?safe=mode:0x66C4b8Ba38a7B57495b7D0581f25784E629516c2) | \| HyperEVM | [0x44613a28347206F5E26C1B8Db7Dc73f450219746](https://safe.onchainden.com/home?safe=hyperevm:0x44613a28347206F5E26C1B8Db7Dc73f450219746) | \| Plasma | [0x0d3319A8057A0C8afd87dFEEA252541A76d56Ebf](https://app.safe.global/home?safe=plasma:0x0d3319A8057A0C8afd87dFEEA252541A76d56Ebf) | \| XLayer | [0x6793df018B07C44E86D0b84C8C5f07EEc14E1270](https://app.safe.global/home?safe=xlayer:0x6793df018B07C44E86D0b84C8C5f07EEc14E1270) | \| Monad | [0x3a921d6956C62012Dd88f95bD44224a7Ef5C9b5a](https://app.safe.global/home?safe=monad:0x3a921d6956C62012Dd88f95bD44224a7Ef5C9b5a) | ### Specifications As per [this vote](https://forum.balancer.fi/t/form-the-emergency-subdao/3197) \| Call | Contract(s) | Purpose | \|:--------------|:-------------------------------------------------------------------------------------------------|:-------------------------------------------------------------------------------------| \| killGauge | Gauge contracts | To stop all distribution of BAL to a gauge. | \| denylistToken | [ProtocolFeeWithdrawer](https://etherscan.io/address/0x5ef4c5352882b10893b70DbcaA0C000965bd23c5) | Instructs the ProtocolFeeWithdrawer to blacklist fee collection of a specific token. | As per [BIP-139](https://forum.balancer.fi/t/bip-139-update-emergency-subdao-permissions/4174) The Emergency DAO Multisigs are authorized to make the following calls to protocol contracts: \| Call | Contract(s) | Purpose | \|:-------------------|:-----------------------|:--------------------------------------------------------------------------------------------------------------------------------------| \| enableRecoveryMode | Pool contracts | for Pools to provide a simple way to exit pools proportionally at the cost of disabling protocol fees(swaps, joins, etc. still work). | \| disable | Pool factory contracts | to shutdown pool factories. This is to prevent further pools from being created, existing pools remain unaffected. | As per [BIP-353](https://forum.balancer.fi/t/bip-353-grant-permissions-for-composable-stable-pool-factory-v5/4974) the Emergency DAO multisig are authorized to make the following calls to protocol contracts: \| Call | Contract(s) | Purpose | \|:--------------------|:---------------|:---------------------------------------------------------------| \| disableRecoveryMode | Pool contracts | Remove a pool from recovery mode, restoring normal operations. | As per [BIP-794](https://forum.balancer.fi/t/bip-794-enable-composable-stable-pool-pause-functionality-to-hypernative/6306) the Emergency DAO multisig was further authorized to install safe modules managed by Hypernative to pause [Balancer v2 composable stable](https://docs-v2.balancer.fi/concepts/pools/composable-stable.html) v6 pools in an event of an exploit: \| Call | Contract(s) | Purpose | \|:------|:---------------|:----------------------------------------------------------------------------| \| pause | Pool contracts | Pauses a specific Balancer v2 pool based on the Composable v6 pool factory. | As per [BIP-883](https://forum.balancer.fi/t/bip-883-emergency-safe-governance-improvements-q4-2025/6865), following a comprehensive security review by the Security Council, two critical improvements were implemented: the signer threshold was reduced from 4/7 to 3/7 across all chains to enable faster response times, and the `VaultAdmin.disableQueryPermanently()` permission was revoked from all emergency safes on chains with Balancer v3 deployments as it was not required for legitimate emergency response scenarios. \| Change | Scope | Purpose | \|:--------------------------------|:---------------------------------|:---------------------------------------------------------------------------------------------------------| \| Threshold reduction (4/7 → 3/7) | All emergency safes | Enable faster response times during critical security incidents while maintaining multi-entity quorum. | \| Revoke `disableQueryPermanently`| Emergency safes on v3 chains | Remove unnecessary permission that could permanently impact protocol functionality if compromised. | As per [BIP-911](https://forum.balancer.fi/t/bip-911-emergency-subdao-signer-swap-q1-2026/6963), a signer swap was performed across all emergency subDAO safes to replace MikeB (`0xF01Cc7154e255D20489E091a5aEA10Bc136696a8`) with Fabio from BLabs (`0x90347b9CC81a4a28aAc74E8B134040d5ce2eaB6D`), strengthening the security posture for Balancer v3 infrastructure by ensuring a responsive signer set across a wide range of time-zones. \| Change | Scope | Purpose | \|:--------------------------------------|:---------------------------------|:-----------------------------------------------------------------------------------------------------| \| Signer swap (MikeB → Fabio) | All emergency safes | Replace MikeB with Fabio from BLabs to ensure responsive signer coverage across time-zones. | ## Multisig \::: tip Multisig Permissions The core of Balancer smart contracts are immutable and do not use proxies or other upgrade mechanisms. The Multisig does **not** have custody of, nor control over, funds from liquidity providers that lie inside Balancer Protocol contracts. Balancer V2 was designed so that even if a multisig goes rogue, all the liquidity is safe and can be withdrawn by their rightful owners. Specific permissions can be found in the article below. \::: ### Hierarchical Safe System As of [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859), Balancer operates through a hierarchical safe system with clear separation of responsibilities. This structure supports the transition of on-chain operations to [Balancer Onchain Limited](./corporate-structure.md). #### Top-Level Safes These safes handle governance, treasury management, and high-level operational control. \| Name | Address | Threshold | Signer Set | Purpose | \|---------------------------|--------------------------------------------------------------------------------------------------------------------------------|-----------|-----------------------------------------------|-------------------------------------------------------| \| DAO Multi-sig | [0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f](https://app.safe.global/home?safe=eth:0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f) | 6/11 | [DAO Signers](#dao-multisig-signer-set) | DAO administrative permissions | \| Treasury Safe | [0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89](https://app.safe.global/home?safe=eth:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) | 5/7 | [Treasury Council](#treasury-council) | Main treasury holding all assets and DeFi strategies | \| Balancer OpCo Ltd Safe | [0x3B8910F378034FD6E103Df958863e5c684072693](https://app.safe.global/home?safe=eth:0x3B8910F378034FD6E103Df958863e5c684072693) | 3/4 | [Foundation Directors](#foundation-directors) | Operational funding, dividends to Balancer Foundation | \| Balancer Onchain Ltd Safe | [0x16b0056636Fcc85f92C49cD49a24bc519d4A1941](https://app.safe.global/home?safe=eth:0x16b0056636Fcc85f92C49cD49a24bc519d4A1941) | 3/4 | [Foundation Directors](#foundation-directors) | Central hub for on-chain operations, fee collection | \| BizDev Safe | [0xF3B4829C8B9E2910C2396538F49a12b0c2475a7e](https://app.safe.global/home?safe=eth:0xF3B4829C8B9E2910C2396538F49a12b0c2475a7e) | 3/5 | [BizDev Team](#bizdev-team) | Third-party incentives and partnership funds | \| Operator Safe | [0xBeF27037bC6311b96635E5e9Af3A73EBF6Ca8878](https://app.safe.global/home?safe=eth:0xBeF27037bC6311b96635E5e9Af3A73EBF6Ca8878) | 3/5 | [MAXYZ Operator](#operator) | Executes on-chain operations | ##### Treasury Safe Multi-Chain Deployment The Treasury Safe is deployed at the same address across multiple chains to hold ecosystem assets and execute DeFi strategies: \| Chain | Address | \|-------|---------| \| Ethereum | [0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89](https://app.safe.global/home?safe=eth:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) | \| Gnosis | [0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89](https://app.safe.global/home?safe=gno:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) | \| Arbitrum | [0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89](https://app.safe.global/home?safe=arb1:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89) | #### Operational Multisigs All operational multisigs use a standardized **1/2 threshold** configuration with: * Balancer Onchain Ltd Safe (Foundation Directors) * Operator Safe (MAXYZ Service Provider) This configuration enables efficient execution with proper oversight. \| Name | Purpose | Chains | Address | \|------|---------|--------|---------| \| Protocol Fees Multisig | Fee collection | [MAINNET](https://app.safe.global/home?safe=eth:0x7c68c42De679ffB0f16216154C996C354cF1161B), [ARBI](https://app.safe.global/home?safe=arb1:0x7c68c42De679ffB0f16216154C996C354cF1161B), [POLYGON](https://app.safe.global/home?safe=matic:0x7c68c42De679ffB0f16216154C996C354cF1161B) | `0x7c68c42De679ffB0f16216154C996C354cF1161B` | \| Mainnet Fee Setter | Default pool owner for Mainnet fees | [MAINNET](https://app.safe.global/home?safe=eth:0xf4A80929163C5179Ca042E1B292F5EFBBE3D89e6) | `0xf4A80929163C5179Ca042E1B292F5EFBBE3D89e6` | \| LM Multisig (Omni-sig) | Gauge management, liquidity mining | [MAINNET](https://app.safe.global/home?safe=eth:0x9ff471F9f98F42E5151C7855fD1b5aa906b1AF7e) | `0x9ff471F9f98F42E5151C7855fD1b5aa906b1AF7e` | \| Aura Locker Safe | vlAURA management | [MAINNET](https://app.safe.global/home?safe=eth:0x9a5BDF08a6969A4bDb7724beE3c6d8964BDc0B28) | `0x9a5BDF08a6969A4bDb7724beE3c6d8964BDc0B28` | #### Chain-Specific DAO Multisigs These multisigs hold administrative permissions on their respective chains, including: * **Authorizer Admin**: Control over protocol parameters and permissions * **Gauge Controller**: Adding/removing liquidity gauges for BAL emissions * **veBAL Allowlisting**: Managing pool eligibility for veBAL voting incentives * **Protocol Fee Configuration**: Setting swap and yield fee percentages All chain-specific DAO multisigs use the [DAO Signer Set](#dao-multisig-signer-set) with a 6/11 threshold. \| Chain | Address | \|-------|---------| \| Ethereum | [0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f](https://app.safe.global/home?safe=eth:0x10A19e7eE7d7F8a52822f6817de8ea18204F2e4f) | \| Arbitrum | [0xaF23DC5983230E9eEAf93280e312e57539D098D0](https://app.safe.global/home?safe=arb1:0xaF23DC5983230E9eEAf93280e312e57539D098D0) | \| Polygon | [0xeE071f4B516F69a1603dA393CdE8e76C40E5Be85](https://app.safe.global/home?safe=matic:0xeE071f4B516F69a1603dA393CdE8e76C40E5Be85) | \| Optimism | [0x043f9687842771b3dF8852c1E9801DCAeED3f6bc](https://app.safe.global/home?safe=oeth:0x043f9687842771b3dF8852c1E9801DCAeED3f6bc) | \| Gnosis | [0x2a5AEcE0bb9EfFD7608213AE1745873385515c18](https://app.safe.global/home?safe=gno:0x2a5AEcE0bb9EfFD7608213AE1745873385515c18) | \| Avalanche | [0x17b11FF13e2d7bAb2648182dFD1f1cfa0E4C7cf3](https://app.safe.global/home?safe=avax:0x17b11FF13e2d7bAb2648182dFD1f1cfa0E4C7cf3) | \| Base | [0xC40DCFB13651e64C8551007aa57F9260827B6462](https://app.safe.global/home?safe=base:0xC40DCFB13651e64C8551007aa57F9260827B6462) | \| Fraxtal | [0x4f22C2784Cbd2B24a172566491Ee73fee1A63c2e](https://safe.mainnet.frax.com/home?safe=fraxtal:0x4f22C2784Cbd2B24a172566491Ee73fee1A63c2e) | \| Mode | [0x4f22C2784Cbd2B24a172566491Ee73fee1A63c2e](https://safe.optimism.io/home?safe=mode:0x4f22C2784Cbd2B24a172566491Ee73fee1A63c2e) | #### Chain-Specific Operational Multisigs \| Name | Chain | Address | \|------|-------|---------| \| Optimism Fees + LM | [OPTIMISM](https://app.safe.global/home?safe=oeth:0x09Df1626110803C7b3b07085Ef1E053494155089) | `0x09Df1626110803C7b3b07085Ef1E053494155089` | \| Gnosis Chain Fees + LM | [GNOSIS](https://app.safe.global/home?safe=gno:0x14969B55a675d13a1700F71A37511bc22D90155a) | `0x14969B55a675d13a1700F71A37511bc22D90155a` | \| Avalanche Ops | [AVAX](https://app.safe.global/home?safe=avax:0x326A7778DB9B741Cb2acA0DE07b9402C7685dAc6) | `0x326A7778DB9B741Cb2acA0DE07b9402C7685dAc6` | \| Base Ops | [BASE](https://app.safe.global/home?safe=base:0x326A7778DB9B741Cb2acA0DE07b9402C7685dAc6) | `0x326A7778DB9B741Cb2acA0DE07b9402C7685dAc6` | \| Fraxtal Ops | [FRAXTAL](https://safe.mainnet.frax.com/home?safe=fraxtal:0x9ff471F9f98F42E5151C7855fD1b5aa906b1AF7e) | `0x9ff471F9f98F42E5151C7855fD1b5aa906b1AF7e` | \| Mode Ops | [MODE](https://safe.optimism.io/home?safe=mode:0x9ff471F9f98F42E5151C7855fD1b5aa906b1AF7e) | `0x9ff471F9f98F42E5151C7855fD1b5aa906b1AF7e` | ### Context Since its inception, the long term vision for the Balancer Protocol is to be fully governed by BAL token holders, while token ownership is aimed to be widely spread across the Balancer community. Protocol governance is a highly complex and rapidly evolving topic. The Balancer community has taken a thoughtful approach to decentralization, with each step taken with due care and learning from others' experiences. Balancer V2 contracts allow for some tweaking of core protocol parameters. As a placeholder for future on-chain governance, such limited admin powers have been granted to multisigs. The eventual goal remains moving entire governance and execution on-chain. ### Current State of Operations As of [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859), all on-chain operational responsibilities have transitioned from the Balancer DAO to Balancer Onchain Limited. This provides: 1. **Legal Clarity**: A formal entity for on-chain operations helps manage regulatory and legal risks 2. **Operational Efficiency**: Centralized operations under a dedicated entity with clear service provider relationships 3. **Enhanced Oversight**: The Treasury Council provides robust checks and balances 4. **Risk Management**: Proper legal structure and self-insurance fund protect participants Multisigs do NOT have decision-making power. Their role is to enact on-chain the decisions BAL holders make via off-chain voting and assist community members in the governance process. All Balancer Multisigs are deployed using [Safe](https://safe.global/) (formerly Gnosis Safe), the most battle-tested multisig contract on Ethereum. The [Balancer Multisig Ops Repo](https://github.com/balancer/multisig-ops) describes all multisigs and operations as well as the external touch-points available. ### Signer Groups #### DAO Multisig Signer Set The DAO Multisig Signer Set is reserved for major changes to protocol operations and management of treasury funds. **Requires 6/11 signers.** As of [BIP-907](https://forum.balancer.fi/t/bip-907-dao-multisig-signer-set-update/), the signer set has been updated to reflect current ecosystem participation: \| Signer | Association | Address | \|--------|-------------|---------| \| [0xMaki](https://twitter.com/0xMaki) | LayerZero, AURA, DCV | `0x285b7EEa81a5B66B62e7276a24c1e0F83F7409c1` | \| [Ernesto](https://twitter.com/eboadom) | BGD | `0xA39a62304d8d43B35114ad7bd1258B0E50e139b3` | \| [Mounir](https://twitter.com/mounibec) | Paraswap | `0x0951FF0835302929d6c0162b3d2495A85e38ec3A` | \| [Stefan](https://twitter.com/StefanDGeorge) | Gnosis | `0x9F7dfAb2222A473284205cdDF08a677726d786A0` | \| [bonustrack87](https://twitter.com/bonustrack87) | Snapshot | `0x9BE6ff2A1D5139Eda96339E2644dC1F05d803600` | \| [David Gerai](https://twitter.com/davgarai) | Raft | `0xAc1aA53108712d7f38093A67d380aD54B562a650` | \| [gosuto](https://twitter.com/gosaborern) | Balancer Contributor | `0x11e450c72c2258ec792d5f64a263ecb18e8c0f06` | \| [elbagococina](https://twitter.com/elbagococina) | Karpatkey | `0x6578183A203b41C419b93DF9121b5e3b26561aC5` | \| [netto.eth](https://twitter.com/nettofc) | Blockful/ENS | `0x235f00a6e9416b114780f0b97afcb40f623f65b4` | \| [MikeB](https://twitter.com/MikeB_Eng) | former Balancer Maxis | `0xF01Cc7154e255D20489E091a5aEA10Bc136696a8` | \| [hubert](https://twitter.com/hubert_LL) | StakeDAO | `0x02e4De712d99f4B1b1e12aa3675D8b0A582caA5D` | Beyond current signers, [BIP-16](https://forum.balancer.fi/t/bip-16-update-dao-Multisig-replacement-list/3361) established a group of backup signers who can replace current signers without further governance. #### Treasury Council The Treasury Council oversees the Treasury Safe and administers the self-insurance fund. Established by [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859), it replaces the previous Ecosystem Council. **Requires 5/7 signers.** \| Member | Address | \|--------|---------| \| 0xDanko | `0x122AFb4667C5f80e45721a42C7c81e9140C62FA4` | \| Xeonus | `0xaa5af0dd9c52c773d36cdbc509a0b2a1ded4c196` | \| danielmk | `0x606681E47afC7869482660eCD61bd45B53523D83` | \| mendesfabio | `0x90347b9CC81a4a28aAc74E8B134040d5ce2eaB6D` | \| solarcurve | `0x512fce9B07Ce64590849115EE6B32fd40eC0f5F3` | \| gosuto | `0x11e450c72c2258ec792d5f64a263ecb18e8c0f06` | \| notsoformal | `0xd17a9f089862351af82fa782435fac0f9e17786c` | The Treasury Council has authority to: * Object to Corporate Resolutions or activities not deemed in the ecosystem's best interests * Oversee distributions, liquidations, or other material actions proposed by Directors * Ensure alignment with Balancer governance resolutions * Administer the self-insurance fund #### Foundation Directors Foundation Directors control the Balancer OpCo Ltd Safe and Balancer Onchain Ltd Safe. **Requires 3/4 signers.** The Foundation board consists of: * **Leeward Management Limited** - Corporate director appointed per [BIP-480](https://forum.balancer.fi/t/bip-480-appointment-of-director-to-the-balancer-foundation/5362) * Two community directors representing the Balancer ecosystem #### BizDev Team The BizDev Team manages incentive funds and partnerships through the BizDev Safe. **Requires 3/5 signers.** \| Member | Address | \|--------|---------| \| Simon | `0xc57b29CAc1a1CD4E8e678622D35459c4AF6F8b9c` | \| Zekraken | `0xafFC70b81D54F229A5F50ec07e2c76D2AAAD07Ae` | \| Marcus BLabs | `0xb7364Fca20EEC90f51b158C05199044AD362b675` | \| Danko | `0x122AFb4667C5f80e45721a42C7c81e9140C62FA4` | \| Xeonus | `0x7019Be4E4eB74cA5F61224FeAf687d2b43998516` | #### Operator The Operator Safe is a **3/5 multisig** currently managed by [MAXYZ](https://forum.balancer.fi/t/maxyz-flawless-onchain-execution/6851), a service provider engaged by Balancer Onchain Limited. The Operator executes on-chain operations through the operational multisigs. **Safe Address:** `0xBeF27037bC6311b96635E5e9Af3A73EBF6Ca8878` (deployed on all networks where Balancer V3 is active) The Operator can be exchanged for another service provider if needed—Balancer Onchain Ltd Safe maintains control and can replace the operator through the operational multisig configuration. ### Signer Duties All signers are expected to sign Ethereum transactions ratifying each decision made by BAL holders through snapshot votes. This signature is expected within two weeks after the snapshot vote concludes. Signers are encouraged to sign open requests even if they have already reached quorum to signal their liveliness. A signer shall lose their role (by action of the remaining multisig signers) in case they: * Act against BAL token holders' off-chain voting * Go through 3 months or 2 votes (whichever takes longer) without performing any signer duties ### Multisig Powers Balancer V2 and V3 have different governance models reflecting their maturity and deployment strategies. #### Balancer V2 Powers V2 smart contracts grant specific powers to an "admin" address, which points to the appropriate multisig (typically DAO multisigs on established chains). These powers include: * Set a share of swap fees to be diverted to the protocol (hard capped at 50% of the swap fee) * Set a flash loan fee * Extract from the vault collected protocol fees and/or excess balances (e.g., airdrops), to any destination * Set the address of the oracle implementation * Set relayer addresses: relayers are (user opt-in, audited) contracts that can make calls to the vault (with the transaction "sender" being any arbitrary address) and use the sender's ERC20 vault allowance, internal balance or BPTs on their behalf * Set dynamic-fee controllers: addresses that may change the swap fee for pools created by the dynamic-fee pool factory * Add and remove veBAL gauges #### Balancer V3 Powers V3 deployments use a role-based permission system through the Authorizer contract. Admin powers include: * Configure protocol swap and yield fee percentages * Set pool creator fee percentages * Manage pool registration and configuration * Enable/disable vault query functionality * Pause/unpause the vault and pools * Manage hook permissions and configurations ##### Established Chains On chains with mature V3 deployments (Ethereum, Arbitrum, Base, Gnosis, Avalanche, Optimism), the **DAO Multisig** holds administrative permissions following the standard governance model. ##### Multi-Stage Deployment Framework (Newer Chains) For newer V3 deployments on emerging chains, Balancer uses a multi-stage deployment approach that initially grants administrative permissions to the **Omni-sig** (`0x9ff471F9f98F42E5151C7855fD1b5aa906b1AF7e`). This enables greater operational flexibility during the critical early phases of deployment. **Chains using this framework:** Plasma, HyperEVM, Monad, XLayer \| Phase | Duration | Admin Control | Description | \|:------|:---------|:--------------|:------------| \| Phase 1: Technical Deployment | Months 1-2 | Omni-sig | Full technical functionality, frontend integration, initial pool creation | \| Phase 2: Growth & Partnerships | Months 2-8 | Omni-sig | Ecosystem partnerships, TVL growth, protocol integrations | \| Phase 3: BAL Integration | Month 8+ | Transition to DAO Multisig | Gauge system integration, veBAL voting, full governance transition | **Governance Transition:** Upon successful completion of Phase 2 milestones (typically $15M+ TVL, 5+ protocol integrations, sustained trading volume), the Omni-sig transfers administrative privileges to a newly established DAO multisig following the standard DAO signer set. **Exit Criteria:** Each phase has clear success metrics. If deployments don't achieve sufficient traction, the DAO can propose to wind down operations rather than proceeding to the next phase. Example deployment BIPs using this framework: * [BIP-862: Deploy Balancer v3 on HyperEVM](https://forum.balancer.fi/t/bip-862-deploy-balancer-v3-on-hyperevm/6628) * [BIP-858: Deploy Balancer v3 on Plasma](https://forum.balancer.fi/t/bip-858-deploy-balancer-v3-on-plasma/6606) ## Governance Process ### Summary The Balancer Governance process has evolved through a number of BIPs as Balancer moves along on its quest towards decentralization. The most recent governance overhaul BIP at the time of this writing is [BIP-163](https://snapshot.org/#/balancer.eth/proposal/0xcd2cab0522b0e9a90ad40f93aca4505b17d60468224c22b69c4f9bd2bbd64e31). Balancer governance submissions consist of 2 items: an English proposal and a multisig payload that executes the changes described on-chain. [Balancer Onchain Limited](./corporate-structure.md), through its service provider [MAXYZ](https://forum.balancer.fi/t/maxyz-flawless-onchain-execution/6851), is tasked with supporting community members in putting together proposals when required, and with the final evaluation and execution of approved proposals. Operational support can be reached on the [Balancer Discord](https://discord.balancer.fi/) or through an issue in the [Balancer Multisig Ops Repo](https://github.com/balancer/multisig-ops/issues). ![img.png](./images/govProcess.png) ### Governance Timeline The following timeline requirements apply to different proposal types (per [DAO Governance Guidelines](https://forum.balancer.fi/t/balancer-dao-governance-guidelines/6311)): #### Forum Discussion Periods \| Proposal Type | Minimum Discussion | Submission Deadline | \|---------------|-------------------|---------------------| \| General proposals | 72 hours | Tuesday 20:00 CET | \| Gauge additions | 24 hours | Thursday 20:00 CET | \| Funding proposals | 1 week | 1 week before snapshot | #### Voting Schedule * **Voting starts**: Friday 20:00 CET * **Voting ends**: Tuesday 20:00 CET (4 days / 96 hours) * **Quorum**: 2 million veBAL #### Execution Timeline \| Action Type | Typical Execution | \|-------------|-------------------| \| Gauge activations | Tuesday after successful vote | \| Standard multi-sig execution | End of month (usually last week) | \| Gauge removals | End of month, quarterly basis | \::: warning Funding Proposals Funding proposals and real-world contracts may require additional review periods and legal sign-off, potentially extending timelines beyond minimum requirements. \::: ### Outline This page outlines the Balancer Governance Process from Request for Comment \[RFC] through executing a result. 1. Post a Request for Comment to the forum 2. Facilitate preliminary discussion 3. Update and refine RFC to become a Proposal 4. Snapshot vote 5. Execute result or try again in 30 days #### Step 1: Write Request For Comment (RFC) An initial request for governance is made up of 2 potential components. An English language description of the purpose of the proposal and the details of the changes to be made, and a payload to execute such changes on chain which can be loaded into Safe if on-chain changes are required for execution. When possible, any off-chain changes to code should also have PRs linked in governance. ##### **English Description** Start a new conversation: [General Proposals](https://forum.balancer.fi/c/governance/7) or [veBAL Gauge additions](https://forum.balancer.fi/c/vebal/13) with the \[RFC] tag. The message should contain the following sections. * **Link to Transaction Payload PR** * *The Snapshot body should begin with a link to the transaction payload PR on GitHub. If the text is too long it can be truncated. Note that if the proposal requires no on-chain actions to be executed, the payload is not required.* * *Note that this link can be added towards the end of the discussion process once the proposal is moving to snapshot.* * **Background and motivation** * *What is the current state or what you're addressing?* * *What is the reason for this?* * *Why is it good for the veBAL ecosystem or the Balancer Protocol?* * *Is there any relevant information that the common reader might not know?* * **English Specification** * *Clearly state exactly what this proposal will change and the effects it will have on the operation of the protocol or balance of the treasury.* * **Dependencies(if any)** * *What is needed to introduce the change?* * **Risk assessment** * \_What can go wrong? What is the impact of your proposal on the rest of the community, ecosystem, protocol? * *For Spend (how does it affect runway)* * *For Gauge Votes (consider pool makeup and caps as per the Gauge Framework defined in BIP- and/or community discussion)* For other changes explain any and all risks clearly.\_ * **Open Questions** * *Any obvious discussions or things you are still considering?* Further, votes requesting new gauges should include the following information: * What are the initial fees set to, and what is the reasoning for this? * If stableswap, how is the A-factor set and why was the given a-factor chosen? * If custom pool, how does this pool generate revenue? Is 50% of the revenue generated sent to the fees collector? Where can details about the performance of this pool be found? Where can users deposit into it and swap through it? #### **Step 2: Discussion** * Encourage and participate in discussions on the forum. * Promote your topics, find voters, get feedback. * Consider contacting [Delegates](https://forum.balancer.fi/c/delegate-citadel/14) to obtain their support. * Encourage interested parties on Discord to gather their thoughts in a forum post * It is advised that proper time is given for the community to discuss a proposal before bringing it to a Snapshot vote and that the original proposer is open to making changes as part of the discussion process. * The Operator currently performs initial validation on any submitted snapshots and gets in touch with the proposer if there are issues, inviting them to take down their vote and repost it properly to avoid an end result that cannot be executed due to lack of compliance. #### **Step 3: Develop and validate transaction Pull Request** **NOTE:** The Operator team is available to build and validate your PR. If you don't want to get involved in the technical details of defining your execution, contact the team on Discord and they will be happy to help. Signers are listed in the [Multisig documentation](./multisig.md). A Pull Request (PR) that posts a transaction to a Safe multisig which executes the changes specified by the BIP on-chain is required as part of the body of a Proposal specified above before it can be brought to valid snapshot. The file(s) should be added into their own directory here on the [Multisig Ops Repo](https://github.com/balancer/multisig-ops/tree/main/BIPs/00proposed). Once the BIP is ready to go to Snapshot vote and a BIP number is selected, the files will be moved into the [BIPs directory](https://github.com/balancer/multisig-ops/tree/main/BIPs) following the apparent pattern there. The description of the pull request should contain a link to the Forum Post with the English Specification of it. The Operator will review the payload and post any concerns or questions in review. Examples of how to submit payload PRs for common governance quests can be found [HERE](https://github.com/balancer/multisig-ops/tree/main/BIPs/00examples) #### **Step 4: Snapshot** The Snapshot process is started when an address with at least 200,000 veBAL in delegation posts a snapshot to the forum that meets all of the required specifications as defined in [BIP-163](https://forum.balancer.fi/t/bip-163-restructure-governance-process-disband-governance-council/4244). * The Snapshot body should begin with a link to any required transaction payload PR on GitHub. Followed by the full text of the BIP. If the text is too long, it can be truncated. * A link to the forum discussion should be included in the discussion (optional) field of Snapshot. * The BIP is titled like `BIP-[XXX] Title from Forum`, where XXX is the next number in the BIP sequence. * The original forum proposer should update their post to match the title from the Snapshot and include a link to it at the bottom of the body of the Forum Post. * Barring clear community consensus otherwise the vote should be of Type "Basic Voting" and the choices should be one of \[Yes, let's do it - No, This is not the way - Abstain]. * Runs for 96 hours (4 days) starting on **Friday 20:00 CET** and concluding **Tuesday 20:00 CET**. * Has a quorum of 2 million veBAL. * The linked payload matches the English specification and passes review and is in a recognizable/verifiable form by the Operator. * The linked payload simulates successfully in Tenderly and/or produces the desired results on fork. **IMPORTANT:** A Snapshot vote that does not meet all of the above requirements will not be valid even if it wins a majority of the votes. Please take your time when posting Snapshots. Several community members have delegations of over 200k veBAL and would be happy to help you post your Snapshot if you are unsure and it has some community support. If a Snapshot is approved by governance but rejected for technical reasons, the Operator will help to fix the payload and facilitate a revote to approve. #### Snapshot Governance Configuration As of [BIP-882](https://forum.balancer.fi/t/bip-882-transitioning-onchain-operations-of-the-balancer-dao-to-balancer-onchain-limited/6859): * **Space Controller**: Balancer Onchain Ltd Safe ([0x16b0056636Fcc85f92C49cD49a24bc519d4A1941](https://app.safe.global/home?safe=eth:0x16b0056636Fcc85f92C49cD49a24bc519d4A1941)) * **Snapshot Authors**: * Operator EOA: `0x58865c1B463Fd2772cD50EB50976A07FaE3Dc6F1` * Foundation EOA: `0x122AFb4667C5f80e45721a42C7c81e9140C62FA4` * **Treasury Display**: Treasury Safe ([0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89](https://app.safe.global/home?safe=eth:0x0EFcCBb9E2C09Ea29551879bd9Da32362b32fc89)) #### Step 5: Results and Execution If the vote fails in an approve/reject vote it will not be executed on. Proposers are encouraged to wait at least 30 days and/or until something significant has changed before posting another vote, and delegates with sufficient veBAL to post votes are asked to be considerate about creating governance noise and SPAM by reposting failed votes in rapid succession. If the vote succeeds or a result has been chosen, follow through to make sure that it is properly executed. Depending on what the vote is about, it may require an action by the [multisig](multisig.md). The Operator is currently responsible for organizing the on-chain execution of governance and is working toward making the process as transparent as possible in the public [Balancer Multisig Ops GitHub Repo](https://github.com/balancer/multisig-ops). Assuming all reviews are finished and dependencies are met, the Operator will make every effort to execute on finished proposals in the same week that governance concludes. Note that in some cases complex BIPs may require more time for final multisigner review. The Operator will endeavor to post a comment to the Forum post with a link to the execution TX upon execution. ## Protocol Fee Operations This page describes how protocol fees are collected and processed through Balancer's safe infrastructure. For fee percentages, distribution splits, and core pool requirements, see the [Protocol Fee Model](../protocol-fee-model/protocol-fee-model.md). ### Fee Sources Protocol fees are collected from the following sources: #### Swap Fees Balancer collects a percentage of swap fees paid by traders. From the swapper's perspective, there is no price increase—the protocol fee is taken as a fraction of the fee already being collected for liquidity providers. #### Yield Fees Protocol fees are applied to yield earned by yield-bearing assets with rate providers. The percentage differs between V2 (50%) and V3 (10%), with V3's reduced rate designed to drive adoption of boosted pools technology. #### Flash Loan Fees [Flash loans](/concepts/vault/flash-loans.md) allow users to borrow assets without collateral, as long as the borrowed amount is repaid within the same transaction. In Balancer V2, governance had the ability to set flash loan fees, but they were always set to zero to encourage developers to build on Balancer. In V3, flash loans operate through the Vault's transient unlock mechanism and remain fee-free by design. ### Fee Collection Infrastructure Protocol fees are collected differently for Balancer V2 and V3, but both ultimately flow through the Protocol Fees Multisig (also known as the Fee Collector Safe) on Ethereum mainnet for distribution. ![Fee Collection Infrastructure](./images/fee_collection_infra.png) #### Balancer V3 Collection V3 fees are swept from pools using burner contracts to the [Omni-sig](./multisig.md) on each chain, then bridged to Ethereum mainnet and transferred to the Protocol Fees Multisig. #### Balancer V2 Collection V2 fees are processed by [Mimic](https://mimic.fi/) infrastructure, which handles swapping collected tokens to USDC, bridging from L2s to mainnet, and transferring to the Protocol Fees Multisig. ### Fee Distribution Flow From the Protocol Fees Multisig, fees are distributed according to governance-approved splits (see [Protocol Fee Model](../protocol-fee-model/protocol-fee-model.md) for percentages): ![Fee Distribution Flow](./images/fee_distro.png) #### Distribution Recipients \| Recipient | Description | \|-----------|-------------| \| **veBAL Holders** | Direct USDC payments to veBAL lockers | \| **Core Pool Voting Incentives** | Incentives placed on core pools to drive veBAL votes toward revenue-generating pools | \| **Balancer Onchain Ltd Safe** | DAO's share of protocol revenue | \::: tip Core Pool Status Interested in having your pool achieve core pool status to benefit from the incentive flywheel? See the [Core Pools guide](../../partner-onboarding/onboarding-overview/core-pools.md) for requirements and application process. \::: #### Corporate Structure Flow From the Balancer Onchain Ltd Safe, the DAO's share flows up the [corporate structure](./corporate-structure.md): 1. **Balancer Onchain Ltd Safe** receives the DAO portion 2. **Dividends** flow to Balancer OpCo Ltd Safe 3. **Dividends** flow to Treasury Safe for ecosystem reserves ### Governance Controls #### Protocol Fees Multisig The Protocol Fees Multisig (`0x7c68c42De679ffB0f16216154C996C354cF1161B`) controls fee collection and initial distribution. It operates under the [1/2 operational multisig configuration](./multisig.md#operational-multisigs) with Balancer Onchain Ltd Safe and Operator Safe as signers. #### Fee Parameter Changes Changes to protocol fee percentages require governance approval through the standard [governance process](./process.md). The DAO Multisig or appropriate chain-specific multisig executes approved changes. ### Related Documentation * [Protocol Fee Model](../protocol-fee-model/protocol-fee-model.md) - Fee percentages and distribution splits * [Core Pools](../../partner-onboarding/onboarding-overview/core-pools.md) - Core pool requirements and benefits * [Multisig](./multisig.md) - Safe infrastructure and signer groups * [Corporate Structure](./corporate-structure.md) - Legal entity hierarchy ## Snapshot ### Overview [Snapshot](https://snapshot.org/#/), a spinoff from the Balancer team, is an off-chain gasless multi-governance client with easy to verify and hard to contest results. Snapshot derives its name from taking a "snapshot" of the Ethereum blockchain at a certain block number before voting opens. By having a fixed point in time, this eliminates the need to worry about moving tokens voting multiple times or someone using a flash loan to borrow a ton of voting power. ### Eligibility/How to Vote In order to be eligible to [vote on Balancer](https://snapshot.org/#/balancer) proposals, you need to satisfy at least one of the following: * Hold veBAL tokens * Have someone delegate their voting power to your address When a vote opens, click on the proposal in the Balancer Space, scroll to the voting section, and cast your vote. Your wallet provider will ask you to sign a message (don't worry, you won't be charged any gas). ### Further Reading If you're interested in learning more or want to use Snapshot for your own project, [check out their documentation](https://docs.snapshot.org/)! ## Protocol Fee Model and Core Pool Framework This documentation outlines Balancer's protocol fee model and core pool framework across Balancer v2 and v3, as established by governance with [BIP-734](https://snapshot.org/#/s\:balancer.eth/proposal/0x7a451385e49e341dce818927bf36aa35dfc6e42dabe328cb34e873c84fa452e4). ### Summary Balancer's protocol fee model is designed to optimize revenue generation and redistribution for liquidity providers (LPs), veBAL holders, and the DAO. The model differs between Balancer v2 and v3, with v3 introducing a more LP-friendly yield fee structure to drive adoption of boosted pools technology. \::: info Key Changes in v3 * Reduced yield fees from 50% to 10% to increase adoption * Universal 50% swap fees across all pools * Simplified fee distribution model * Introduction of boosted pools technology \::: ![veBAL Pool Flows](./images/veBAL-pool-flows-dark.webp) ### Fee Structure Overview \| Fee Type | Balancer v2 | Balancer v3 | \|----------|-------------|-------------| \| Yield Fees | 50% | 10% | \| Swap Fees | 50% | 50% | ### Fee Distribution Model Balancer protocol fees are distributed differently based on pool classification. Pools are classified as either core pools or non-core pools: * **Core Pools**: Pools that meet specific criteria (detailed in Core Pool Framework) and participate in the protocol's incentive flywheel. These pools typically contain yield-bearing assets or serve as primary liquidity venues. * **Non-Core Pools**: All other pools that don't meet core pool criteria or haven't applied for core pool status. #### Core Pools \| Recipient | Percentage | \|-----------|------------| \| Voting Incentives | 70% | \| veBAL Holders | 12.5% | \| DAO | 17.5% | #### Non-Core Pools \| Recipient | Percentage | \|-----------|------------| \| veBAL Holders | 82.5% | \| DAO | 17.5% | ### Fee Model on Balancer v3 #### Fee Structure * Yield fees: 10% on yield-bearing asset yields * Swap fees: 50% on all collected swap fees \::: tip Boosted Pools V3 introduces boosted pools that deploy underlying liquidity into yield-generating markets, allowing any token with an external yield market to be transformed into a yield-bearing asset. \::: ### Fee Model on Balancer v2 #### Fee Structure * Yield fees: 50% on yield-bearing asset yields (unchanged) * Swap fees: 50% on all collected swap fees (unchanged) \::: warning V2 Changes The non-core pool fee redirect to core pools (introduced in [BIP-457](https://snapshot.org/#/s\:balancer.eth/proposal/0x97856a0a66781666509f8e2d3dfa05aceabc543526e7fa546321f36f44030c03)) has been discontinued. \::: ### Core Pool Framework The core pool framework establishes criteria for pools to achieve core status and access enhanced benefits. #### Composition Requirements * Minimum 50% yield-bearing or boosted tokens for weighted or composable stable pools * 80/20 weighted pools that utilize Balancer as their primarily liquidity hub (e.g. RDNT:WETH 80/20 pool on Arbitrum). Protocols need to apply to core pool status for such pool types (Check the ALCX/ETH [proposal](https://forum.balancer.fi/t/bip-290-designate-alcx-eth-80-20-as-a-core-pool-with-10-emissions-cap/4753) as a good example) * Must maintain a minimum $100k TVL consistently #### Technical Requirements * No yield fee exemption * Fee settings delegated to Balancer governance in some form or another (no sovereignty on fees) * Alternative protocol fee setting required if default settings unavailable #### Token Requirements * Verified smart contracts for all tokens * No transfer restrictions or rebasing mechanics #### Core Pool List Maintenance The MAXYZ service provider is running and maintaining automation jobs that query on-chain states of all pools deployed on Balancer v2 and v3 that also have a veBAL gauge active. Based on this data following procedures are executed: * Bi-weekly evaluation of core pool status by checking that a gauge is active and TVL is >= $100k USD * New pool types require governance approval * Regular framework review through DAO governance \::: danger Important Note Core pool status can be lost if requirements are no longer met during bi-weekly evaluations. The currently active core pool list is publicly available via the [bal\_addresses repository](https://github.com/balancer/bal_addresses/blob/main/outputs/core_pools.json). \::: ### References and Additional Resources 1. [Protocol Fee Dune Dashboard](https://dune.com/balancer/protocol-fees) 2. [Core Pools Analytics Dashboard](https://balancer.defilytica.com/#/corePools) 3. [Automatic Core Pool List](https://github.com/balancer/bal_addresses/blob/main/outputs/core_pools.json) 4. [Fee Distribution Model Analysis](https://docs.google.com/spreadsheets/d/1dx82Hqcw53AwnXOkY3CFSJMshojjT7BzlsKf3zQa-FQ/edit?gid=759201376#gid=759201376) 5. [Core Pool Framework BIP](https://forum.balancer.fi/t/bip-457-core-pool-incentive-program-automation/5254) ### Routers In the Balancer v3 architecture, Routers serve as the pivotal interface for users, facilitating efficient interaction with the underlying Vault primitives. Rather than directly engaging with the Vault, users are encouraged to use Routers as their primary entry point. This approach streamlines operations and enhances flexibility by abstracting multi-step operations into simple user-facing functions. Key Functions: * Aggregation of Operations: Routers excel at consolidating complex user interactions into specific single-purpose function calls. This encapsulation provides users with a seamless and intuitive experience. * External API Provision: Acting as the external interface, Routers furnish users with accessible endpoints to interact with the protocol. This abstraction shields users from the complexities of the underlying system, promoting ease of use. * Integration with the Vault: Routers interface with the Vault, enabling seamless access to fundamental operations and liquidity management functionality. * Custom Logic Implementation: Beyond facilitating basic operations, Routers implement custom logic tailored to specific user requirements. This flexibility allows for the seamless incorporation of bespoke functionality into the protocol ecosystem. * Dynamic Update Capabilities: Unlike the Vault, Routers are stateless and do not retain liquidity. This inherent flexibility facilitates the deployment of new Router versions with patches and extended functionality, ensuring that users can always access the latest innovations. In essence, Routers play a pivotal role in abstracting complexity, enhancing user accessibility, and fostering innovation. By serving as the conduit between users and the underlying Vault system, Routers enable seamless interactions and diverse use cases. ### Balancer Routers Balancer has developed, audited and deployed Router contracts with the goal of providing simplified, easy to use functions for common liquidity actions. These Routers also serve as a useful reference for other Router implementations. #### Basic Router * Most common user actions, initialize/add/remove/swap * [API](../../developer-reference/contracts/router-api.md) * [Code](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Router.sol) #### Batch Router * Batch swaps * [API](../../developer-reference/contracts/batch-router-api.md) * [Code](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BatchRouter.sol) #### Buffer Router * Liquidity operations on buffers * [API](../../developer-reference/contracts/buffer-router-api.md) * [Code](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BufferRouter.sol) #### Composite Liquidity Router * Liquidity operations on pools containing ERC4626 tokens, and nested pools (i.e. pools containing the BPT of other pools). Nested pools are supported in CLR V3. * [API](../../developer-reference/contracts/composite-liquidity-router-api.md) * [Code](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/CompositeLiquidityRouter.sol) #### Add Unbalanced Via Swap Router * Specialized router for adding unbalanced liquidity to two-token pools by combining a proportional add with a swap. Useful for pool types that don't natively allow unbalanced liquidity (e.g., AutoRange Pools). * [API](../../developer-reference/contracts/unbalanced-add-via-swap-router-api.md) * [Code](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/UnbalancedAddViaSwapRouter.sol) Additionally all Routers expose [Query Functions](./queries.md) providing the ability to query the result of an operation using the latest onchain state. Latest deployment of the Routers can be found in the [deployments section](/developer-reference/contracts/deployment-addresses/mainnet.html). ## Query Functions ### Overview Queries provide the ability to simulate an operation and find its result without executing a transaction. [Balancer Routers](./overview.md#balancer-routers) provide a query for all state changing liquidity operations. An example of how this is useful can be seen when [setting slippage limits](/integration-guides/add-liquidity/sdk-tutorial.html#query-add-liquidity). \::: info queries are expected to be call offchain as they cannot be called onchain. \::: ### Router queries The detailed Router API description can be found in the [Router API section](/developer-reference/contracts/router-api.html). * `queryAddLiquidityProportional` * `queryAddLiquidityUnbalanced` * `queryAddLiquiditySingleTokenExactOut` * `queryAddLiquidityCustom` * `queryRemoveLiquidityProportional` * `queryRemoveLiquiditySingleTokenExactIn` * `queryRemoveLiquiditySingleTokenExactOut` * `queryRemoveLiquidityCustom` * `queryRemoveLiquidityRecovery` * `querySwapSingleTokenExactIn` * `querySwapSingleTokenExactOut` ### Batch Router queries The detailed Router API description can be found in the [Batch Router API section](/developer-reference/contracts/batch-router-api.html). * `querySwapExactIn` * `querySwapExactOut` ### Buffer Router queries The detailed Buffer Router API description can be found in the [Buffer Router API section](/developer-reference/contracts/buffer-router-api.html). * `queryInitializeBuffer` * `queryAddLiquidityToBuffer` * `queryRemoveLiquidityToBuffer` ### Composite Liquidity Router queries The detailed Router API description can be found in the [Composite Liquidity Router API section](/developer-reference/contracts/composite-liquidity-router-api.html). * `queryAddLiquidityUnbalancedToERC4626Pool` * `queryAddLiquidityProportionalToERC4626Pool` * `queryRemoveLiquidityProportionalFromERC4626Pool` * `queryAddLiquidityUnbalancedNestedPool` (V3+) * `queryRemoveLiquidityProportionalNestedPool` (V3+) ### Complex queries The Router contracts are primarily used as entrypoints for standard queries. However, Balancer's design allows for a more flexible querying mechanism. Any Vault operation that includes a `onlyWhenUnlocked` modifier can be queried natively. #### Quote This is facilitated through a `quote` mechanism. The concept of Transient Accounting enables the querying of complex Vault operations. To execute a query, the Router invokes the `quote` function on the Vault. The Vault requires that any invocation of `quote` be executed as a staticcall (ensured by the `query` modifier). Within the quote context, the Router has the flexibility to carry out a series of complex actions without the need to settle any debt. #### quoteAndRevert Since `quote` changes the Vault state some query combinations are not possible. For example, if you wanted to quote `querySwapExactIn` for POOL\_A but also `querySwapExactOut` for POOL\_A in its initial state, you would have to use `quoteAndRevert`. In this variant, the call always reverts and returns the result in the revert data (similar to the v2 mechanism). :::info This section is a technical explainer of how the Router works. If you are looking to integrate with the Router, take a look at the [API docs](./overview.md). ::: ### Example Transaction Flow The Router and Vault interact in a back and forth manner to achieve the intended outcome of liquidity or query operations. ![Router Vault interaction](/images/router-vault.png) Every user interaction going through the Router follows the same pattern of execution flow. The elegance of `unlock` wrapping the transaction in a Vault context is further explained in the [transient accounting](../vault/transient-accounting.md) section 1. The Router calls `unlock` on the Vault, allowing access to protected state-changing functions that perform token [accounting](../vault/transient-accounting.md) by triggering the `transient` modifier. You can think of this step as the Router opening a tab with the Vault, after which any debts or credits from subsequent Vault operations accrue to that tab. 2. The Router executes a hook function (ie: `swapSingleTokenHook`) which calls the Vault's primitives (ie: `swap`). These operations incur debt or supply credit to the currently open tab. 3. To finalize the user operation, all outstanding debts and credits accrued during `swap` need to be settled. If the tab is not settled, the transaction will revert. This settlement step (i.e., when `unlock` returns and executes the rest of the `transient` modifier) closes out the tab opened with the Vault in step 1. \::: info Retail users should not interact directly with the Vault. [Routers](../router/overview.md) serve as the entry point for all user actions. \::: ## The Vault The Vault is the core of the Balancer protocol; it is a smart contract that holds and manages all tokens in each Balancer pool. First introduced in Balancer v2, the vault architecture separates token accounting from pool logic, allowing for simplified pool contracts that focus on the implementation of their swap, add liquidity and remove liquidity logic. This architecture brings different pool designs under the same umbrella; the Vault is agnostic to pool math and can accommodate any system that satisfies a few requirements. Anyone who comes up with a novel idea can develop a custom pool plugged directly into Balancer's existing liquidity instead of needing to build their own Decentralized Exchange. In v3, the vault more formally defines the requirements of a pool contract, shifting core design patterns out of the pool and into the vault. The result is pool contracts that are compact and much easier to reason about. The Vault works with standard ERC20 tokens and is incompatible with rebasing or double entry point tokens. * [On-chain API](/developer-reference/contracts/vault-api.html) * [Configuration](/developer-reference/contracts/vault-config.html) * Features * [Transient accounting](/concepts/vault/transient-accounting.html) * [ERC20MultiToken](/concepts/vault/erc20-multi-token.html) * [Liquidity Buffers](/concepts/vault/buffer.html) * [Token types](/concepts/vault/token-types.html) * [Decimal scaling](/concepts/vault/token-scaling.html#decimal-scaling) * [Rate scaling](/concepts/vault/token-scaling.html#rate-scaling) * [Yield fee](/concepts/vault/yield-fee.html) * [Swap fee](/concepts/vault/swap-fee.html) * [Live balances](/concepts/vault/token-scaling.html#live-balances) * [Liquidity invariant approximation](./features/liquidity-invariant-approximation.html) * [Flash Loans](/concepts/vault/flash-loans.html) ## Add/Remove liquidity types Balancer protocol leverages the [Liquidity invariant approximation](/concepts/vault/liquidity-invariant-approximation.html) to provide a generalized solution for add and remove liquidity operations. This enables the Vault to implement complex `unbalanced` and `singleAsset` liquidity operations that all custom AMMs built on Balancer support by default. The Vault's [`addLiquidity`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IVaultMain.sol#L93-L95) and [`removeLiquidity`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IVaultMain.sol#L112-L114) functions accept a `kind` argument that identifies the type of operation to be performed. As each `kind` has slightly different requirements, the argument impacts how the other function arguments are interpreted. If you're an integrator looking to implement add or remove liquidity for an existing pool, see the [Router Onchain API](/concepts/router/overview.html). ### Add liquidity ```solidity enum AddLiquidityKind { PROPORTIONAL, UNBALANCED, SINGLE_TOKEN_EXACT_OUT, DONATION, CUSTOM } ``` * `AddLiquidityKind.PROPORTIONAL` - Add liquidity in proportional amounts and receive exact amount of BPT out. * `AddLiquidityKind.UNBALANCED` - Add liquidity to a pool with exact amounts of any pool token, avoiding unnecessary dust in the user's wallet. * `AddLiquidityKind.SINGLE_TOKEN_EXACT_OUT` - Add liquidity to a pool with a single token and receive an exact amount of BPT out. * `AddLiquidityKind.DONATION` - Add liquidity without receiving any BPT. * `AddLiquidityKind.CUSTOM` - For AMMs with a use case not covered by the built-in functions, custom allows the pool to implement an add liquidity operation whose requirements are defined by the pool. Note that DONATION is only useful for narrow use cases, such as LVR reduction, and must be explicitly enabled on pool registration. It also precludes nesting the pool (i.e., using the BPT in other Balancer pools, or relying on the rate for anything external), as the BPT rates of pools that allow donation can be trivially manipulated. It is supported on standard Balancer pools (Weighted and Stable), but if you use it, be aware of the security implications, and make sure you know what you're doing! ### Remove liquidity ```solidity enum RemoveLiquidityKind { PROPORTIONAL, SINGLE_TOKEN_EXACT_IN, SINGLE_TOKEN_EXACT_OUT, CUSTOM } ``` * `RemoveLiquidityKind.PROPORTIONAL` - Remove liquidity from a pool in proportional amounts, causing zero price impact and avoiding the swap fee charged when exiting non-proportional. * `RemoveLiquidityKind.SINGLE_TOKEN_EXACT_IN` - Remove liquidity from a pool in a single token and burn an exact amount of BPT. * `RemoveLiquidityKind.SINGLE_TOKEN_EXACT_OUT` - Remove liquidity from a pool and receive an exact amount of a single token. * `RemoveLiquidityKind.CUSTOM` - For AMMs with a use case not covered by the built-in functions, custom allows the pool to implement a remove liquidity operation whose requirements are defined by the pool. \::: info This page is a work in progress \::: ## ERC4626 Liquidity Buffers Liquidity Buffers, an internal mechanism of the Vault, facilitate liquidity for pairs of an ERC4626 `asset` (underlying token like DAI) and the ERC4626 Vault Token (like waDAI). The Balancer Vault provides additional liquidity, enabling the entry into the ERC4626 Vault Token positions without the need to wrap or unwrap tokens through the lending protocol, thereby avoiding higher gas costs. ERC4626 liquidity buffers trade on a `previewDeposit` & `previewMint` basis. Meaning given an amount of 100 DAI, the liquidity buffer gives out waDAI based on the return value from `previewDeposit(100 DAI)`. A significant benefit of the Vault's liquidity buffers is that Liquidity Providers (LPs) can now provide liquidity in positions of [100% boosted pools](/concepts/explore-available-balancer-pools/boosted-pool.html) (two yield-bearing assets) while simultaneously adding gas efficient batch swap routes. It's important to note that ERC4626 liquidity buffers are not Balancer Pools. They are a concept internal to the Vault and only function with tokens that comply with the ERC4626 Standard. :::info If your organization is a DAO and you're seeking to enhance liquidity for your ERC4626 compliant token, Balancer's ERC4626 liquidity buffers can be a valuable tool. By providing POL to these buffers, you can enable LPs of your token to gain increased access to yield-bearing tokens. This arrangement allows LPs to concentrate on [boosted pools](/concepts/explore-available-balancer-pools/boosted-pool.html), while your DAO contributes POL to the buffer. ::: ### Adding liquidity to a buffer Liquidity can be added to a buffer for a specific token pair. Buffers must be initialized before use, by invoking the `initializeBuffer` function in the [Buffer Router](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BufferRouter.sol). Initialization is the only operation that can be done "unbalanced," and sets the initial proportions of wrapped and underlying tokens. Thereafter, liquidity can only be added and removed proportionally. This is for simplicity and security reasons, as allowing ongoing unbalanced adds/removes would mean supporting implicit wrap/unwrap operations (in the same way unbalanced liquidity operations on pools involve an implicit swap). it is strongly recommended to initialize ERC4626 buffers *before* deploying any pools with corresponding wrapped tokens. To ensure the pools work as intended, buffers should be created and funded by sponsors prior to deploying pools designed to use them. Note that from an economic perspective, there is no intrinsic benefit to being an LP in a buffer. Buffers aren’t pools, so there are no swap fees, no yield participation (compared to simply HODLing), and no connection to the LM system. Funding them is permissionless, but who would do it? The general idea is that funding parties would be DAO partners (starting with the Balancer DAO) who are motivated to support the platform and promote use of their protocol or yield-bearing token. Immediate access to Balancer liquidity and gas-efficient trades for community members should be enough to entice DAOs and lending protocols to contribute. They would be potentially sacrificing yield on a relatively small position, in hopes that the UX improvement and gas savings for retail users will attract disproportionately greater trade volume and TVL. Note also that since buffers are not pools, buffer liquidity is not tokenized. Instead of receiving BPT in return for a buffer deposit, the system calculates buffer "shares," and maintains internal accounting of the total shares issued, and the share balance per LP. An important consequence of this is that these shares cannot be transferred. Only the account (or contract) that added liquidity will be able to remove it, so if a contract is involved in providing liquidity, care must be taken to ensure that contract supports all necessary operations. Providing liquidity to buffers has no benefit for retail users, who should not be calling these functions. (Accordingly, there is no UI for them.) ```solidity /** * @notice Adds liquidity for the first time to an internal ERC4626 buffer in the Vault. * @dev Calling this method binds the wrapped token to its underlying asset internally; the asset in the wrapper * cannot change afterwards, or every other operation on that wrapper (add / remove / wrap / unwrap) will fail. * To avoid unexpected behavior, always initialize buffers before creating or initializing any pools that contain * the wrapped tokens to be used with them. * * @param wrappedToken Address of the wrapped token that implements IERC4626 * @param exactAmountUnderlyingIn Amount of underlying tokens that will be deposited into the buffer * @param exactAmountWrappedIn Amount of wrapped tokens that will be deposited into the buffer * @param minIssuedShares Minimum amount of shares to receive from the buffer, expressed in underlying token * native decimals * @return issuedShares the amount of tokens sharesOwner has in the buffer, denominated in underlying tokens * (This is the BPT of the Vault's internal ERC4626 buffer.) */ function initializeBuffer( IERC4626 wrappedToken, uint256 exactAmountUnderlyingIn, uint256 exactAmountWrappedIn, uint256 minIssuedShares ) external returns (uint256 issuedShares); ``` Thereafter, additional liquidity can be added by invoking the `addLiquidityToBuffer` function in the [Buffer Router](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BufferRouter.sol), where you designate the ERC4626 Token as the buffer reference. Note that for security reasons, liquidity can only be added (or removed) proportionally. It's important to note that a buffer can still function with zero liquidity. It can be used to wrap and unwrap assets, meaning that even an empty buffer can facilitate swaps through the Vault. ```solidity /** * @notice Adds liquidity proportionally to an internal ERC4626 buffer in the Vault. * @dev Requires the buffer to be initialized beforehand. Restricting adds to proportional simplifies the Vault * code, avoiding rounding issues and minimum amount checks. It is possible to add unbalanced by interacting * with the wrapper contract directly. * * @param wrappedToken Address of the wrapped token that implements IERC4626 * @param maxAmountUnderlyingIn Maximum amount of underlying tokens to add to the buffer. It is expressed in * underlying token native decimals * @param maxAmountWrappedIn Maximum amount of wrapped tokens to add to the buffer. It is expressed in wrapped * token native decimals * @param exactSharesToIssue The amount of shares that `sharesOwner` wants to add to the buffer, in underlying * token decimals * @return amountUnderlyingIn Amount of underlying tokens deposited into the buffer * @return amountWrappedIn Amount of wrapped tokens deposited into the buffer */ function addLiquidityToBuffer( IERC4626 wrappedToken, uint256 maxAmountUnderlyingIn, uint256 maxAmountWrappedIn, uint256 exactSharesToIssue ) external returns (uint256 amountUnderlyingIn, uint256 amountWrappedIn); ``` ### Removing liquidity from a buffer After you've added liquidity to a buffer, you have the option to remove a specified amount based on the share amount. This is done by invoking the function `removeLiquidityFromBuffer` on the Vault. This function will subsequently burn a specified amount of your bufferShares and return the corresponding amount of tokens that you had previously provided. Note that in contrast to adding liquidity, removing liquidity depends critically on knowing the identity of the sender - otherwise, a malicious actor could withdraw liquidity belonging to another address. Since the Router must be trusted to send the correct sender to the Vault, removing liquidity through the Router would require governance to grant permission to that Router to perform the operation. This is not ideal, as it's a potential vector to lock funds (if governance revoked the permission), or even steal funds (if governance approved a malicious Router). In order to keep it permissionless, `removeLiquidityFromBuffer` was moved to the Vault, where it can be called directly (with no ambiguity about the sender), avoiding this issue. ```solidity /** * @notice Removes liquidity from an internal ERC4626 buffer in the Vault. * @dev Only proportional exits are supported, and the sender has to be the owner of the shares. * This function unlocks the Vault just for this operation; it does not work with a Router as an entrypoint. * * Pre-conditions: * - The buffer needs to be initialized. * - sharesOwner is the original msg.sender, it needs to be checked in the Router. That's why * this call is authenticated; only routers approved by the DAO can remove the liquidity of a buffer. * - The buffer needs to have some liquidity and have its asset registered in `_bufferAssets` storage. * * @param wrappedToken Address of the wrapped token that implements IERC4626 * @param sharesToRemove Amount of shares to remove from the buffer. Cannot be greater than sharesOwner's total shares. It is expressed in underlying token native decimals * @param minAmountUnderlyingOutRaw Minimum amount of underlying tokens to receive from the buffer. It is expressed in underlying token native decimals * @param minAmountWrappedOutRaw Minimum amount of wrapped tokens to receive from the buffer. It is expressed in * wrapped token native decimals * @return removedUnderlyingBalanceRaw Amount of underlying tokens returned to the user * @return removedWrappedBalanceRaw Amount of wrapped tokens returned to the user */ function removeLiquidityFromBuffer( IERC4626 wrappedToken, uint256 sharesToRemove, uint256 minAmountUnderlyingOutRaw, uint256 minAmountWrappedOutRaw ) external returns (uint256 removedUnderlyingBalanceRaw, uint256 removedWrappedBalanceRaw); ``` ### Using a buffer to swap. The swapper has the responsibility to decide whether a specific swap route should use Buffers by indicating if a given `pool` is a buffer. Remember: You can always use a buffer even it is does not have liquidity (instead it will simply wrap or unwrap). This is done by setting the boolean entry in the `SwapPathStep` struct. The `pool` param in this particular case is the wrapped Tokens entrypoint, meaning the address on which the user would call deposit. In the case of Aave, this would be waUSDC. ```solidity struct SwapPathStep { address pool; IERC20 tokenOut; // If true, pool is an ERC4626 buffer. Used to wrap/unwrap tokens if pool doesn't have enough liquidity. bool isBuffer; } ``` The availability of sufficient liquidity in the buffer affects the gas cost of the swap. If the buffer lacks enough liquidity, the gas cost increases. This is because the Vault has to get the additional liquidity from the lending protocol, which involves either depositing into or withdrawing from it. Buffers aim to streamline the majority of trades by eliminating the need to wrap or unwrap the swapper's tokens. Instead, they route these tokens through the Balancer trade paths. In the case of trading DAI to USDC via (DAI-waDAI Buffer, waDAI - waUSDC Boosted pool, USDC-waUSDC Buffer) for a 3 hop trade the `SwapPathExactAmountIn` would look like: ```solidity struct SwapPathExactAmountIn { IERC20 tokenIn; // For each step: // If tokenIn == pool, use removeLiquidity SINGLE_TOKEN_EXACT_IN. // If tokenOut == pool, use addLiquidity UNBALANCED. SwapPathStep[] steps; uint256 exactAmountIn; uint256 minAmountOut; } ``` ```solidity SwapPathExactAmountIn({ tokenIn: address(DAI), steps: [ SwapPathStep({ pool: address(waDAI), // the address where the Vault calls `deposit` or `mint` depending on SWAP_TYPE and Buffer liquidity tokenOut: IERC20(address(waDAI)), isBuffer: true }), SwapPathStep({ pool: address(boostedPool) tokenOut: IERC20(address(waUSDC)), isBuffer: false }), SwapPathStep({ pool: address(waUSDC) tokenOut: IERC20(address(USDC)), isBuffer: true }) ], exactAmountIn: uint256(myExactAmountIn) // your defined amount minAmountOut: uint256(myMinAmountOut) // your calculated min amount out }) ``` The trade will execute regardless of whether the Buffer has enough liquidity or not. Remember: If the buffer does not have enough liquidity it will simply additionally wrap or unwrap (and incur additional gas cost). #### Swapping DAI to USDC via 3 hops. Let's consider a swap from 10k DAI to USDC. The exchangeRate of 1waDAI - DAI is 1.1 & exchangeRate for waUSDC - USDC is 1.1. Involved pools & Buffers are: * DAI - waDAI Buffer * waDAI - waUSDC Boosted Pool (100% boosted) * USDC - waUSDC Buffer Considering these three pools only, the way to swap through them is via a `swapExactIn` operation on the BatchRouter. 1. Swap DAI to waDAI via the DAI - waDAI Buffer 2. Swap waDAI to waUSDC via the waDAI - waUSDC 100% Boosted pool 3. swap waUSDC to USDC via the USDC - waUSDC Buffer ##### Balances with enough buffer liquidity available Balances of pool & buffers before the batch swap: \| DAIBufferBalance before Swap | DAIBufferBalance after Swap | waDAIBufferBalance before Swap | waDAIBufferBalance after Swap | \| ---------------------------- | ---------------------------------------------------- | ------------------------------ | ------------------------------------------------------------ | \| 110k DAI | 120k DAI (+10k DAI) | 91k waDAI | 81909.1 waDAI (-9090.9 waDAI) | \| BoostedPool waDAI Balance before Swap | BoostedPool waDAI Balance after Swap | BoostedPool waUSDC Balance before Swap | BoostedPool waUSDC Balance after Swap | \| ------------------------------------- | --------------------------------------------------------------- | -------------------------------------- | --------------------------------------------------------------- | \| 900k waDAI | 909090.9 waDAI (+9090.9 waDAI) | 900k waUSDC | 890909.1 waUSDC (-9090.9 waUSDC) | \| USDCBufferBalance before Swap | USDCBufferBalance after Swap | waUSDCBufferBalance before Swap | waUSDCBufferBalance after Swap | \| ----------------------------- | ----------------------------------------------------- | ------------------------------- | ----------------------------------------------------------------- | \| 100k USDC | 90000 USDC (-10k USDC) | 91k waUSDC | 100090.9 waUSDC (+9090.9 waUSDC) | ##### Balances without enough buffer liquidity available in DAI - waDAI buffer Consider now an EXACT\_IN trade of 60k DAI to USDC. The DAI - waDAI buffer does not have enough liquidity to support the trade from its reserves, so it calls into the waDAI contract to wrap DAI to waDAI (amount) and additionally rebalances the buffer to balanced reserves. The exchangeRate of 1waDAI - DAI is 1.1 & exchangeRate for waUSDC - USDC is 1.1. With the incoming 60000 DAI the buffer wraps 63000 DAI in total as it: * Needs to give out 54545 waDAI to faciliate the 60k USDC out trade eventually * Needs to be balances based on the 1 waDAI = 1.1 DAI exchange rate Wrapping 63000 DAI gives 57272 waDAI out. The final waDAIBufferBalances after the swap are calculated as 40000 initialBalance + 57272 waDAI from wrapping - 54545 waDAI to faciliate the trade. \| DAIBufferBalance before Swap | DAIBufferBalance after Swap | waDAIBufferBalance before Swap | waDAIBufferBalance after Swap | \| ---------------------------- | ---------------------------------------------------- | ------------------------------ | ---------------------------------------------------------- | \| 50k DAI | 47000 DAI (-3000 DAI) | 40k waDAI | 42727 waDAI (+2727 waDAI) | \| BoostedPool waDAI Balance before Swap | BoostedPool waDAI Balance after Swap | BoostedPool waUSDC Balance before Swap | BoostedPool waUSDC Balance after Swap | \| ------------------------------------- | ------------------------------------------------------------------ | -------------------------------------- | ------------------------------------------------------------------ | \| 900k waDAI | 954545.45 waDAI (+54545.45 waDAI) | 900k waUSDC | 845454.55 waUSDC (-54545.45 waUSDC) | \| USDCBufferBalance before Swap | USDCBufferBalance after Swap | waUSDCBufferBalance before Swap | waUSDCBufferBalance after Swap | \| ----------------------------- | ------------------------------------------------------- | ------------------------------- | -------------------------------------------------------------------- | \| 100k USDC | 40000 USDC (-60000 USDC) | 91k waUSDC | 145545.45 waUSDC (+54545.45 waUSDC) | Even though the DAI - waDAI buffer did not have enough liquidity the trade was successfully routed via Balancer. The difference now is that the Vault utilized the buffer internal wrapping capability to wrap DAI into waDAI & rebalanced itself. ## ERC20MultiToken ERC20MultiToken was inspired by [ERC1155](https://docs.openzeppelin.com/contracts/3.x/erc1155), but customized for Balancer v3. At a high level, it allows the [Balancer Vault](/concepts/vault) full control over Balancer Pool Token (BPT) accounting, enabling it to both mint and burn BPT directly. By centralizing both token and BPT accounting in the vault, Balancer v3 ensures atomic updates to critical pool state. In contrast to ERC1155, ERC20MultiToken allows Balancer Pool Tokens to be fully ERC20-compliant, supporting composability. The full implementation of ERC20MultiToken can be found [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/token/ERC20MultiToken.sol). The snippet below presents the storage variables implemented in ERC20MultiToken. We can see that each variable has a top level mapping that is indexed on the address of the token (pool): ```solidity // Users' pool token (BPT) balances. mapping(address token => mapping(address owner => uint256 balance)) private _balances; // Users' pool token (BPT) allowances. mapping(address token => mapping(address owner => mapping(address spender => uint256 allowance))) private _allowances; // Total supply of all pool tokens (BPT). These are tokens minted and burned by the Vault. // The Vault balances of regular pool tokens are stored in `_reservesOf`. mapping(address token => uint256 totalSupply) private _totalSupplyOf; ``` Additionally, we can observe that each action `mint`, `burn`, `approve`, etc. Takes the pool's address as the first argument, and additionally invokes ERC20-compliant events on BalancerPoolToken. ```solidity function _approve(address token, address owner, address spender, uint256 amount) internal { if (owner == address(0)) { revert ERC20InvalidApprover(owner); } if (spender == address(0)) { revert ERC20InvalidSpender(spender); } _allowances[token][owner][spender] = amount; emit Approval(token, owner, spender, amount); // We also emit the "approve" event on the pool token to ensure full compliance with the ERC20 standard. // If this function fails we keep going, as this is used in recovery mode. // Well-behaved pools will just emit an event here, so they should never fail. try BalancerPoolToken(pool).emitApproval(owner, spender, amount) {} catch { // solhint-disable-previous-line no-empty-blocks } } ``` ### Where is the public interface? You'll notice that [ERC20MultiToken.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/token/ERC20MultiToken.sol) contains only internal functions. You can find the public interface defined in [IVaultExtension.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IVaultExtension.sol#L231-L290) and implemented in [VaultExtension.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L589-L630). To ensure that the state changing public interface is always delegate-called by the vault, each function has the [onlyVaultDelegateCall](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L69-L72) modifier. ```solidity function approve(address owner, address spender, uint256 amount) external onlyVaultDelegateCall returns (bool) { _approve(msg.sender, owner, spender, amount); return true; } ``` ### How is ERC20 compliance achieved? ERC20MultiToken leverages the relationship between the Balancer vault and its pools to ensure that all pool tokens (BPT) are fully ERC20-compliant. For a detailed discussion on how this is achieved, refer to the [BalancerPoolToken](/concepts/core-concepts/balancer-pool-tokens.html) section in the docs, or go directly to the implementation [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol). ## Introduction Flash loans in Balancer V3 allow users to borrow assets without collateral, as long as the borrowed amount is repaid within the same transaction. This page explains the logic behind executing a flash loan and settling it correctly using the Balancer V3 Vault. ## Prerequisites * Basic understanding of Solidity and smart contract development. * Familiarity with ERC20 token approvals and transfers. * Knowledge of Balancer V3's Vault architecture. ## Flash Loan Process 1. **Unlocking the Vault:** Flash loans in Balancer V3 operate within a transient unlocked state, where the Vault grants temporary access to funds. 2. **Receiving Funds:** The requested asset is transferred to the borrower contract. 3. **Executing Transactions:** The borrower contract can use the funds for any logic, such as liquidations, lending, or arbitrage. 4. **Repaying the Loan:** The borrowed amount must be returned before the transaction ends. 5. **Settling with the Vault:** The contract ensures that the Vault registers the repayment and completes the process. ## Example Contract Implementation ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.24; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IVaultMain } from "./IVaultMain.sol"; contract BalancerFlashLoan { IVaultMain public immutable balancerVault; address public immutable loanToken; address public owner; constructor(address _balancerVault, address _loanToken) { balancerVault = IVaultMain(_balancerVault); loanToken = _loanToken; owner = msg.sender; } modifier onlyOwner() { require(msg.sender == owner, "Not owner"); _; } function executeFlashLoan(uint256 amount) external onlyOwner { // Prepare calldata for the vault callback bytes memory userData = abi.encode(amount); balancerVault.unlock(abi.encodeWithSelector(this.receiveFlashLoan.selector, userData)); } function receiveFlashLoan(bytes memory userData) external { require(msg.sender == address(balancerVault), "Unauthorized callback"); // Decode flash loan amount uint256 amount = abi.decode(userData, (uint256)); // Send some tokens from the vault to this contract (taking a flash loan) balancerVault.sendTo(IERC20(loanToken), address(this), amount); // Execute any logic with the borrowed funds (e.g., arbitrage, liquidation, etc.) // Repay the loan IERC20(loanToken).transfer(address(balancerVault), amount); // Settle the repayment balancerVault.settle(IERC20(loanToken), amount); } } ``` :::info Key Considerations when using Flash Loans: * Ensure Approval: If interacting with other contracts, ensure the Balancer Vault has sufficient token allowance. * Transaction Atomicity: The entire flash loan execution and repayment must occur within the same transaction. * Settlement Accuracy: The `settle()` function is required to inform the Vault that the borrowed funds have been repaid and to settle the balances. ::: ## Liquidity Invariant Approximation Adding and removing liquidity are considered liquidity operations in the context of this page. A Balancer pool allows adding and removing liquidity not only proportionally, but also in non-proportional or "unbalanced" ways. An unbalanced add or remove liquidity can be considered a combination of a proportional add or remove plus a swap. The vault must handle both scenarios with the same outcome for users and LPs, in order to ensure a fair settlement system. ### Theory Liquidity operations that are disproportionate allow for indirect swaps. This scenario must have the same outcome as swaps and proportional liquidity operations combined. This means: * Swap fees for indirect swaps must not be lower than those for direct swaps * BPT amounts are properly minted in both cases (an indirect swap, and a proportional liquidity operation plus a swap) * Both users must have the same net token balances after each operation ### Examples Alice starts with balances of \[100, 0]. She executes addLiquidityUnbalanced(\[100, 0]) and then removes liquidity proportionally, resulting in balances of \[66, 33]. Bob, starting with the same balances \[100, 0], performs a swapExactIn(34). We determine the amount Alice indirectly traded as 34 (100 - 66 = 34), allowing us to compare the swap fees incurred on the trades. This comparison ensures that the fees for a direct swap remain higher than those for an indirect swap. Finally, we assess the final balances of Alice and Bob. Two criteria must be satisfied: 1. The initial token balances for the trade should be identical, meaning Alice's \[66, ...] should correspond to Bob's \[66, ...]. 2. The resulting balances from the trade should ensure that Bob always has an equal or greater amount than Alice. However, the difference should not be excessive, i.e., we aim not to disadvantage users in liquidity operations. This implies that Alice's balance \[..., 33] should be less than or at most equal to Bob's \[..., 34]. ### Implications This methodology and evaluation criteria apply to all disproportionate liquidity operations and pool types. Moreover, this approach confirms the correct amount of BPT (Balancer Pool Tokens) minted or burned for liquidity operations. If more BPT were minted or fewer BPT were burned than required, it would result in Alice having more assets at the end than Bob. \::: info Custom pool implication As a custom pool developer, you are faced with the choice of allowing these combinations in your pool. If you chose to do so, you must verify the liquidity-invariant approximation approach in your tests to ensure fair and even settlement across various operations. \::: ### Verification For reference, see the [`LiquidityApproximation.t.sol`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/test/foundry/LiquidityApproximation.t.sol) test file where the required assertions are verified in `assertLiquidityOperationNoSwapFee()` and `assertLiquidityOperation()`. ## Swap fee A swap fee is charged for each swap, as well as on the non-proportional amounts in add/remove liquidity operations. When a pool is registered, the initial swap fee is passed as a parameter and stored as part of [the pool's configuration](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L28-L39). The swap fee is always charged on the amount in (i.e., on the given amount for EXACT\_IN, and the calculated amount for EXACT\_OUT). :::info Let's imagine a liquidity pool that maintains an equal balance of DAI and USDC, known as a 50/50 pool. A user decides to add liquidity to this pool, but does so in an unbalanced manner: they contribute 15 DAI and 10 USDC. In order to maintain the balance of the pool, an equal amount of DAI and USDC should be added. In this case, that would be 10 DAI and 10 USDC. This balanced contribution is referred to as the 'proportional add portion'. However, the user has added an extra 5 DAI, which is not matched by an equivalent amount of USDC. This extra 'non-proportional' contribution disrupts the balance of the pool. As a result, the pool charges swap fees on this non-proportional amount. The exact amount of the non-proportional contribution, and therefore the amount that incurs swap fees, is determined by the current balances of DAI and USDC in the pool. ::: ### Setting a static swap fee Users who have been granted authorization have the capability to set a fixed swap fee percentage for liquidity pools. This can be done by invoking the `vault.setStaticSwapFeePercentage(address pool, uint256 swapFeePercentage)` function. If users prefer not to have the swap fee of a pool controlled by Balancer governance (through the `Authorizer`), they can opt out by providing a non-zero address for the `swapManager`. This address could be a multi-sig or custom contract. Unlike in v2, v3 does not impose limits on the swap fee percentage at the Vault level. Rather, these limits are set at the pool level (0.001% - 10% for standard Balancer Weighted, and 0.0001% - 10% for Stable pools). ### Swap fees by pool type. Different types of pools can have varying minimum and maximum swap fees. These variations are determined by the mathematical security properties and specific product requirements. Maximum and minimum swap fees are set on a per pool basis implemented via the `ISwapFeePercentageBounds` interface, which is inherited by `IBasePool`: ```solidity /// @return minimumSwapFeePercentage The minimum swap fee percentage for a pool function getMinimumSwapFeePercentage() external view returns (uint256); /// @return maximumSwapFeePercentage The maximum swap fee percentage for a pool function getMaximumSwapFeePercentage() external view returns (uint256); ``` This means that all new pool types (assuming they implement `IBasePool`) will need to think about what the swap fee range should be, according to the pool type's math and other constraints, then override and set the values accordingly. (Note that invariant limits must also be defined for new pool types, by implementing `IUnbalancedLiquidityInvariantRatioBounds`.) ### Dynamic swap fee (using Hooks) Using Hooks a pool can be set up to use dynamic swap fees. When registering a pool with a dynamic swap fee, `shouldCallComputeDynamicSwapFee` should be true in the HooksConfig. Instead of getting the swap fee from the [pool's configuration](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L28-L39), the Vault uses the [`onComputeDynamicSwapFeePercentage()`](/developer-reference/contracts/hooks-api.html#oncomputedynamicswapfeepercentage) hook to fetch the dynamic swap fee. This function returns the swap fee percentage to be used for the current swap. It's important to note that even when a pool is set to use dynamic swap fees, it still maintains a static swap fee, which is not directly used (though it is sent to the dynamic fee hook for reference). :::info The capability to compute dynamic swap fee percentages opens up new and creative ways to calculate fees. For example, the fees can be adjusted depending on the swap direction, or configured to maintain a token's pegged value. In addition to these, dynamic swap fees can also be used to: * Adjust fees based on market conditions: higher fees can be charged during periods of high volatility to discourage frequent trading and maintain stability. * Implement tiered fee structures: different fees can be charged based on the size of the swap, with larger swaps incurring higher fees. * Encourage certain types of trading behavior: lower fees can be set for trades that contribute to the pool's liquidity or stability. ::: ## Token scaling Working with fixed-point math in Solidity presents a unique set of challenges that developers must navigate to ensure accurate and secure smart contract functionality. In an effort to abstract this complexity, the Vault manages decimal and rate scaling internally, scaling all token balances and input values prior to being sent to the [Pool](/concepts/explore-available-balancer-pools/). By doing this, we ensure consistency of rounding direction across all [Custom Pool](/build/build-an-amm/create-custom-amm-with-novel-invariant.html) implementations, removing a significant amount of complexity from the pool and allowing it to focus primarily on its invariant implementation. ### Decimal scaling All token balances and input values are scaled to 18 decimal places prior to being sent to the [Pool](/concepts/explore-available-balancer-pools/). Once scaled, these numbers are referred to internally as `scaled18`. #### Pool registration During pool registration, the vault stores the `tokenDecimalDiffs` for each token in the pool in the `PoolConfig` bits. Refer to the full implementation [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L230). ```solidity tokenDecimalDiffs[i] = uint8(18) - IERC20Metadata(address(token)).decimals(); ``` A token with 6 decimals (USDC) would have a `tokenDecimalDiff = 18 - 6 = 12`, and a token with 18 decimals (WETH) would have a `tokenDecimalDiff = 18 - 18 = 0`. Note that tokens with more than 18 decimals would revert here with an arithmetic error. #### Scaling factors The `tokenDecimalDiffs` are then used to calculate the decimal `scalingFactors` for each token. This implementation can be found in the [PoolConfigLib](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/lib/PoolConfigLib.sol#L240-L259). ```solidity function getDecimalScalingFactors( PoolConfigBits memory config, uint256 numTokens ) internal pure returns (uint256[] memory) { uint256[] memory scalingFactors = new uint256[](numTokens); bytes32 tokenDecimalDiffs = bytes32(uint256(config.getTokenDecimalDiffs())); for (uint256 i = 0; i < numTokens; ++i) { uint256 decimalDiff = tokenDecimalDiffs.decodeUint( i * PoolConfigConst.DECIMAL_DIFF_BITLENGTH, PoolConfigConst.DECIMAL_DIFF_BITLENGTH ); // This is equivalent to `10**(18+decimalsDifference)` but this form optimizes for 18 decimal tokens. scalingFactors[i] = FixedPoint.ONE * 10 ** decimalDiff; } return scalingFactors; } ``` #### References To review the scaling implementations, refer to [ScalingHelpers.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/solidity-utils/contracts/helpers/ScalingHelpers.sol). You can review the logic flow of [swap](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L181-L275), [addLiquidity](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L489-L572) and [removeLiquidity](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L761-L841) to better understand how the vault manages token scaling. ### Rate scaling With the successful rollout of [The Merge](https://ethereum.org/roadmap/merge) and the adoption of [ERC-4626](https://docs.openzeppelin.com/contracts/4.x/erc4626), the ecosystem has seen a proliferation of yield bearing tokens. Recognizing the pivotal role that LSTs will play in the liquidity landscape moving forward, Balancer seeks to position itself as the definitive yield-bearing hub in DeFi. To facilitate the adoption of yield bearing liquidity, Balancer abstracts the complexity of managing LSTs by centralizing all rate scaling in the vault, providing all pools with uniform rate scaled balances and input values by default, drastically reducing LVR and ensuring that yield is not captured by arbitrage traders. #### What is a token rate The classical example of a token with a rate is Lido's [wstETH](https://help.lido.fi/en/articles/5231836-what-is-lido-s-wsteth). As [stETH](https://help.lido.fi/en/articles/5230610-what-is-steth) accrues value from staking rewards, the exchange rate of wstETH -> stETH grows over time. #### How does the Balancer Vault utilize token rates Besides [decimal scaling](#decimal-scaling) a token's rate is taken into account in Balancer in the following scenarios: * Price computation as part of Stable and Boosted pools * Yield fee computation on tokens with rates A token's rate is defined as an 18-decimal fixed point number. It represents the ratio of the token's value relative to that of its underlying. For example, a rate of 1.1e18 of rETH means that 1 rETH has the value of 1.1 ETH. #### Creating a pool with tokens that have rates On pool [register](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IVaultExtension.sol#L97-L106) a [TokenConfig](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L142-L147) is provided for each of the pool's tokens. To define a token with a rate, specify the token type as `TokenType.WITH_RATE`. Additionally, you must provide a `rateProvider` address that implements the [`IRateProvider`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/solidity-utils/helpers/IRateProvider.sol) interface. Refer to [Token types](/concepts/vault/token-types.html) for a detailed explanation on each token type. #### Rate scaling usage Rate scaling is used on every `swap`, `addLiquidity` and `removeLiquidity` operation. If the token was registered as `TokenType.WITH_RATE`, an external call to the Rate Provider is made via `getRate`. If the `TokenType.STANDARD` was selected, the rate is always `1e18`. These rates are used to upscale the `amountGiven` in the Vault primitives. :::info 1. With a swap, the known token amount is given in native decimals as [`amountGivenRaw`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L223) 2. `AmountGivenRaw` is upscaled 3. `AmountGivenScaled18` is forwarded to the pool. 4. Rates are undone before calculating and returning either `amountIn` or `amountOut`. ::: You can read more on the [Rate Providers page](/concepts/core-concepts/rate-providers.html). ### Live balances The term `liveBalances` is used internally to refer to balances that have been: 1. [Decimal scaled](/concepts/vault/token-scaling.html#decimal-scaling) - Upscaled to 18 decimals 2. [Rate scaled](/concepts/vault/token-scaling.html#rate-scaling) - Adjusted for token rates 3. [Yield fee adjusted](/concepts/vault/yield-fee.html) - Had yield fees deducted. Any token balances sent to the pool will always be in live balance form. This ensures consistency across all tokens and removes the burden of token scaling from the pool logic. ## Token Types In line with Balancer's [yield-bearing native thesis](https://medium.com/balancer-protocol/balancer-the-yield-bearing-asset-thesis-f44489ba2deb), the vault supports a token specialization to provide built-in support for yield-bearing assets. ### Tokens with external rates (`WITH_RATE`) Tokens should be defined as `WITH_RATE` when they have an externally available exchange rate to some other asset that the AMM should consider when pricing assets internally. Two classical examples are: * `wstETH` - A wrapped version of the rebasing token stETH, the wstETH rate represents the exchange rate of wstETH -> stETH, which grows as staking rewards accumulate. * `EURe` - When pairing a EURO stable coin against a USD stable coin, there is a known FX market exchange rate of EUR -> USD. See [Rate scaling](./token-scaling.md#rate-scaling) For an in-depth explanation on how Balancer manages tokens with rates. When [registering](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L144-L166) a token as `WITH_RATE`, your [`TokenConfig`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L84-L89) should resemble the following: ```solidity TokenConfig({ token: 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0, tokenType: TokenType.WITH_RATE, rateProvider: 0x72D07D7DcA67b8A406aD1Ec34ce969c90bFEE768, paysYieldFees: true }) ``` \::: info What does `paysYieldFees` mean? paysYieldFees means that a portion of the yield a specific token accrues is used to fund Balancer DAO operations. Similar to how swap fees accrue to the Balancer treasury. \::: ### All other tokens (`STANDARD`) Any token that is not `WITH_RATE` should be set as `STANDARD`. When [registering](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/VaultExtension.sol#L144-L166) a token as `STANDARD`, your [`TokenConfig`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L84-L89) should resemble the following: ```solidity TokenConfig({ token: 0xba100000625a3754423978a60c9317c58a424e3D, tokenType: TokenType.STANDARD, rateProvider: 0x0000000000000000000000000000000000000000, paysYieldFees: true }) ``` ## Transient accounting Transient accounting shifts the validation of accurate token accounting to the start and conclusion of a Vault interaction. This is achieved by initiating a transient state that monitors the debt and credit created during vault interactions. This transient state guarantees the atomic execution of operations within it and confirms the proper settlement of all debt and credit at the end of the execution, prior to exiting the transient state. Upon activation of the transient state, the vault is unlocked and permissions to certain Vault functions are opened up. The Vault then returns execution control back to the Router through a hook. Anyone is now authorized to call: * `sendTo`: Sends tokens from the Vault to a recipient. * `settle`: Balances the changes for a token. * `takeFrom`: Collects tokens from a sender. * `swap`: Exchanges one type of token for another. * `addLiquidity`: Adds one or more tokens to a liquidity pool. * `removeLiquidity`: Removes one or more tokens from a liquidity pool. * `erc4626BufferWrapOrUnwrap`: Wraps/unwraps tokens based on provided parameters. * `initializeBuffer`: Initializes an ERC4626 buffer. This sets the underlying asset token, and is necessary to enable use of the buffer. * `addLiquidityToBuffer`: Adds liquidity to an ERC4626 buffer. * `removeLiquidityFromBuffer`: Removes liquidity from an ERC4626 buffer. * `initialize`: Initialize a liquidity pool. * `removeLiquidityRecovery`: Removes liquidity proportionally, burning an exact pool token amount (only in Recovery Mode). ### Key concepts #### 1. Enabling transient state The transient state is activated when the `transient` modifier is used during the unlocking of the Vault. As the transient state is enabled, the Vault hands back control to the caller through a hook, which allows all the previously mentioned functions to be called. After the operations within the hook are completed, the transient state is expected to be closed. The transaction can only succeed if all the credit and debt accumulated during the hook execution is settled, which is indicated by `_nonZeroDeltaCount()` being zero. ```solidity modifier transient() { bool isUnlockedBefore = _isUnlocked().tload(); if (isUnlockedBefore == false) { _isUnlocked().tstore(true); } // The caller does everything here and has to settle all outstanding balances _; if (isUnlockedBefore == false) { if (_nonZeroDeltaCount().tload() != 0) { revert BalanceNotSettled(); } _isUnlocked().tstore(false); } } ``` #### 2. Debt or credit tracking The majority of functions listed above either accrue debt (`_takeDebt`) or supply credit (`_supplyCredit`) as part of their implementation. The amount of debt taken or credit supplied is stored in an internal `TokenDeltaMappingSlotType` type. ```solidity /** * @notice Retrieves the token delta for a specific token. * @dev This function allows reading the value from the `_tokenDeltas` mapping. * @param token The token for which the delta is being fetched * @return The delta of the specified token */ function getTokenDelta(IERC20 token) external view returns (int256); ``` Each time debt is taken or credit is supplied for a given token, the token delta is updated to reflect the net change. If the net change for a token zero out, the internal `_nonZeroDeltaCount()` is decremented; a non-zero net change increments the `_nonZeroDeltaCount()`. ```solidity function _accountDelta(IERC20 token, int256 delta) internal { // If the delta is zero, there's nothing to account for. if (delta == 0) return; // Get the current recorded delta for this token. int256 current = _tokenDeltas().tGet(token); // Calculate the new delta after accounting for the change. int256 next = current + delta; // If the resultant delta becomes zero after this operation, // decrease the count of non-zero deltas. if (next == 0) { _nonZeroDeltaCount().tDecrement(); } // If there was no previous delta (i.e., it was zero) and now we have one, // increase the count of non-zero deltas. else if (current == 0) { _nonZeroDeltaCount().tIncrement(); } // Update the delta for this token. _tokenDeltas().tSet(token, next); } ``` This transient accounting approach starts tracking token balances at the beginning of an operation (when opening a tab) and stops at the end (when closing the tab), providing full flexibility for any token amount changes in between. This eliminates the need for additional balance management elsewhere, allowing for a clear separation between token accounting and execution logic. Before closing the temporary state, the only requirement is to ensure that `_nonZeroDeltaCount()` equals 0. ## Introduction Balancer Protocol Fees are collected by the Balancer Protocol on most operations, as a percentage of either pool swap fees or token yield. The Yield Fee is a specific type of Protocol fee that is applied to yield-bearing tokens, such as wstETH. The fee percentage is controlled by Balancer governance. The Yield Fee is distributed in three primary ways: * as liquidity incentives for [core pools](https://forum.balancer.fi/t/bip-19-incentivize-core-pools-l2-usage/3329). This forms a key part of the [Yield Bearing Asset Thesis](https://medium.com/balancer-protocol/balancer-the-yield-bearing-asset-thesis-f44489ba2deb). * to the Balancer DAO, providing a source of revenue for the protocol and contributing to the operational costs. * to veBAL holders. ## Implementation Yield fees are charged on every state changing Vault interaction if: * the token is configured as a [WITH\_RATE](token-types.md#tokens-with-external-rates-with_rate) type with `paysYieldFees` set to `true` * yield has accrued since the last fee computation Yield fees are computed by taking the difference between `currentLiveBalance` and `lastLiveBalance` and multiplying it by the `aggregateYieldFeePercentage`. The `aggregateYieldFeePercentage` is set by Balancer governance, and is defined in the [`ProtocolFeeController`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/ProtocolFeeController.sol). Note the use of live balances, which are described [here](./token-scaling.md#live-balances). \:::info What does `aggregate` mean? Collected fees are allocated between up to three recipients: the Balancer protocol, the pool creator (if defined on registration), and the LPs. The `ProtocolFeeController` is used to set the percentages for swap and yield fees for both the protocol and pool creators. In general, the protocol fee is collected first, and the remaining balance is split between the pool creator and LPs. See [this interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IProtocolFeeController.sol#L187-L194) for an example calculation. For performance reasons, the Vault stores only the aggregate percentage (i.e., the total fee, to be allocated later by the controller), computes only one "cut," and emits no events on the critical path. Collected aggregate fees can then be permissionlessly transferred to the `ProtocolFeeController`, where they are split between the protocol and pool creator, and available for permissioned withdrawal. The controller emits events that allow off-chain processes to monitor swap and yield fees per pool, albeit not in "real time" on every operation. The Vault stores accrued fees per pool per token in an \_aggregateFeeAmounts\[pool]\[token] mapping. The `bytes32` slot holds both yield and swap fees accrued by this pool. ```solidity // Aggregate protocol swap/yield fees accumulated in the Vault for harvest. // Reusing PackedTokenBalance for the bytes32 values to save bytecode (despite differing semantics). // It's arbitrary which is which: we define raw = swap; derived = yield. mapping(address pool => mapping(IERC20 token => bytes32 packedFeeAmounts)) internal _aggregateFeeAmounts; ``` \::: ```solidity function _computeYieldFeesDue( PoolData memory poolData, uint256 lastLiveBalance, uint256 tokenIndex, uint256 aggregateYieldFeePercentage ) internal pure returns (uint256 aggregateYieldFeeAmountRaw) { uint256 currentLiveBalance = poolData.balancesLiveScaled18[tokenIndex]; // Do not charge fees if rates go down. If the rate were to go up, down, and back up again, protocol fees // would be charged multiple times on the "same" yield. For tokens subject to yield fees, this should not // happen, or at least be very rare. It can be addressed for known volatile rates by setting the yield fee // exempt flag on registration, or compensated off-chain if there is an incident with a normally // well-behaved rate provider. if (currentLiveBalance > lastLiveBalance) { unchecked { // Magnitudes checked above, so it's safe to do unchecked math here. uint256 aggregateYieldFeeAmountScaled18 = (currentLiveBalance - lastLiveBalance).mulUp( aggregateYieldFeePercentage ); // A pool is subject to yield fees if poolSubjectToYieldFees is true, meaning that // `protocolYieldFeePercentage > 0`. So, we don't need to check this again in here, saving some gas. aggregateYieldFeeAmountRaw = aggregateYieldFeeAmountScaled18.toRawUndoRateRoundDown( poolData.decimalScalingFactors[tokenIndex], poolData.tokenRates[tokenIndex] ); } } } ``` The `aggregateYieldFeeAmountRaw` represents the final computed yield fee value. Here, 'Raw' signifies that the [rate scaling](./token-scaling.md#rate-scaling) has been reversed, as indicated by the `toRawUndoRateRoundDown` expression. :::info To check whether a token in a liquidity pool is subject to yield fees, you can call `vault.getPoolTokenInfo(address pool)`. The percentage of yield fees charged by the vault for a given pool can be read via `protocolFeeController.getPoolProtocolYieldFeeInfo(address pool)`. ::: ## APR Calculation \::: info Please note that for mainnet liquidity mining APRs can be boosted, therefore a range of 1x to 2.5x of the calculated APR is possible. \::: $$ minAPR(1x) = \frac{\frac{0.4}{ (workingSupply + 0.4) }\* gaugeRelWeight \* weeklyBALemission \* 52 \* priceOfBAL}{ pricePerBPT} \* 100 $$ where * `workingSupply` = value obtained from the gauge Vyper contract in step 2 * `gaugeRelWeight` = relative voting weight obtained from the gauge controller contract from step 3 * `weeklyBALemissions` = \*\*\*\* currently active weekly emissions, fixed at 145’000 BAL * `priceOfBAL` = price of BAL in $ as obtained from an external pricing provider * `pricePerBPT` = price per balancer pool token as inferred from step 4 ## veBAL Boost Calculation \::: info A range of 1x to 2.5x of the calculated APR is possible. A user may be interested in the minimum amount of veBAL for max boost, how their boost is calculated, and the maximum boost they can receive in cases where 2.5x is not attainable. \::: For these calculations consider the following: * `l` = the liquidity a user will provide and stake in a gauge * `L` = the total liquidity which is staked in a pool before a user stakes their own * `L'` = The total liquidity staked in the gauge after a user stakes $$ L' = L + l $$ * `workingSupply` = Working supply is related to the amount of veBAL corresponding to the individual stakers and total pool liquidity staked. This will be denominated in terms of the user and the pool. $$ workingSupply = \min \left(0.4 \* l + 0.6 \* L' \* \frac{veBAL \ Held}{Total\ veBAL} \ , \ l \right) $$ * `min_veBAL` = Minimum veBAL needed for maximum boost (2.5x) Minimum veBAL for Maximum Boost The minimum veBAL a liquidity provider will require in order to receive maximum incentives boost on their staked amount is calculated as follows: $$ Min \ veBAL\_{max \ boost} = Total \ veBAL \* \frac{ l } { L' } $$ ## Calculating Your Boost For a user to calculate their incentives boost they must go through the following steps: 1. Calculate the users' working supply $$ workingSupply\_{user} = min \left(0.4 \ \* \ l + 0.6 \ \* \ L' \* \frac{veBAL / held}{Total \ veBAL}, \ l \ \right) $$ 2. Calculate the users' non-boosted, or minimum working supply $$ Non-boosted \ working \ supply\_{user} = 0.4 \ \* \ l $$ 3. Calculate the user's boost multiplier using: $$ Boost = \frac{\frac { working \ supply\_{user} } { working \ supply\_{user} \ + \ total \ working \ supply\_{pool} } } { \frac { Non-boosted \ working \ supply\_{user} } { Non-boosted \ working \ supply\_{user} \ + \ total \ working \ supply\_{pool} } } $$ In the case a user already has liquidity staked in the pool and is adding additional liquidity the current working supply must be subtracted from the considerations. $$ Boost = \frac{\frac { working \ supply\_{user} } { working \ supply\_{user} \ + \ total \ working \ supply\_{pool} \ - \ current \ working \ supply\_{user} } } { \frac { Non-boosted \ working \ supply\_{user} } { Non-boosted \ working \ supply\_{user} \ + \ total \ working \ supply\_{pool} \ - \ current \ working \ supply\_{user} } } $$ ## Calculating Maximum Boost The maximum boost a user can receive in a pool will not always be 2.5x. Due to other holders in the pool, depending on their veBAL holdings, there is a point where one cannot own enough veBAL to dilute the rest of users to receive max boost. $$ max boost = 2.5 \ \* \ \frac{ Non-boosted \ working \ supply\_{user} \ + \ total \ working \ supply\_{pool} } {max \ working \ supply\_{user} \ + \ total \ working \ supply\_{pool}} $$ Similar to the user's boost above, if a user is adding more to their current position their current working supply must be subtracted to arrive at the correct result. $$ max boost = 2.5 \ \* \ \frac { Non-boosted \ working \ supply\_{user} + total working \ supply\_{pool} \ - \ current \ working \ supply\_{user} } { max \ working \ supply\_{user} \ + \ total \ working \ supply\_{pool}\ - \ current \ working \ supply\_{user}} $$ ## Estimating Gauge Incentive APRs Balancer introduced a new tokenomics system for the Balancer Governance Token (BAL) based on Curve's *ve* model. The system revolves around the following concepts: * A new token “veBAL” is obtained by locking 80:20 BAL:WETH BPTs for a certain duration. This token allows a user to participate in governance, determine where liquidity mining incentives go, and collect a share of protocol fees * veBAL holders can decide where liquidity mining incentives are directed by allocating their vote to "gauges" that are referencing staking contracts of pre-approved pools in the Balancer ecosystem (Mainnet, Arbitrum, Polygon, and Optimism) * The overall gauge vote percentage directs the weekly BAL emissions. If the weekly total amount is 145,000 BAL per week, a pool gauge with 1% of the vote will net in 1,450 BAL towards that gauge * Emissions are set based on the previous weeks voting round which concludes each Thursday 00:00 UTC To estimate the BAL liquidity mining APR for a certain gauge, various endpoints have to be read. Currently, there is not an API to obtain the APRs as they have to be calculated on the fly. Follow the steps below to calculate the liquidity mining APR for a certain gauge: 1. Obtain the current gauge allowlist from the front-end repo 2. Obtain the working supply for each gauge by reading gauge vyper contracts 3. Obtain the relative weight for each gauge via the gauge controller contract 4. Infer the price per Balancer Pool Token (BPT) from the balancer-v2 subgraph 5. Fetch the current BAL price ## Gauges ### Query Gauge by Mainnet Pool The easiest way is to query `getPoolGauge(poolAddress)` on the [`LiquidityGaugeFactory`](https://etherscan.io/address/0x4E7bBd911cf1EFa442BC1b2e9Ea01ffE785412EC#code). ### Query Gauge by L2/Sidechain Pool To get a gauge, query `getPoolGauge(poolAddress)` on the given network's `ChildChainLiquidityGaugeFactory`**.** \| Network | ChildChainLiquidityGaugeFactory | \| -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | \| Polygon | [`0x3b8cA519122CdD8efb272b0D3085453404B25bD0`](https://polygonscan.com/address/0x3b8cA519122CdD8efb272b0D3085453404B25bD0#code) | \| Arbitrum | [`0xb08E16cFc07C684dAA2f93C70323BAdb2A6CBFd2`](https://arbiscan.io/address/0xb08E16cFc07C684dAA2f93C70323BAdb2A6CBFd2#code) | ### Query Pending Tokens for a Given Pool The process differs slightly depending on if we're on Ethereum mainnet or an alternate network (ie Polygon, Arbitrum). No matter the network though, we need to first start at the relevant subgraph: * [Ethereum Gauges Subgraph](https://thegraph.com/hosted-service/subgraph/balancer-labs/balancer-gauges) * [Polygon Gauges Subgraph](https://thegraph.com/hosted-service/subgraph/balancer-labs/balancer-gauges-polygon) * [Arbitrum Gauges Subgraph](https://thegraph.com/hosted-service/subgraph/balancer-labs/balancer-gauges-arbitrum) #### Example Let's start with the [bb-a-USD pool](https://app.balancer.fi/#/pool/0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe) on Ethereum `0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe` ##### Query the Gauges Subgraph: ```graphql { liquidityGauges( where: { poolId: "0x7b50775383d3d6f0215a8f290f2c9e2eebbeceb20000000000000000000000fe" } ) { id } } ``` ##### Result: ```json { "data": { "liquidityGauges": [ { "id": "0x68d019f64a7aa97e2d4e7363aee42251d08124fb" } ] } } ``` Now that we have our Gauge contract address, we can query what the pending tokens are with the following pseudocode: ``` gaugeAddress="0x68d019f64a7aa97e2d4e7363aee42251d08124fb"; userAddress=; gaugeAbi=; gauge=contract(gaugeAddress, gaugeAbi) // How to get pending BAL **ONLY ON MAINNET** pendingBAL = gauge.claimable_tokens(userAddress).call(); // How to get pending tokens tokenAddress=; pendingToken = gauge.claimable_rewards(userAddress, tokenAddress).call(); ``` \::: warning L2 Gauges On Polygon and Arbitrum, the Gauges treat BAL the same as any other liquidity mining token, therefore instead of calling `claimable_tokens` \_\_ on those networks, you will use `claimable_rewards` \_\_ and pass in that network's BAL address. \::: ### Claim Pending Tokens for a Pool #### Ethereum Mainnet Use the [`claim_rewards()`](https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/liquidity-mining/contracts/gauges/ethereum/LiquidityGaugeV5.vy#L440-L450) function on the pool's gauge contract. #### Child Chains (L2, Sidechains, etc) Use the [`get_rewards()`](https://github.com/balancer/balancer-v2-monorepo/blob/master/pkg/liquidity-mining/contracts/gauges/ChildChainStreamer.vy#L139-L148) function on the pool's streamer contract. ### Query Tokens for a Gauge #### Sample Query ``` { liquidityGauges(where:{ poolId: "0x32296969ef14eb0c6d29669c550d4a0449130230000200000000000000000080" }) { id tokens { id symbol decimals totalDeposited } } } ``` #### Sample Response ``` { "data": { "liquidityGauges": [ { "id": "0xcd4722b7c24c29e0413bdcd9e51404b4539d14ae", "tokens": [ { "id": "0x5a98fcbea516cf06857215779fd812ca3bef1b32-0xcd4722b7c24c29e0413bdcd9e51404b4539d14ae", "symbol": "LDO", "decimals": 18, "totalDeposited": "150000" } ] } ] } } ``` \::: warning Be aware that if there are no tokens other than BAL for a given Gauge, the tokens array will come back empty. \::: ## How Many BPT in veBAL? Are you trying value or determine underlying assets that are locked in veBAL? You'll need to query a few things from the [veBAL contract](https://etherscan.io/address/0xc128a9954e6c874ea3d62ce62b468ba073093f25#readContract). Most notably: ``` underlyingBpt = veBAL.token(); (amount, end) = veBAL.locked(); ``` \::: danger Don't use `balanceOf` on the veBAL contract if you're trying to calculate value associated with underlying tokens. `balanceOf` returns a time dependent value only useful for querying a user's current voting power. \::: Now that you have the `underlyingBpt` address and the `amount` of those BPT that you have locked, you can now analyze, [value](https://docs-v2.balancer.fi/reference/lp-tokens/valuing.html#valuing), or [determine underlying tokens](https://docs-v2.balancer.fi/reference/lp-tokens/underlying.html#overview). ## veBAL \::: info The `FeeDistributor` address is [`0x26743984e3357efc59f2fd6c1afdc310335a61c9`](https://etherscan.io/address/0x26743984e3357efc59f2fd6c1afdc310335a61c9#code) \::: ### Query Pending Tokens for a veBAL Holder You can query pending tokens for a given veBAL holder by using `eth_call` to simulate a claim transaction. Below is some simple pseudocode that outlines the process: ``` feeDistributorAddress="0x26743984e3357efc59f2fd6c1afdc310335a61c9"; feeDistributorAbi=; userAddress=; tokenAddress0=; tokenAddress1=; tokens = [tokenAddress0, tokenAddress1]; feeDistributorContract = contract(feeDistributorAddress, feeDistributorAbi); claimableTokens = feeDistributorContract.claimTokens(userAddress,tokens).call(); ``` ### Claim Pending Tokens for a veBAL Holder The process is identical to [querying as above](https://docs-v2.balancer.fi/reference/vebal-and-gauges/vebal.html#query-pending-tokens-for-a-vebal-holder), except instead of `eth_call`, you will use `eth_sendTransaction`. \::: warning How do I Know Which Tokens to Query/Claim? At the time of this writing, there is no subgraph tracking tokens added to the `FeeDistributor`. For now, an easy way you can find the available tokens for claiming is checking what the contract holds on Etherscan \::: ## Extend an Existing Pool Type Using Hooks *This section is for developers looking to extend an existing pool type with custom hooks. If you are looking to create a custom AMM with a novel invariant, start [here](/build/build-an-amm/create-custom-amm-with-novel-invariant.html).* Hooks introduce a new framework for extending the functionality of existing pool types at key points throughout their lifecycle. By enabling actions during pool operations and facilitating dynamic swap fee computation, hooks offer unprecedented control over pool behavior. This innovative concept empowers developers to craft tailored pool behaviors, catering to specific use cases and enhancing operations with greater flexibility and control. \::: info Before you start with this walkthrough, consider reading through the [technical section on hooks](/concepts/core-concepts/hooks.md) and take a look at the [Hooks API](/developer-reference/contracts/hooks-api.html). \::: ### Creating a Dynamic Swap Fee Hook Contract A hooks contract should inherit the [BaseHooks.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BaseHooks.sol) abstract contract, which provides a minimal implementation for a hooks contract. At a high level this contract includes: * **Base implementation**: A complete implementation of the [IHooks.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IHooks.sol) interface, with each implemented function returning false. * **Configuration**: A virtual function `getHookFlags` that must be implemented by your hooks contract, defining which hooks your contract supports. Below, we present a naive implementation of a swap-fee discount hook contract giving any veBAL holder a reduced swap fee. Hooks should also inherit from `VaultGuard`, which stores a reference to the Vault and provides the `onlyVault` modifier. **This is required for `onRegister` and hook overrides whenever they are capable of modifying the contract's state**, to ensure they cannot be called except by the Vault in the standard lifecycle of each particular operation. For hooks that are either `view` or `pure`, the modifier is not strictly necessary. ```solidity contract VeBALFeeDiscountHook is BaseHooks, VaultGuard { // Only pools from a specific factory are able to register and use this hook. address private immutable _allowedFactory; // Only trusted routers are allowed to call this hook, because the hook relies on the `getSender` implementation // implementation to work properly. address private immutable _trustedRouter; // The gauge token received from staking the 80/20 BAL/WETH pool token. IERC20 private immutable _veBAL; /** * @notice A new `VeBALFeeDiscountHookExample` contract has been registered successfully. * @dev If the registration fails the call will revert, so there will be no event. * @param hooksContract This contract * @param factory The factory (must be the allowed factory, or the call will revert) * @param pool The pool on which the hook was registered */ event VeBALFeeDiscountHookExampleRegistered( address indexed hooksContract, address indexed factory, address indexed pool ); constructor(IVault vault, address allowedFactory, address veBAL, address trustedRouter) VaultGuard(vault) { _allowedFactory = allowedFactory; _trustedRouter = trustedRouter; _veBAL = IERC20(veBAL); } /// @inheritdoc IHooks function getHookFlags() public pure override returns (IHooks.HookFlags memory hookFlags) { hookFlags.shouldCallComputeDynamicSwapFee = true; } /// @inheritdoc IHooks function onRegister( address factory, address pool, TokenConfig[] memory, LiquidityManagement calldata ) public override onlyVault returns (bool) { // This hook implements a restrictive approach, where we check if the factory is an allowed factory and if // the pool was created by the allowed factory. Since we only use onComputeDynamicSwapFeePercentage, this // might be an overkill in real applications because the pool math doesn't play a role in the discount // calculation. emit VeBALFeeDiscountHookExampleRegistered(address(this), factory, pool); return factory == _allowedFactory && IBasePoolFactory(factory).isPoolFromFactory(pool); } /// @inheritdoc IHooks function onComputeDynamicSwapFeePercentage( PoolSwapParams calldata params, address pool, uint256 staticSwapFeePercentage ) public view override onlyVault returns (bool, uint256) { // If the router is not trusted, does not apply the veBAL discount because getSender() may be manipulated by a // malicious router. if (params.router != _trustedRouter) { return (true, staticSwapFeePercentage); } address user = IRouterCommon(params.router).getSender(); // If user has veBAL, apply a 50% discount to the current fee (divides fees by 2) if (_veBAL.balanceOf(user) > 0) { return (true, staticSwapFeePercentage / 2); } return (true, staticSwapFeePercentage); } } ``` #### Setting Hook Configuration ```solidity function getHookFlags() external pure override returns (IHooks.HookFlags memory hookFlags) { // all flags default to false hookFlags.shouldCallComputeDynamicSwapFee = true; } ``` The `getHookFlags` function returns a `HookFlags` struct, which indicates which hooks are implemented by the contract. When a pool is registered, the Vault calls this function to store the configuration. In this example, the `shouldCallComputeDynamicSwapFee` flag is set to true, indicating that the contract is configured to calculate the dynamic swap fee. #### Hook Registration ```solidity function onRegister( address factory, address pool, TokenConfig[] memory, LiquidityManagement calldata ) external view override returns (bool) { emit VeBALFeeDiscountHookExampleRegistered(address(this), factory, pool); return factory == _allowedFactory && IBasePoolFactory(factory).isPoolFromFactory(pool); } ``` The `onRegister` function enables developers to implement custom validation logic to ensure the registration is valid. When a new pool is registered, a hook address can be provided to "link" the pool and the hook. At this stage, the `onRegister` function is invoked by the Vault, and it must return true for the registration to be successful. If the validation fails, the function should return false, preventing the registration from being completed. In this example we validate that the `factory` param forwarded from the Vault matches the `allowedFactory` set during the hook deployment, and that the pool was deployed by that factory. If successful, it emits an event for tracking by off-chain processes. #### Implementing the Swap Fee Logic ```solidity function onComputeDynamicSwapFeePercentage( PoolSwapParams calldata params, address pool, uint256 staticSwapFeePercentage ) external view override returns (bool, uint256) { // If the router is not trusted, does not apply the veBAL discount because getSender() may be manipulated by a // malicious router. if (params.router != _trustedRouter) { return (true, staticSwapFeePercentage); } address user = IRouterCommon(params.router).getSender(); // If user has veBAL, apply a 50% discount to the current fee (divides fees by 2) if (_veBAL.balanceOf(user) > 0) { return (true, staticSwapFeePercentage / 2); } return (true, staticSwapFeePercentage); } ``` Now we can implement the logic in the `onComputeDynamicSwapFeePercentage` function, which the Vault calls to retrieve the swap fee value. In our example, any veBal holder enjoys a 50% swap fee discount, instead of the default static swap fee. However, there are some nuances to consider in this implementation. To obtain the user's veBAL balance, we need the sender's address, which we can retrieve by calling `getSender()` on the router. This relies on the router returning the correct address, so it's crucial to ensure the router is "trusted" (any contract can act as a [Router](/concepts/router/overview.html)). In our example we passed a trusted `_router` address, which is saved during the hook deployment. :::info For further reading, the following articles provide more helpful context when it comes to building a hook & understanding the impact hooks can have on pool performance. * [YouTube video](https://www.youtube.com/watch?v=kaz6duliRPA\&t=10s) * [Stable Surge Hook theory](https://medium.com/balancer-protocol/balancers-stablesurge-hook-09d2eb20f219) * [Stable Surge approaches](https://pitchandrolls.com/2024/09/10/unlocking-the-power-of-balancer-v3-hook-development-made-simple/) ::: ## Interacting With The Vault Using Hooks The [Balancer Router](../../concepts/router/overview.md#balancer-routers) is typically the interface Externally Owned Accounts (EOAs) use to interact with the V3 Vault. While the Router uses Permit2 for token permissions, Hooks—being separate smart contracts—cannot sign these permissions. Instead, Hooks interact directly with the Vault. This section covers some common scenarios and usage patterns for Hooks. ### Making A Swap It is possible for a Hook to make a [swap](/developer-reference/contracts/vault-api.html#swaps) by following the following steps: 1. Send the tokens you are swapping to the Vault: ```solidity token.transfer(_vault, amount) ``` 2. Inform the Vault you have sent it tokens. This will update the Vaults transient accounting with the correct balances: ```solidity _vault.settle(token, amount) ``` 3. Perform the swap: ```solidity (amountCalculated, amountIn, amountOut) = _vault.swap( VaultSwapParams({ kind: kind, pool: pool, tokenIn: tokenIn, tokenOut: tokenOut, amountGivenRaw: amount, limitRaw: limit, userData: userData }) ); ``` ### AddingLiquidity - Donating To Pool LPs Hooks can [add](/developer-reference/contracts/vault-api.html#add-liquidity)/[remove](/developer-reference/contracts/vault-api.html#remove-liquidity) liquidity to pools. The following snippet shows how the [`DONATION`](/concepts/vault/add-remove-liquidity-types.html#add-liquidity) kind can be used to add collected fees to a pool. This effectively donates the fees to the pool LPs and can be seen in action in the [ExitFeeHookExample](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/contracts/ExitFeeHookExample.sol#L160-L169). ```solidity _vault.addLiquidity( AddLiquidityParams({ pool: pool, to: msg.sender, // It would mint BPTs to router, but it's a donation so no BPT is minted maxAmountsIn: accruedFees, // Donate all accrued fees back to the pool (i.e. to the LPs) minBptAmountOut: 0, // Donation does not return BPTs, any number above 0 will revert kind: AddLiquidityKind.DONATION, userData: bytes("") // User data is not used by donation, so we can set it to an empty string }) ); ``` ### Collecting Fees The Vault [`sendTo`](/developer-reference/contracts/vault-api.html#sendto) function can be used to collect fees to a hook. ```solidity _vault.sendTo(feeToken, address(this), hookFee); ``` where `address(this)` would be the hook itself. This is used in the [FeeTakingHookExample](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/contracts/FeeTakingHookExample.sol) and the [LotteryHookExample](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/contracts/LotteryHookExample.sol) to collect a fee after swapping. ## Create a custom Router A custom Router is a smart contract which interacts with the Balancer Vault and utilizes the Vaults function in unique combinations. The deployment of a custom Router is beneficial for various projects in the DeFi space. To name some verticals this could be: * DEX aggregators, which want to tweak the default interaction via the Balancer Router in a certain way to deliver best prices to their users. * DeFi projects wanting to provide seamless liquidity migrations of their users from various Dexes to Balancer in order to participate from the deep liquidity offered on Balancer * DeFi projects looking to provide liquidity on Balancer in custom proportions across multiple pools with more granular control metrics as part of the liquidity provisioning * DeFi projects looking to enhance the liquidity mining experience for LPs by introducing a better staking and migration flow. The main work custom routers in the outlined examples above have in common is that: * They utilize multiple Vault interactions based on the required use-case * They add additional control flows and external interactions to the Router smart contract for granular liquidity operations ### Usage The following custom Router displays how LPs can migrate pool liquidity to a new pool with the same tokens and stake it in a gauge without the need to transfer tokens except the BPT. ```solidity // SPDX-License-Identifier: GPL-3.0-or-later pragma solidity ^0.8.4; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IVault } from "@balancer-labs/v3-interfaces/contracts/vault/IVault.sol"; import { RemoveLiquidityParams, RemoveLiquidityKind, AddLiquidityKind, SwapKind, AddLiquidityParams } from "@balancer-labs/v3-interfaces/contracts/vault/VaultTypes.sol"; interface IMockLiquidityGauge { function deposit(uint256 value, address forWhom, bool willClaim) external; } /** * @title MigrationRouter * @notice Router for migrating liquidity from one pool to another and staking the new BPT * @dev This contract utilizes proportional remove liquidity and unbalanced add liquidity to ensure * accrued credit on withdrawal is perfectly canceled out via debt accrued on the add liquidity operation, ensuring no * ERC20 Tokens (Except the BPT) need to be transferred. */ contract MigrationRouter { IVault private immutable _vault; constructor(address vault) { _vault = IVault(vault); } /** * @param poolToExit the pool the LP removes liquidity from * @param exactAmountsInOfExit exact amount of BPT to burn for share of the pool's tokens. * @param minAmountsOutOfExit minimum amount of tokens to receive * @param minBptAmountOutOfJoin minimum amount of BPT to receive for the pool to be joined * @param poolToJoin address of the pool to join * @param gaugeToStakeIn address of the gauge to stake in * @param sender address of the user */ function migrate8020PoolAndStake( address poolToExit, uint256 exactAmountsInOfExit, //BptIn uint256[] calldata minAmountsOutOfExit, //tokensOut uint256 minBptAmountOutOfJoin, address poolToJoin, address gaugeToStakeIn, address sender ) external { _vault.unlock( abi.encodeWithSignature( "migrate8020PoolAndStakeHook(address,uint256,uint256[],uint256,address,address,address)", poolToExit, exactAmountsInOfExit, minAmountsOutOfExit, minBptAmountOutOfJoin, poolToJoin, gaugeToStakeIn, sender ) ); } /** * @param poolToExit the pool the LP removes liquidity from * @param exactAmountsInOfExit exact amount of BPT to burn for share of the pool's tokens. * @param minAmountsOutOfExit minimum amount of tokens to receive * @param minBptAmountOutOfJoin minimum amount of BPT to receive for the pool to be joined * @param poolToJoin address of the pool to join * @param gaugeToStakeIn address of the gauge to stake in * @param sender address of the user */ function migrate8020PoolAndStakeHook( address poolToExit, uint256 exactAmountsInOfExit, uint256[] calldata minAmountsOutOfExit, uint256 minBptAmountOutOfJoin, address poolToJoin, address gaugeToStakeIn, address sender ) external { // user already has BPT in possession as it was unstaked previously // removeLiquidity RemoveLiquidityParams memory params = RemoveLiquidityParams({ pool: poolToExit, from: sender, maxBptAmountIn: exactAmountsInOfExit, minAmountsOut: minAmountsOutOfExit, kind: RemoveLiquidityKind.PROPORTIONAL, userData: "0x" }); (, uint256[] memory amountsOut, ) = _vault.removeLiquidity(params); // addLiquidity AddLiquidityParams memory addLiquidityParams = AddLiquidityParams({ pool: poolToJoin, to: address(this), maxAmountsIn: amountsOut, minBptAmountOut: minBptAmountOutOfJoin, kind: AddLiquidityKind.UNBALANCED, userData: "0x" }); (, uint256 bptAmountOut, ) = _vault.addLiquidity(addLiquidityParams); // bpt has been minted to Router and Router stakes for the `sender` IMockLiquidityGauge(gaugeToStakeIn).deposit(bptAmountOut, sender, false); } } ``` ## Balancer Routers One of the major architectural changes from V2 to V3 is the introduction of Routers. In V2, the Vault was the "user interface" -- all swaps and liquidity operations were contract calls on the Vault itself. This design proved limiting when users wanted to, for instance, combine swaps and liquidity operations in a single call. Since the Vault is immutable, adding "new" operations required the use of relayers, or extensions to the already-complex pool designs. Furthermore, the Vault only exposed a few simple primitives (e.g., join, exit, batchSwap), requiring users to "encode" the details as non-human-readable userData. In V3, these concerns are entirely separated. Users never call the Vault directly; instead, the user interface is implemented using Router contracts. There are several standard routers for different purposes, each of which exposes a clear and transparent set of functions. No computation is required; all can be used easily, directly from Etherscan. *** ### Router Architecture Overview #### The Hooks Pattern All V3 routers follow a "hooks" architecture. Each router implements hooks that are called by the Vault during operations. This pattern allows: 1. **Separation of concerns**: The Vault handles core protocol logic, while routers handle user interaction and token transfers 2. **Extensibility**: New routers can be created without modifying the Vault 3. **Composability**: Operations can be chained together within a single transaction 4. **Gas efficiency**: The Vault can batch operations and settle only net token transfers When a user calls a router function (e.g., `addLiquidityUnbalanced`), the router: 1. Validates parameters and handles ETH wrapping if needed 2. Calls the Vault with operation parameters 3. The Vault calls back into the router's hook function 4. The router hook transfers tokens from the user to the Vault 5. The Vault validates (settles) the operation and returns results #### Query Functions All routers provide query functions (prefixed with `query`) that simulate operations without executing them. These are useful for: * Estimating amounts before executing transactions * Building UIs that show expected results * Testing operation parameters Query functions use the same underlying logic as their execution counterparts, but don't transfer tokens or modify state. They can only be called in a static context. *** ### Standard Routers #### Router ([IRouter](https://github.com/balancer/balancer-v3-monorepo/blob/cdb5d86cf458362538ada1ba24ff33506c74ed94/pkg/interfaces/contracts/vault/IRouter.sol)) #### BatchRouter ([IBatchRouter](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IBatchRouter.sol)) #### CompositeLiquidityRouter ([ICompositeLiquidityRouter](https://github.com/balancer/balancer-v3-monorepo/blob/cdb5d86cf458362538ada1ba24ff33506c74ed94/pkg/interfaces/contracts/vault/ICompositeLiquidityRouter.sol)) #### BufferRouter ([IBufferRouter](https://github.com/balancer/balancer-v3-monorepo/blob/cdb5d86cf458362538ada1ba24ff33506c74ed94/pkg/interfaces/contracts/vault/IBufferRouter.sol)) #### UnbalancedAddViaSwapRouter ([IUnbalancedAddViaSwapRouter](https://github.com/balancer/balancer-v3-monorepo/blob/cdb5d86cf458362538ada1ba24ff33506c74ed94/pkg/interfaces/contracts/vault/IUnbalancedAddViaSwapRouter.sol)) *** ### Inheritance Diagram ![Balancer Routers](/images/router-diagram.png) *** ### Common Patterns #### ETH Handling Most routers support native ETH through the `wethIsEth` parameter: * When `true`: Automatically wraps incoming ETH to WETH and unwraps outgoing WETH to ETH * When `false`: Treats WETH as a regular ERC20 token **Example:** ```solidity // Add liquidity with ETH router.addLiquidityUnbalanced{value: 1 ether}( pool, exactAmountsIn, minBptAmountOut, true, // wethIsEth = true, so ETH is automatically wrapped userData ); ``` #### Slippage Protection Most operations have min/max amount parameters for slippage protection: * **Adding liquidity**: `minBptAmountOut` ensures you receive enough BPT * **Removing liquidity**: `minAmountsOut` ensures you receive enough tokens * **Swaps**: `minAmountOut` (exact in) or `maxAmountIn` (exact out) protects against unfavorable prices #### Deadlines Swap operations include a `deadline` parameter to prevent transactions from executing at stale prices if they sit in the mempool too long. #### User Data The `userData` parameter allows passing additional data to pools or hooks. Use cases: * Custom pool logic requiring extra parameters * Hook-specific configuration * Protocol-specific metadata Most operations can use empty bytes (`""`) for userData if not needed. #### Token Ordering Token arrays must always be in **token registration order** (the order the pool registered them with the Vault). This is typically ascending by token address. **Exception:** Nested pool operations allow arbitrary token ordering since the order is ambiguous across multiple pools. *** ### Router Selection Guide \| Use Case | Router | Key Functions | \|----------|--------|---------------| \| Basic swaps | `Router` | `swapSingleTokenExactIn`, `swapSingleTokenExactOut` | \| Add/remove liquidity (standard pools) | `Router` | `addLiquidityUnbalanced`, `removeLiquidityProportional` | \| Initialize new pool | `Router` | `initialize` | \| Multi-hop swaps | `BatchRouter` | `swapExactIn`, `swapExactOut` | \| ERC4626 pool operations | `CompositeLiquidityRouter` | `addLiquidityUnbalancedToERC4626Pool` | \| Nested pool operations | `CompositeLiquidityRouter` (V3+) | `addLiquidityUnbalancedNestedPool` | \| Buffer management | `BufferRouter` | `initializeBuffer`, `addLiquidityToBuffer` | \| Two-token unbalanced add | `UnbalancedAddViaSwapRouter` | `addLiquidityUnbalanced` | *** ### Creating Custom Routers The router architecture is designed to be extensible. To create a custom router: 1. **Inherit from RouterCommon**: Provides base functionality and utilities 2. **Implement RouterHooks**: Handle token transfers and Vault callbacks 3. **Add your custom functions**: Expose user-friendly interfaces for your specific use case 4. **Follow the hooks pattern**: Your functions should call the Vault, which calls back your hooks 5. **Handle token transfers and ETH wrapping**: Use `_takeTokenIn` and `_sendTokenOut` utilities, and `wethIsEth` 6. **Implement query equivalents**: Allow users to simulate operations #### Example Custom Router Structure ```solidity import { RouterCommon } from "./RouterCommon.sol"; import { RouterHooks } from "./RouterHooks.sol"; contract MyCustomRouter is RouterHooks { constructor( IVault vault, IWETH weth, IPermit2 permit2 ) RouterHooks(vault, weth, permit2, "MyRouter v1.0") {} function myCustomOperation( address pool, // ... parameters ) external payable returns (/* ... return values */) { // 1. Validate parameters // 2. Call Vault (which calls back into hooks) // 3. Return results } function queryMyCustomOperation( address pool, address sender, // ... parameters ) external returns (/* ... return values */) { // Query version of operation } } ``` See the [Create a Custom Router](./create-custom-router.md) guide for detailed instructions. *** ### Security Considerations #### Router Approvals Routers require token approvals to transfer tokens on your behalf. Best practices: * Only approve the specific router you're using * Routers configured for retail use Permit2 to transfer tokens * Routers configured for programmatic use (i.e., "prepaid"), assume transfers are done externally (no transfers needed, so no approvals) * Revoke unused approvals #### Query Safety Query functions are read-only and safe to call, but: * Results may change between query and execution due to other transactions * Always use appropriate slippage protection for state-changing calls * Be aware of front-running risks on public mempools #### Hook Validation The Vault validates that hooks are called correctly: * Only the Vault can call hook functions (enforced by the `onlyVault` modifier) * Routers cannot bypass Vault security * Hook execution is atomic with the main operation *** ### Additional Resources * **Balancer V3 Vault Documentation**: Core protocol concepts * **Router Implementation Examples**: See `balancer-v3-monorepo/pkg/vault/contracts/` * **Router Tests**: See `balancer-v3-monorepo/pkg/vault/test/foundry/` for usage examples * **SDK Integration**: The Balancer SDK wraps router calls for easier integration ## Create a custom AMM with a novel invariant Balancer protocol provides developers with a modular architecture that enables the rapid development of custom AMMs. AMMs built on Balancer inherit the security of the Balancer vault, and benefit from a streamlined development process. Balancer v3 was re-built from the ground up with developer experience as a core focus. Development teams can now focus on their product innovation without having to build an entire AMM. *This section is for developers looking to build a new custom pool type with a novel invariant. If you are looking to extend an existing pool type with hooks, start [here](/build/build-a-hook/extend-existing-pool-type.html).* ### Build your custom AMM At a high level, creating a custom AMM on Balancer protocol involves the implementation of five functions `onSwap`, `computeInvariant` and `computeBalance` as well as the `ISwapFeePercentageBounds` and `IUnbalancedLiquidityInvariantRatioBounds` interfaces (which define `getMinimumSwapFeePercentage` / `getMaximumSwapFeePercentage`, and `getMinimumInvariantRatio` / `getMaximumInvariantRatio`, respectively). To expedite the development process, Balancer provides two contracts to inherit from: * [IBasePool.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IBasePool.sol) - This interface defines the required functions that every Balancer pool must implement * [BalancerPoolToken.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BalancerPoolToken.sol) - This contract implements the [ERC20MultiToken](/concepts/vault/erc20-multi-token.html) standard that enables your pool contract to be ERC20 compliant while delegating BPT accounting to the vault. For more information, refer to [BalancerPoolToken](/concepts/core-concepts/balancer-pool-tokens.html). Both `IBasePool` and `BalancerPoolToken` are used across all core Balancer pools, even those implemented by Balancer Labs (ie: [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol)). Standard Balancer pools also implement the optional `Version` interface (for easy on- and off-chain verification of the contract version), and `IPoolInfo`, which exposes Vault getters (e.g., `getTokens`, and `getTokenInfo`) through the pool itself as a convenience. On top of that, the standard pools define custom interfaces that return structs corresponding to the immutable and dynamic data fields for the pools. For instance, Weighted Pools return the weights. Note that dynamic pool values (e.g., live balances), exposed through either `IPoolInfo` or the custom interfaces, will only be valid on-chain if the Vault is locked (i.e., not in the middle of a transaction). Below, we present a naive implementation of a two token `ConstantProductPool` and `ConstantSumPool` utilizing (sqrt(X \* Y) = K) and (X + Y = K) as references for walking through the required functions necessary to implement a custom AMM on Balancer protocol. You can think of the invariant as a measure of the "value" of the pool, which is related to the total liquidity (i.e., the "BPT rate" is `invariant` / `totalSupply`). Two critical properties must hold (for Balancer, and for AMMs in general): 1. The invariant should not change due to a swap. In practice, it can *increase* due to swap fees, which effectively add liquidity after the swap - but it should never decrease. 2. The invariant must be "linear"; i.e., increasing the balances proportionally must increase the invariant in the same proportion: inv(a \* n,b \* n,c \* n) = inv(a, b, c) \* n Property #1 is required to prevent "round trip" paths that drain value from the pool (and all LP shareholders). Intuitively, an accurate pricing algorithm ensures the user gets an equal value of token out given token in, so the total value should not change. Property #2 is essential for the "fungibility" of LP shares. If it did not hold, then different users depositing the same total value would get a different number of LP shares. In that case, LP shares would not be interchangeable, as they must be in a fair DEX. \::: code-tabs#shell @tab Constant Product Pool ```solidity contract ConstantProductPool is IBasePool, BalancerPoolToken { using FixedPoint for uint256; uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 0; uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 0.1e18; // 10% constructor( IVault vault, string memory name, string memory symbol) BalancerPoolToken(vault, name, symbol) { // solhint-disable-previous-line no-empty-blocks } /** * @notice Execute a swap in the pool. * @param params Swap parameters * @return amountCalculatedScaled18 Calculated amount for the swap */ function onSwap(PoolSwapParams calldata params) external pure returns (uint256 amountCalculatedScaled18) { uint256 poolBalancetokenIn = params.balancesScaled18[params.indexIn]; // X uint256 poolBalancetokenOut = params.balancesScaled18[params.indexOut]; // Y if (params.kind == SwapKind.EXACT_IN) { uint256 amountTokenIn = params.amountGivenScaled18; // dx // dy = (Y * dx) / (X + dx) amountCalculatedScaled18 = (poolBalancetokenOut * amountTokenIn) / (poolBalancetokenIn + amountTokenIn); } else { uint256 amountTokenOut = params.amountGivenScaled18; // dy // dx = (X * dy) / (Y - dy) amountCalculatedScaled18 = (poolBalancetokenIn * amountTokenOut) / (poolBalancetokenOut - amountTokenOut); } } /** * @notice Computes and returns the pool's invariant. * @dev This function computes the invariant based on current balances. * @param balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates * @param rounding Rounding direction to consider when computing the invariant * @return invariant The calculated invariant of the pool, represented as a uint256 */ function computeInvariant( uint256[] memory balancesLiveScaled18, Rounding rounding ) public view returns (uint256 invariant) { // expected to work with 2 tokens only. invariant = FixedPoint.ONE; for (uint256 i = 0; i < balancesLiveScaled18.length; ++i) { invariant = rounding == Rounding.ROUND_DOWN ? invariant.mulDown(balancesLiveScaled18[i]) : invariant.mulUp(balancesLiveScaled18[i]); } // scale the invariant to 1e18 invariant = Math.sqrt(invariant) * 1e9; } /** * @notice Computes the new balance of a token after an operation. * @dev This takes into account the invariant growth ratio and all other balances. * @param balancesLiveScaled18 Current live balances (adjusted for decimals, rates, etc.) * @param tokenInIndex The index of the token we're computing the balance for, in token registration order * @param invariantRatio The ratio of the new invariant (after an operation) to the old * @return newBalance The new balance of the selected token, after the operation */ function computeBalance( uint256[] memory balancesLiveScaled18, uint256 tokenInIndex, uint256 invariantRatio ) external pure returns (uint256 newBalance) { uint256 otherTokenIndex = tokenInIndex == 0 ? 1 : 0; uint256 newInvariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN).mulDown(invariantRatio); newBalance = (newInvariant * newInvariant / balancesLiveScaled18[otherTokenIndex]); } /// @inheritdoc ISwapFeePercentageBounds function getMinimumSwapFeePercentage() external pure returns (uint256) { return _MIN_SWAP_FEE_PERCENTAGE; } /// @inheritdoc ISwapFeePercentageBounds function getMaximumSwapFeePercentage() external pure returns (uint256) { return _MAX_SWAP_FEE_PERCENTAGE; } /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds function getMinimumInvariantRatio() external pure returns (uint256) { return WeightedMath._MIN_INVARIANT_RATIO; } /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds function getMaximumInvariantRatio() external pure returns (uint256) { return WeightedMath._MAX_INVARIANT_RATIO; } } ``` @tab Constant Sum Pool ```solidity contract ConstantSumPool is IBasePool, BalancerPoolToken { uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 0; uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 0.1e18; // 10% constructor( IVault vault, string memory name, string memory symbol) BalancerPoolToken(vault, name, symbol) {} /** * @notice Execute a swap in the pool. * @param params Swap parameters * @return amountCalculatedScaled18 Calculated amount for the swap */ function onSwap(PoolSwapParams calldata params) external pure returns (uint256 amountCalculatedScaled18) { amountCalculatedScaled18 = params.amountGivenScaled18; } /** * @notice Computes and returns the pool's invariant. * @dev This function computes the invariant based on current balances. There is no precision loss for addition, * so we can ignore the rounding direction. * * @param balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates * @param rounding Rounding direction to consider when computing the invariant * @return invariant The calculated invariant of the pool, represented as a uint256 */ function computeInvariant( uint256[] memory balancesLiveScaled18, Rounding ) public pure returns (uint256 invariant) { invariant = balancesLiveScaled18[0] + balancesLiveScaled18[1]; } /** * @dev Computes the new balance of a token after an operation, given the invariant growth ratio and all other * balances. * @param balancesLiveScaled18 Current live balances (adjusted for decimals, rates, etc.) * @param tokenInIndex The index of the token we're computing the balance for (tokens are sorted alphanumerically) * @param invariantRatio The ratio of the new invariant (after an operation) to the old * @return newBalance The new balance of the selected token, after the operation */ function computeBalance( uint256[] memory balancesLiveScaled18, uint256 tokenInIndex, uint256 invariantRatio ) external pure returns (uint256 newBalance) { uint256 invariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN); newBalance = (balancesLiveScaled18[tokenInIndex] + invariant.mulDown(invariantRatio)) - invariant; } /// @return minimumSwapFeePercentage The minimum swap fee percentage for a pool function getMinimumSwapFeePercentage() external pure returns (uint256) { return _MIN_SWAP_FEE_PERCENTAGE; } /// @return maximumSwapFeePercentage The maximum swap fee percentage for a pool function getMaximumSwapFeePercentage() external pure returns (uint256) { return _MAX_SWAP_FEE_PERCENTAGE; } /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds function getMinimumInvariantRatio() external pure returns (uint256) { return WeightedMath._MIN_INVARIANT_RATIO; } /// @inheritdoc IUnbalancedLiquidityInvariantRatioBounds function getMaximumInvariantRatio() external pure returns (uint256) { return WeightedMath._MAX_INVARIANT_RATIO; } } ``` \::: \::: info What does Scaled18 mean? Internally, Balancer protocol scales all tokens to 18 decimals to minimize the potential for errors that can occur when comparing tokens with different decimals numbers (ie: WETH/USDC). `Scaled18` is a suffix used to signify values has already been scaled. **By default, ALL values provided to the pool will always be `Scaled18`.** Refer to [Decimal scaling](/concepts/vault/token-scaling.html#pool-registration) for more information. \::: \::: info What does Live refer to in balancesLiveScaled18? The keyword `Live` denotes balances that have been scaled by their respective `IRateProvider` and have any pending yield fees removed. Refer to [Live Balances](/concepts/vault/token-scaling.html#live-balances) for more information. \::: \::: info How are add and remove liquidity operations implemented? Balancer protocol leverages a novel approximation, termed the [Liquidity invariant approximation](/concepts/vault/liquidity-invariant-approximation.html), to provide a generalized solution for liquidity operations. By implementing `computeInvariant` and `computeBalance`, your custom AMM will immediately support all Balancer liquidity operations: `unbalanced`, `proportional` and `singleAsset`. \::: #### Compute Invariant Custom AMMs built on Balancer protocol are defined primarily by their invariant. Broadly speaking, an invariant is a mathematical function that defines how the AMM exchanges one asset for another. A few widely known invariants include [Constant Product (X \* Y = K)](https://docs.uniswap.org/contracts/v2/concepts/protocol-overview/how-uniswap-works) and [StableSwap](https://berkeley-defi.github.io/assets/material/StableSwap.pdf). Our two-token `ConstantSumPool` uses the constant sum invariant, or `X + Y = K`. To implement `computeInvariant`, we simply add the balances of the two tokens. For the `ConstantProductPool`, the invariant calculation is the square root of the product of balances. This ensures invariant growth proportional to liquidity growth. \::: code-tabs#shell @tab Constant Product Pool ```solidity function computeInvariant( uint256[] memory balancesLiveScaled18, Rounding rounding ) public view returns (uint256 invariant) { // expected to work with 2 tokens only. invariant = FixedPoint.ONE; for (uint256 i = 0; i < balancesLiveScaled18.length; ++i) { invariant = rounding == Rounding.ROUND_DOWN ? invariant.mulDown(balancesLiveScaled18[i]) : invariant.mulUp(balancesLiveScaled18[i]); } // scale the invariant to 1e18 invariant = Math.sqrt(invariant) * 1e9; } ``` @tab Constant Sum Pool ```solidity function computeInvariant( uint256[] memory balancesLiveScaled18, Rounding ) public pure returns (uint256 invariant) { invariant = balancesLiveScaled18[0] + balancesLiveScaled18[1]; } ``` \::: For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol) and [Stable Pool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol) implementations. \::: info application context on computeBalance In the context of `computeBalance` the invariant is used as a measure of liquidity. What you need to consider when implementing all possible liquidity operations on the pool is that: * `bptAmountOut` for an unbalanced add liquidity operation should equal `bptAmountOut` for a proportional add liquidity in the case that `exactAmountsIn` for the unbalanced add are equal to the `amountsIn` for the same `bptAmountOut` for both addLiquidity scenarios. `AddLiquidityProportional` does not call into the custom pool it instead calculates `bptAmountOut` using [BasePoolMath.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/BasePoolMath.sol#L50-L54) whereas `addLiquidityUnbalanced` calls the custom pool's `computeInvariant`. * The `amountIn` for an `exactBptAmountOut` in an `addLiquiditySingleTokenExactOut` should equal the `amountIn` for an unbalanced addLiquidity when the `bptAmountOut` is expected to be the same for both operations. `addLiquiditySingleTokenExactOut` uses `computeBalance` whereas `addLiquidityUnbalanced` uses `computeInvariant`. These are important consideration to ensure that LPs get the same share of the pool's liquidity when adding liquidity. In a Uniswap V2 Pair adding liquidity not in proportional amounts gets [penalized](https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol#L123), which you can also implement in a custom pool, as long as you accurately handle the bullet points outlined above. \::: #### Compute Balance `computeBalance` returns the new balance of a pool token necessary to achieve an invariant change. It is essentially the inverse of the pool's invariant. The `invariantRatio` is the ratio of the new invariant (after an operation) to the old. `computeBalance` is used for liquidity operations where the token amount in/out is unknown, specifically [`AddLiquidityKind.SINGLE_TOKEN_EXACT_OUT`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L645-L660) and [`RemoveLiquidityKind.SINGLE_TOKEN_EXACT_IN`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L871-L885). You can see the implementations of the `ConstantProductPool` and `ConstantSumPool` below: \::: code-tabs#shell @tab Constant Product Pool ```solidity function computeBalance( uint256[] memory balancesLiveScaled18, uint256 tokenInIndex, uint256 invariantRatio ) external pure returns (uint256 newBalance) { uint256 otherTokenIndex = tokenInIndex == 0 ? 1 : 0; uint256 newInvariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN).mulDown(invariantRatio); newBalance = (newInvariant * newInvariant / balancesLiveScaled18[otherTokenIndex]); } ``` @tab Constant Sum Pool ```solidity function computeBalance( uint256[] memory balancesLiveScaled18, uint256 tokenInIndex, uint256 invariantRatio ) external pure returns (uint256 newBalance) { uint256 invariant = computeInvariant(balancesLiveScaled18, Rounding.ROUND_DOWN); newBalance = (balancesLiveScaled18[tokenInIndex] + invariant.mulDown(invariantRatio)) - invariant; } ``` \::: \::: info A note on invariantRatio The `invariantRatio` refers to the new BPT supply over the total BPT supply and is calculated within the `BasePoolMath.sol` via `newSupply.divUp(totalSupply)`. \::: For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L113-L124) and [StablePool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol#L157-L171) implementations. #### On Swap Although the outcome of `onSwap` could be determined using `computeInvariant` and `computeBalance`, it is highly likely that there is a more gas-efficient strategy. `onSwap` is provided as a means to facilitate lower cost swaps. Balancer protocol supports two types of swaps: * `EXACT_IN` - The user defines the exact amount of `tokenIn` they want to spend. * `EXACT_OUT` - The user defines the exact amount of `tokenOut` they want to receive. The `minAmountOut` or `maxAmountIn` are enforced by the [vault](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/Vault.sol#L387-L429) . When swapping tokens, our constant `K` must remain unchanged. Since our two-token `ConstantSumPool` uses the constant sum invariant (`X + Y = K`), the amount entering the pool will always equal the amount leaving the pool: \::: code-tabs#shell @tab Constant Product Pool ```solidity function onSwap(PoolSwapParams calldata params) external pure returns (uint256 amountCalculatedScaled18) { amountCalculatedScaled18 = params.balancesScaled18[params.indexOut] * params.amountGivenScaled18 / (params.balancesScaled18[params.indexIn] + params.amountGivenScaled18 ); } ``` @tab Constant Sum Pool ```solidity function onSwap(PoolSwapParams calldata params) external pure returns (uint256 amountCalculatedScaled18) { amountCalculatedScaled18 = params.amountGivenScaled18; } ``` \::: The `PoolSwapParams` struct definition can be found [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L238-L246). For additional references, refer to the [WeightedPool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol) and [StablePool](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-stable/contracts/StablePool.sol) implementations. #### Constructor arguments At a minimum, your constructor should have the required arguments to instantiate the `BalancerPoolToken`: * `IVault vault`: The address of the Balancer vault * `string name`: ERC20 compliant `name` that will identify the pool token (BPT). * `string symbol`: ERC20 compliant `symbol` that will identify the pool token (BPT). ```solidity constructor(IVault vault, string name, string symbol) BalancerPoolToken(vault, name, symbol) {} ``` The approach taken by Balancer Labs is to define a [NewPoolParams](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/WeightedPool.sol#L59-L65) struct to better organize the constructor arguments. ### Swap fees The charging of swap fees is managed entirely by the Balancer vault. The pool is only responsible for declaring the `swapFeePercentage` for any given swap or unbalanced liquidity operation on registration as well as declaring an minimum and maximum swap fee percentage. For more information, see [Swap fees](https://docs-v3.balancer.fi/concepts/vault/swap-fee.html). \::: info Do I need to take swap fees into account when implementing onSwap? No, swap fees are managed entirely by the Balancer vault. For an `EXACT_OUT` swap, the amount in (`params.amountGivenScaled18`) will already have the swap fee removed before `onSwap` is called. Fees are always taken on `tokenIn`. \::: Balancer supports two types of swap fees: * **Static swap fee**: Defined on `vault.registerPool()` and managed via calls to `vault.setStaticSwapFeePercentage()`. For more information, see [Swap fee](/concepts/vault/swap-fee.html). * **Dynamic swap fee**: Managed by a **Hooks** contract. Whether a swap with a pool uses the dynamic swap fee is determined at pool registration. A Hook sets the flag indicating support for dynamic fees on `vault.registerPool()`. For more information, see [Dynamic swap fees](/concepts/vault/swap-fee.html#dynamic-swap-fee). ### Hooks Hooks as standalone contracts are not part of a custom pool's implementation. However they can be combined with custom pools. For a detailed understanding, see [Hooks](/concepts/core-concepts/hooks.html). #### Vault reentrancy Hooks allow a pool to reenter the vault within the context of a pool operation. While `onSwap`, `computeInvariant` and `computeBalance` must be executed within a reentrancy guard, the vault is architected such that hooks operate outside of this requirement. ### Add / Remove liquidity The implementation of `computeInvariant` and `computeBalance` allows a pool to support ALL [Add/Remove liquidity types](/concepts/vault/add-remove-liquidity-types.html). For instances where your custom AMM has additional requirements for add/remove liquidity operations, Balancer provides support for `AddLiquidityKind.CUSTOM` and `RemoveLiquidityKind.CUSTOM`. An example custom liquidity operation can be found in [Cron Finance's](https://docs.cronfi.com/twamm/) TWAMM implementation on Balancer v2, specifically when the pool [registers long term orders](https://github.com/Cron-Finance/v1-twamm/blob/main/contracts/twault/CronV1Pool.sol#L438). When adding support for custom liquidity operations, it's recommended that your pool contract implement [IPoolLiquidity](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IPoolLiquidity.sol) ```solidity contract ConstantSumPool is IBasePool, IPoolLiquidity, BalancerPoolToken { ... } ``` #### Add liquidity custom For your AMM to support add liquidity custom, it must: * Implement `onAddLiquidityCustom`, as defined [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IPoolLiquidity.sol#L19-L32) * Set `LiquidityManagement.supportsAddLiquidityCustom` to `true` on pool register. #### Remove liquidity custom For your AMM to support remove liquidity custom, it must: * Implement `onRemoveLiquidityCustom`, as defined [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IPoolLiquidity.sol#L46-L59) * Set `LiquidityManagement.supportsRemoveLiquidityCustom` to `true` on pool register. #### Remove support for built in liquidity operations There may be instances where your AMM should not support specific built-in liquidity operations. If certain operations should be enabled in your custom pool is defined in `LiquidityManagement`. You can choose to: * disable add and remove liquidity unbalanced (i.e., non-proportional; enabled by default. These cannot be disabled independently.) * enable add liquidity custom (disabled by default) * enable remove liquidity custom (disabled by default) * enable donation (disabled by default) To achieve this, the respective entry in the `LiquidityManagement` struct needs to be set. ```solidity struct LiquidityManagement { bool disableUnbalancedLiquidity; bool enableAddLiquidityCustom; bool enableRemoveLiquidityCustom; bool enableDonation; } ``` These settings get passed into the [pool registration](/developer-reference/contracts/vault-api.html#registerpool) flow. ### Testing your pool Depending on the combination of liquidity operations you allow for your pool you need to ensure the correct amount of BPT get's minted whenever a user adds/removes liquidity unbalanced (which calls into `computeInvariant`) and proportional adds/removes (which does not call into the pool and solely relies on [BasePoolMath.sol](https://github.com/balancer/balancer-v3-monorepo/blob/4864599800adc88d6a53f0a5b71c8352eac2f3a1/pkg/solidity-utils/contracts/math/BasePoolMath.sol#L7)). Let's say your pool has reserves of \[100, 100] and an `addLiquidityProportional([50,50])` gets the user 100 BPT in return, if the user were to `addLiquidityUnbalanced([50,50])` you must ensure that the amount of BPT that gets minted is the same as in the `addLiquidityProportional([50,50])` operation. Consider also reading through [liquidity invariant approximation](/concepts/vault/liquidity-invariant-approximation.md) to get more context on various combination of pool operations. ### Deploying your pool See the guide to [Deploy a Custom AMM Using a Factory](/build/build-an-amm/deploy-custom-amm-using-factory.html). ## Deploy a Custom AMM Using a Factory *This section is for developers looking to deploy a custom pool contract that has already been written. If you are looking to design a custom AMM with a novel invariant, start [here](/build/build-an-amm/create-custom-amm-with-novel-invariant.html).* Balancer recommends that custom pools be deployed via a factory contract because our off-chain infrastructure uses the factory address as a means to identify the type of pool, which is important for integration into the UI, SDK, and external aggregators. Factories also provide important security benefits, mainly guaranteeing the integrity of the setup and preventing front-running. If the contract deploys and registers the factory in one operation, the pool is certain to have the desired configuration. Although core Balancer pool factories do not do this, as they are meant to be generic and support many different use cases, including separate funding, consider also initializing through the factory (or at least in the same transaction as the create). Factories could even inspect the on-chain conditions during deployment, and revert if they are unfavorable (e.g., nested pool rates have been manipulated), further reducing the risks of front-running or other types of interference. Regarding initialization, it is also recommended to initialize ERC4626 buffers *before* deploying any pools with corresponding wrapped tokens. Initialization is the step that sets the proportion of underlying and wrapped tokens; thereafter, liquidity can only be added proportionally. To ensure the pools work as intended, buffers should be created and funded by sponsors prior to deploying pools designed to use them. For maximum security, consider using a private node for sensitive / high liquidity operations, to avoid risks associated with the public mempool. To fully set up a new custom pool so that normal liquidity operations and swaps are enabled, five required steps must be taken: 1. Deploy a factory contract that inherits from [BasePoolFactory.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-utils/contracts/BasePoolFactory.sol) 2. Deploy the pool contract using the factory's `_create` function 3. Register the pool using the factory's `_registerPoolWithVault` function 4. Use [Permit2](https://github.com/Uniswap/permit2) to approve the Router to spend the tokens that will be used to initialize the pool 5. Call [`router.initialize()`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IRouter.sol#L46-L53) to seed the pool with initial liquidity \::: tip To see example foundry scripts for deploying a custom pool using a factory, check out [Scaffold Balancer v3](https://github.com/balancer/scaffold-balancer-v3) \::: ### Creating a Custom Pool Factory Contract A factory contract should inherit the [BasePoolFactory.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-utils/contracts/BasePoolFactory.sol) abstract contract, which sets the table for deploying pools with `CREATE3` and streamlines the registration process. Below, we present an example custom pool factory that uses the `ConstantSumPool` contract from [Build your custom AMM](/build/build-an-amm/create-custom-amm-with-novel-invariant.html#build-your-custom-amm) \::: code-tabs#shell @tab ConstantSumPool ```solidity contract ConstantSumFactory is BasePoolFactory { // Each factory can only deploy one type of custom pool constructor( IVault vault, uint32 pauseWindowDuration ) BasePoolFactory(vault, pauseWindowDuration, type(ConstantSumPool).creationCode) {} // Streamline the process of deploying and registering a pool function create( string memory name, string memory symbol, bytes32 salt, TokenConfig[] memory tokens, uint256 swapFeePercentage, bool protocolFeeExempt, PoolRoleAccounts memory roleAccounts, address poolHooksContract, LiquidityManagement memory liquidityManagement ) external returns (address pool) { // Deploy a new pool pool = _create(abi.encode(getVault(), name, symbol), salt); // Register the pool _registerPoolWithVault( pool, tokens, swapFeePercentage, protocolFeeExempt, roleAccounts, poolHooksContract, liquidityManagement ); } } ``` \::: #### Factory Constructor Parameters * `IVault vault`: The address of the Balancer vault * `uint32 pauseWindowDuration`: The period, starting from deployment of a factory, during which pools can be paused and unpaused, see [FactoryWidePauseWindow.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/solidity-utils/contracts/helpers/FactoryWidePauseWindow.sol) * `bytes memory creationCode`: The creation bytecode of the pool contract used by `CREATE3` for deployment \::: code-tabs#shell @tab BasePoolFactory ```solidity constructor( IVault vault, uint32 pauseWindowDuration, bytes memory creationCode ) SingletonAuthentication(vault) FactoryWidePauseWindow(pauseWindowDuration) { _creationCode = creationCode; } ``` \::: #### Pool Deployment Parameters * `bytes memory constructorArgs`: The abi encoded constructor args for the custom pool * `bytes32 salt`: Used to compute a unique, deterministic address for each pool deployment \::: code-tabs#shell @tab BasePoolFactory ```solidity function _create(bytes memory constructorArgs, bytes32 salt) internal returns (address pool) { pool = CREATE3.deploy(_computeFinalSalt(salt), abi.encodePacked(_creationCode, constructorArgs), 0); _registerPoolWithFactory(pool); } ``` \::: #### Pool Registration Parameters * `TokenConfig[] memory tokens`: An array of descriptors for the tokens the pool will manage, see [Token Types](https://docs-v3.balancer.fi/concepts/vault/token-types.html) * `uint256 swapFeePercentage`: Fee charged for each swap. For more information, see [Swap fees](https://docs-v3.balancer.fi/concepts/vault/swap-fee.html) * `bool protocolFeeExempt`: If true, the pool's initial aggregate fees will be set to 0 * `PoolRoleAccounts memory roleAccounts`: Addresses allowed to change certain pool settings, see [Pool Role Permissions](https://docs-v3.balancer.fi/concepts/core-concepts/pool-role-accounts.html) * `address poolHooksContract`: Contract that implements the hooks for the pool. If no hooks, use the zero address * `LiquidityManagement memory liquidityManagement`: Specifies support for [Custom Liquidity Operations](https://docs-v3.balancer.fi/build/build-an-amm/create-custom-amm-with-novel-invariant.html#add-remove-liquidity) \::: code-tabs#shell @tab BasePoolFactory ```solidity function _registerPoolWithVault( address pool, TokenConfig[] memory tokens, uint256 swapFeePercentage, bool protocolFeeExempt, PoolRoleAccounts memory roleAccounts, address poolHooksContract, LiquidityManagement memory liquidityManagement ) internal { getVault().registerPool( pool, tokens, swapFeePercentage, getNewPoolPauseWindowEndTime(), protocolFeeExempt, roleAccounts, poolHooksContract, liquidityManagement ); } ``` \::: \::: info Although deploying pools via a factory contract is the recommended approach, it is not mandatory since it is possible to call [`vault.registerPool`](https://docs-v3.balancer.fi/developer-reference/contracts/vault-api.html#registerpool) directly. \::: ### Initializing a Custom Pool After a custom pool has been deployed and registered, the next step is to add initial liquidity to the pool, which is a three step process: 1. Ensure the [Permit2](https://github.com/Uniswap/permit2) contract has been granted sufficient allowance to spend tokens on behalf of the `msg.sender` 2. Transfer sufficient allowance to the [Router](https://docs-v3.balancer.fi/concepts/router/overview.html) with [`Permit2.approve`](https://github.com/Uniswap/permit2/blob/cc56ad0f3439c502c246fc5cfcc3db92bb8b7219/src/AllowanceTransfer.sol#L25-L30) 3. Call [`router.initialize()`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IRouter.sol#L46-L53) After a pool has been initialized, normal liquidity operations and swaps are instantly enabled. ## Integrating a Custom AMM with Subgraph For those interested in integrating a custom AMM with the Balancer toolkit, including its API and SDK, the first essential step involves integrating your AMM with the Subgraph. \::: tip Subgraph Documentation For a comprehensive guide on working with Subgraphs, also check out [The Graph's documentation](https://thegraph.com/docs/en/). \::: ### Understanding the Balancer Subgraphs Balancer has two types of Subgraphs for each network: * **Vault Subgraph**: Indexes events related to the Balancer Vault, such as swaps, liquidity provision, pool tokens balances, etc. Essential for detailed pool token information. * **Pools Subgraph**: Indexes pool factories and their parameters, playing a critical role in making custom AMMs liquidity visible within the Balancer ecosystem. Balancer chose to split its Subgraph into two to enhance developer experience and speed up the performance. This split makes the developer's job of adding and working with the code much simpler, especially with the Pools Subgraph, which has a clear and easy-to-understand setup. This approach also addresses a well-known challenge with Subgraphs - the long time it often takes to sync them. Since the Pools Subgraph only deals with pool factories and their params, it holds less data than the Vault Subgraph. This means it can sync faster, leading to quicker integrations and a smoother experience for developers. ### Integrating with the Pools Subgraph #### Step 1: Forking and Cloning the Subgraph Repository Begin by forking the [Subgraph repository](https://github.com/balancer/balancer-subgraph-v3) and cloning it to your local development environment. #### Step 2: Installing Dependencies Ensure your environment is prepared with: * **Node.js (Version 18 or higher)**: Required for executing the integration scripts. * **pnpm**: The package manager used for handling project dependencies. #### Step 3: Adding a New Pool Factory via CLI Navigate to the `subgraphs/pools` directory and run the following command: ```bash pnpm run add-pool ``` This command launches a CLI designed to facilitate the addition of a new factory. ##### Network Selection The tool first asks for the network where the new pool factory will be added, ensuring correct indexing within the Subgraph. ##### Factory Address Input You are then prompted to provide the address of your pool factory. For contracts verified on Etherscan, the CLI automatically retrieves the necessary ABIs. ##### Event Selection After ABI retrieval, select the event emitted upon creating a new pool. This step is crucial for indexing your pools. ##### Defining Pool Type Finally, define the name of your pool type. Assuming a new type named "Custom," this differentiates your pools from existing types like "Weighted" and "Stable." #### Completion Following these steps adds your pool factory to the Subgraph Manifest. A new folder (e.g., `mappings/custom/index.ts`) is created for the event handler that indexes new pools from your factory. Integrating your custom AMM with the Subgraph is a foundational step towards making your liquidity pools accessible and visible through the Balancer protocol. *** ## Balancer Subgraph The Balancer Subgraph indexes data on the Balancer smart contracts with a GraphQL interface. It updates data in response to function calls and contract events to maintain data. Balancer uses Subgraph Studio for development and deployment of its subgraphs. For querying non-rate-limited endpoints, users need to obtain an API key from The Graph. More information on querying The Graph can be found [here](https://thegraph.com/docs/en/querying/querying-the-graph/). ### V3 Subgraphs The schemas of GraphQL elements are defined in two separate schema files: * Vault: [`v3-vault/schema.graphql`](https://github.com/balancer/balancer-subgraph-v3/blob/main/subgraphs/v3-vault/schema.graphql) * Pools: [`v3-pools/schema.graphql`](https://github.com/balancer/balancer-subgraph-v3/blob/main/subgraphs/v3-pools/schema.graphql) #### Vault Subgraphs \| Network | URL | \| --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | \| Arbitrum | | \| Avalanche | | \| Base | | \| Ethereum | | \| Gnosis | | \| HyperEVM | | \| Monad | | \| Optimism | | \| Plasma | | \| Sepolia | | #### Pools Subgraphs \| Network | URL | \| --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | \| Arbitrum | | \| Avalanche | | \| Base | | \| Ethereum | | \| Gnosis | | \| HyperEVM | | \| Monad | | \| Optimism | | \| Plasma | | \| Sepolia | | ## Active Permissions This section includes all of the permissions currently setup in the Authorizer for Balancer v3 deployments. The tables here are generated on the latest deployments repo state. * [Mainnet](mainnet.md) * [Gnosis](gnosis.md) * [Base](base.md) * [Avalanche](avalanche.md) * [Optimism](optimism.md) * [HyperEVM](hyperevm.md) * [Plasma](plasma.md) * [Sepolia](sepolia.md) ## Avalanche Authorizer Permissions ## Base Authorizer Permissions ## Gnosis Authorizer Permissions ## HyperEVM Authorizer Permissions ## Mainnet Authorizer Permissions ## Monad Authorizer Permissions ## Optimism Authorizer Permissions ## Plasma Authorizer Permissions ## Sepolia Authorizer Permissions ## XLayer Authorizer Permissions ## Batch Router API The Batch Router can be used to interact with Balancer onchain via state changing operations or used to query operations in an off-chain context. The Batch Router enables multi-hop swaps across multiple pools and tokens. Each path can have multiple steps, and you can execute multiple paths in a single transaction. ### State-changing functions #### Batch swaps ##### `swapExactIn` ```solidity function swapExactIn( SwapPathExactAmountIn[] memory paths, uint256 deadline, bool wethIsEth, bytes calldata userData ) external payable returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut); ``` Executes a swap operation involving multiple paths (steps), specifying exact input token amounts. Each path is independent and can have multiple hops. **Example:** Swap USDC → DAI → WETH in one path, and USDC → USDT → WETH in another path, all in one transaction. **Parameters:** \| Name | Type | Description | \|------------|-------------------------------|----------------------------------------------------------------------------------------------| \| paths | SwapPathExactAmountIn\[] memory | Swap paths from token in to token out, specifying exact amounts in | \| deadline | uint256 | Deadline for the swap, after which it will revert | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes calldata | Additional (optional) data required for the swap | **Returns:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------------------------------| \| pathAmountsOut | uint256\[] memory | Calculated amounts of output tokens corresponding to the last step of each given path | \| tokensOut | address\[] memory | Output token addresses | \| amountsOut | uint256\[] memory | Calculated amounts of output tokens, ordered by output token address | ##### `swapExactOut` ```solidity function swapExactOut( SwapPathExactAmountOut[] memory paths, uint256 deadline, bool wethIsEth, bytes calldata userData ) external payable returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn); ``` Executes a swap operation involving multiple paths (steps), specifying exact output token amounts. You specify exactly how much you want to receive. **Parameters:** \| Name | Type | Description | \|------------|--------------------------------|----------------------------------------------------------------------------------------------| \| paths | SwapPathExactAmountOut\[] memory | Swap paths from token in to token out, specifying exact amounts out | \| deadline | uint256 | Deadline for the swap, after which it will revert | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes calldata | Additional (optional) data required for the swap | **Returns:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------------------------------| \| pathAmountsIn | uint256\[] memory | Calculated amounts of input tokens corresponding to the first step of each given path | \| tokensIn | address\[] memory | Input token addresses | \| amountsIn | uint256\[] memory | Calculated amounts of input tokens, ordered by input token address | ### Queries #### `querySwapExactIn` ```solidity function querySwapExactIn( SwapPathExactAmountIn[] memory paths, address sender, bytes calldata userData ) external returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut); ``` Queries a swap operation involving multiple paths (steps), specifying exact input token amounts, without actually executing it. **Parameters:** \| Name | Type | Description | \|------------|-------------------------------|----------------------------------------------------------------------------------------------| \| paths | SwapPathExactAmountIn\[] memory | Swap paths from token in to token out, specifying exact amounts in | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes calldata | Additional (optional) data required for the query | **Returns:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------------------------------| \| pathAmountsOut | uint256\[] memory | Calculated amounts of output tokens to be received corresponding to the last step of each given path | \| tokensOut | address\[] memory | Calculated output token addresses | \| amountsOut | uint256\[] memory | Calculated amounts of output tokens to be received, ordered by output token address | #### `querySwapExactOut` ```solidity function querySwapExactOut( SwapPathExactAmountOut[] memory paths, address sender, bytes calldata userData ) external returns (uint256[] memory pathAmountsIn, address[] memory tokensIn, uint256[] memory amountsIn); ``` Queries a swap operation involving multiple paths (steps), specifying exact output token amounts, without actually executing it. **Parameters:** \| Name | Type | Description | \|------------|--------------------------------|----------------------------------------------------------------------------------------------| \| paths | SwapPathExactAmountOut\[] memory | Swap paths from token in to token out, specifying exact amounts out | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes calldata | Additional (optional) data required for the query | **Returns:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------------------------------| \| pathAmountsIn | uint256\[] memory | Calculated amounts of input tokens to be sent corresponding to the first step of each given path | \| tokensIn | address\[] memory | Calculated input token addresses | \| amountsIn | uint256\[] memory | Calculated amounts of input tokens to be sent, ordered by input token address | ### Data Structures #### `SwapPathStep` ```solidity struct SwapPathStep { address pool; // Pool to swap through IERC20 tokenOut; // Token to receive from this step bool isBuffer; // If true, use ERC4626 buffer instead of pool } ``` Defines a single step in a swap path. **Fields:** \| Name | Type | Description | \|-----------|---------|----------------------------------------------------------------------------| \| pool | address | Address of the pool to swap through | \| tokenOut | IERC20 | Token to receive from this step | \| isBuffer | bool | If true, the "pool" is an ERC4626 Buffer used to wrap/unwrap tokens if pool doesn't have enough liquidity | #### `SwapPathExactAmountIn` ```solidity struct SwapPathExactAmountIn { IERC20 tokenIn; // Starting token SwapPathStep[] steps; // Swap steps to execute uint256 exactAmountIn; // Exact amount to send uint256 minAmountOut; // Minimum amount to receive } ``` Defines a swap path with an exact input amount. **Fields:** \| Name | Type | Description | \|---------------|-----------------|--------------------------------------------------------------------------------------------| \| tokenIn | IERC20 | Starting token for this path | \| steps | SwapPathStep\[] | Array of swap steps to execute. For each step: If tokenIn == pool, use removeLiquidity SINGLE\_TOKEN\_EXACT\_IN. If tokenOut == pool, use addLiquidity UNBALANCED | \| exactAmountIn | uint256 | Exact amount of tokenIn to send | \| minAmountOut | uint256 | Minimum amount of final output token to receive (slippage protection) | #### `SwapPathExactAmountOut` ```solidity struct SwapPathExactAmountOut { IERC20 tokenIn; // Starting token SwapPathStep[] steps; // Swap steps to execute uint256 maxAmountIn; // Maximum amount to send uint256 exactAmountOut; // Exact amount to receive } ``` Defines a swap path with an exact output amount. **Fields:** \| Name | Type | Description | \|----------------|-----------------|--------------------------------------------------------------------------------------------| \| tokenIn | IERC20 | Starting token for this path | \| steps | SwapPathStep\[] | Array of swap steps to execute. For each step: If tokenIn == pool, use removeLiquidity SINGLE\_TOKEN\_EXACT\_OUT. If tokenOut == pool, use addLiquidity SINGLE\_TOKEN\_EXACT\_OUT | \| maxAmountIn | uint256 | Maximum amount of tokenIn to send (slippage protection) | \| exactAmountOut | uint256 | Exact amount of final output token to receive | ### Use Cases * **Arbitrage**: Execute multiple profitable paths simultaneously * **Route optimization**: Split trades across multiple pools for better pricing * **Token bridging**: Swap through intermediate tokens (e.g., Token A → USDC → Token B) * **Complex strategies**: Combine swaps through multiple pools in one transaction ## Buffer Router API The Buffer Router can be used to interact with Balancer onchain via state changing operations or used to query operations in an off-chain context. The Buffer Router manages liquidity for internal ERC4626 buffers in the Vault. Buffers enable efficient wrapping/unwrapping of yield-bearing tokens by maintaining liquidity pools of both underlying and wrapped tokens. ### State-changing functions #### ERC4626 Buffers ##### `initializeBuffer` ```solidity function initializeBuffer( IERC4626 wrappedToken, uint256 exactAmountUnderlyingIn, uint256 exactAmountWrappedIn, uint256 minIssuedShares ) external returns (uint256 issuedShares); ``` Adds liquidity for the first time to an internal ERC4626 buffer in the Vault. This binds the wrapped token to its underlying asset permanently. **Important:** Calling this method binds the wrapped token to its underlying asset internally; the asset in the wrapper cannot change afterwards, or every other operation on that wrapper (add / remove / wrap / unwrap) will fail. To avoid unexpected behavior, always initialize buffers before creating or initializing any pools that contain the wrapped tokens to be used with them. **Parameters:** \| Name | Type | Description | \|--------------------------|----------|----------------------------------------------------------------------------------------------| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| exactAmountUnderlyingIn | uint256 | Amount of underlying tokens that will be deposited into the buffer | \| exactAmountWrappedIn | uint256 | Amount of wrapped tokens that will be deposited into the buffer | \| minIssuedShares | uint256 | Minimum amount of shares to receive from the buffer, expressed in underlying token native decimals | **Returns:** \| Name | Type | Description | \|--------------|---------|-------------------------------------------------------------------------------------------------------------| \| issuedShares | uint256 | The amount of tokens sharesOwner has in the buffer, denominated in underlying tokens (This is the BPT of the Vault's internal ERC4626 buffer) | ##### `addLiquidityToBuffer` ```solidity function addLiquidityToBuffer( IERC4626 wrappedToken, uint256 maxAmountUnderlyingIn, uint256 maxAmountWrappedIn, uint256 exactSharesToIssue ) external returns ( uint256 amountUnderlyingIn, uint256 amountWrappedIn ); ``` Adds liquidity proportionally to an existing internal ERC4626 buffer in the Vault. **Note:** Requires the buffer to be initialized beforehand. Restricting adds to proportional simplifies the Vault code, avoiding rounding issues and minimum amount checks. It is possible to add unbalanced by interacting with the wrapper contract directly. **Parameters:** \| Name | Type | Description | \|----------------------|----------|----------------------------------------------------------------------------------------------| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| maxAmountUnderlyingIn | uint256 | Maximum amount of underlying tokens to add to the buffer. It is expressed in underlying token native decimals | \| maxAmountWrappedIn | uint256 | Maximum amount of wrapped tokens to add to the buffer. It is expressed in wrapped token native decimals | \| exactSharesToIssue | uint256 | The amount of shares that `sharesOwner` wants to add to the buffer, in underlying token decimals | **Returns:** \| Name | Type | Description | \|---------------------|---------|------------------------------------------------------| \| amountUnderlyingIn | uint256 | Amount of underlying tokens deposited into the buffer | \| amountWrappedIn | uint256 | Amount of wrapped tokens deposited into the buffer | ### Queries #### `queryInitializeBuffer` ```solidity function queryInitializeBuffer( IERC4626 wrappedToken, uint256 exactAmountUnderlyingIn, uint256 exactAmountWrappedIn ) external returns (uint256 issuedShares); ``` Queries an `initializeBuffer` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|-------------------------|----------|---------------------------------------------------------------------------------| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| exactAmountUnderlyingIn | uint256 | Amount of underlying tokens that the sender wishes to deposit into the buffer | \| exactAmountWrappedIn | uint256 | Amount of wrapped tokens that the sender wishes to deposit into the buffer | **Returns:** \| Name | Type | Description | \|--------------|---------|--------------------------------------------------------------| \| issuedShares | uint256 | The amount of shares that would be minted, in underlying token decimals | #### `queryAddLiquidityToBuffer` ```solidity function queryAddLiquidityToBuffer( IERC4626 wrappedToken, uint256 exactSharesToIssue ) external returns ( uint256 amountUnderlyingIn, uint256 amountWrappedIn ); ``` Queries an `addLiquidityToBuffer` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|--------------------|----------|---------------------------------------------------------------------| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| exactSharesToIssue | uint256 | The amount of shares that would be minted, in underlying token decimals | **Returns:** \| Name | Type | Description | \|--------------------|---------|--------------------------------------------------------------| \| amountUnderlyingIn | uint256 | Amount of underlying tokens that would be deposited into the buffer | \| amountWrappedIn | uint256 | Amount of wrapped tokens that would be deposited into the buffer | #### `queryRemoveLiquidityFromBuffer` ```solidity function queryRemoveLiquidityFromBuffer( IERC4626 wrappedToken, uint256 exactSharesToRemove ) external returns ( uint256 removedUnderlyingBalanceOut, uint256 removedWrappedBalanceOut ); ``` Queries a `removeLiquidityFromBuffer` operation without actually executing it. **Note:** The execution function `removeLiquidityFromBuffer` exists on the Vault, not the router. **Parameters:** \| Name | Type | Description | \|---------------------|----------|----------------------------------------------------------------------| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| exactSharesToRemove | uint256 | The amount of shares that would be burned, in underlying token decimals | **Returns:** \| Name | Type | Description | \|------------------------------|---------|----------------------------------------------------------------| \| removedUnderlyingBalanceOut | uint256 | Amount of underlying tokens that would be removed from the buffer | \| removedWrappedBalanceOut | uint256 | Amount of wrapped tokens that would be removed from the buffer | ## Composite Liquidity Router API The Composite Liquidity Router can be used to interact with Balancer onchain via state changing operations or used to query operations in an off-chain context. Specialized router for two complex scenarios: 1. **ERC4626 Pools**: Pools containing yield-bearing tokens (e.g., waDAI, waUSDC) 2. **Nested Pools**: Pools where one or more tokens are BPTs from other pools ### State-changing functions #### ERC4626 Pool Operations ERC4626 pools contain wrapped yield-bearing tokens. This router allows you to interact with them using only underlying tokens, automatically handling wrapping/unwrapping through Vault buffers. ##### `addLiquidityUnbalancedToERC4626Pool` ```solidity function addLiquidityUnbalancedToERC4626Pool( address pool, bool[] memory wrapUnderlying, uint256[] memory exactAmountsIn, uint256 minBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256 bptAmountOut); ``` Adds liquidity to an ERC4626 pool using underlying or wrapped tokens. **Example:** Add liquidity to a \[waDAI, waUSDC] pool using only DAI and USDC. The router automatically: 1. Swaps DAI for waDAI through the Vault buffer 2. Swaps USDC for waUSDC through the Vault buffer 3. Adds liquidity to the pool **Note:** Ensure that any buffers associated with the wrapped tokens in the ERC4626 pool have been initialized before initializing or adding liquidity to the "parent" pool, and also make sure limits are set properly. **Parameters:** \| Name | Type | Description | \|-----------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| wrapUnderlying | bool\[] memory | For each token, true = use underlying (e.g., DAI), false = use wrapped (e.g., waDAI) | \| exactAmountsIn | uint256\[] memory | Exact amounts of underlying/wrapped tokens in, sorted in token registration order | \| minBptAmountOut | uint256 | Minimum amount of pool tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data required for adding liquidity | **Returns:** \| Name | Type | Description | \|--------------|---------|-----------------------------------------| \| bptAmountOut | uint256 | Actual amount of pool tokens received | ##### `addLiquidityProportionalToERC4626Pool` ```solidity function addLiquidityProportionalToERC4626Pool( address pool, bool[] memory wrapUnderlying, uint256[] memory maxAmountsIn, uint256 exactBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256[] memory amountsIn); ``` Adds liquidity proportionally to an ERC4626 pool using underlying or wrapped tokens. **Note:** Ensure that any buffers associated with the wrapped tokens in the ERC4626 pool have been initialized before initializing or adding liquidity to the "parent" pool, and also make sure limits are set properly. **Parameters:** \| Name | Type | Description | \|-------------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| wrapUnderlying | bool\[] memory | For each token, true = use underlying (e.g., DAI), false = use wrapped (e.g., waDAI) | \| maxAmountsIn | uint256\[] memory | Maximum amounts of underlying/wrapped tokens in, sorted in token registration order | \| exactBptAmountOut | uint256 | Exact amount of pool tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data required for adding liquidity | **Returns:** \| Name | Type | Description | \|-----------|------------------|-----------------------------------------------| \| amountsIn | uint256\[] memory | Actual amounts of tokens added to the pool | ##### `removeLiquidityProportionalFromERC4626Pool` ```solidity function removeLiquidityProportionalFromERC4626Pool( address pool, bool[] memory unwrapWrapped, uint256 exactBptAmountIn, uint256[] memory minAmountsOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256[] memory amountsOut); ``` Removes liquidity proportionally from an ERC4626 pool, receiving underlying or wrapped tokens. **Parameters:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| unwrapWrapped | bool\[] memory | For each token, true = receive underlying, false = receive wrapped | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided | \| minAmountsOut | uint256\[] memory | Minimum amounts of each token, sorted in token registration order | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data required for removing liquidity | **Returns:** \| Name | Type | Description | \|------------|------------------|------------------------------------| \| amountsOut | uint256\[] memory | Actual amounts of tokens received | #### Nested Pool Operations Nested pools contain BPTs from other pools as tokens. This router handles the complexity of traversing multiple pool levels. > NB: These nested pool functions will be introduced in CompositeLiquidityRouter V3, which is not yet released (as of January, 2026). **Important:** Pools with "overlapping" tokens (where both parent and child pools contain the same token) are not supported. ##### `addLiquidityUnbalancedNestedPool` ```solidity function addLiquidityUnbalancedNestedPool( address parentPool, address[] memory tokensIn, uint256[] memory exactAmountsIn, address[] memory tokensToWrap, uint256 minBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256 bptAmountOut); ``` Adds liquidity to a nested pool using only the leaf tokens (tokens from child pools). **Example:** * Parent pool: \[BPT\_A, BPT\_B, USDC] * Child pool A: \[DAI, USDT] * Child pool B: \[WETH, WBTC] * `tokensIn` could be: \[DAI, USDT, WETH, WBTC, USDC] The router: 1. Adds DAI and USDT to pool A to get BPT\_A 2. Adds WETH and WBTC to pool B to get BPT\_B 3. Adds BPT\_A, BPT\_B, and USDC to parent pool **Note:** A nested pool is one in which one or more tokens are BPTs from another pool (child pool). Since there are multiple pools involved, the token order is not well-defined, and must be specified by the caller. If the parent or nested pools contain ERC4626 tokens that appear in the `tokensToWrap` list, they will be wrapped and their underlying tokens pulled as input, and expected to appear in `tokensIn`. Otherwise, they will be treated as regular tokens. **Parameters:** \| Name | Type | Description | \|-----------------|------------------|----------------------------------------------------------------------------------| \| parentPool | address | The address of the parent pool (which contains BPTs of other pools) | \| tokensIn | address\[] memory | An array with all tokens from the child pools, and all non-BPT parent tokens, in arbitrary order | \| exactAmountsIn | uint256\[] memory | An array with the amountIn of each token, sorted in the same order as tokensIn | \| tokensToWrap | address\[] memory | A list of ERC4626 tokens which should be wrapped if encountered during pool traversal | \| minBptAmountOut | uint256 | Expected minimum amount of parent pool tokens to receive | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data required for the operation | **Returns:** \| Name | Type | Description | \|--------------|---------|------------------------------------------------| \| bptAmountOut | uint256 | The actual amount of parent pool tokens received | ##### `removeLiquidityProportionalNestedPool` ```solidity function removeLiquidityProportionalNestedPool( address parentPool, uint256 exactBptAmountIn, address[] memory tokensOut, uint256[] memory minAmountsOut, address[] memory tokensToUnwrap, bool wethIsEth, bytes memory userData ) external payable returns (uint256[] memory amountsOut); ``` Removes liquidity from a nested pool, receiving the leaf tokens. The router automatically: 1. Exits the parent pool to get child BPTs 2. Exits each child pool to get leaf tokens 3. Unwraps ERC4626 tokens if requested **Note:** A nested pool is one in which one or more tokens are BPTs from another pool (child pool). Since there are multiple pools involved, the token order is not well-defined, and must be specified by the caller. If the parent or nested pools contain ERC4626 tokens that appear in the `tokensToUnwrap` list, they will be unwrapped and their underlying tokens sent to the output. Otherwise, they will be treated as regular tokens. **Parameters:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------------------| \| parentPool | address | The address of the parent pool (which contains BPTs of other pools) | \| exactBptAmountIn | uint256 | The exact amount of `parentPool` tokens provided | \| tokensOut | address\[] memory | An array with all tokens from the child pools, and all non-BPT parent tokens, in arbitrary order | \| minAmountsOut | uint256\[] memory | An array with the minimum amountOut of each token, sorted in the same order as tokensOut | \| tokensToUnwrap | address\[] memory | A list of ERC4626 tokens which should be unwrapped if encountered during pool traversal | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data required for the operation | **Returns:** \| Name | Type | Description | \|------------|------------------|--------------------------------------------------------------------------------| \| amountsOut | uint256\[] memory | An array with the actual amountOut of each token, sorted in the same order as tokensOut | ### Queries #### `queryAddLiquidityUnbalancedToERC4626Pool` ```solidity function queryAddLiquidityUnbalancedToERC4626Pool( address pool, bool[] memory wrapUnderlying, uint256[] memory exactAmountsIn, address sender, bytes memory userData ) external returns (uint256 bptAmountOut); ``` Queries an `addLiquidityUnbalancedToERC4626Pool` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|----------------|------------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| wrapUnderlying | bool\[] memory | Flags indicating whether the corresponding token should be wrapped or used as an ERC20 | \| exactAmountsIn | uint256\[] memory | Exact amounts of underlying/wrapped tokens in, sorted in token registration order | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data required for the query | **Returns:** \| Name | Type | Description | \|--------------|---------|------------------------------------------| \| bptAmountOut | uint256 | Expected amount of pool tokens to receive | #### `queryAddLiquidityProportionalToERC4626Pool` ```solidity function queryAddLiquidityProportionalToERC4626Pool( address pool, bool[] memory wrapUnderlying, uint256 exactBptAmountOut, address sender, bytes memory userData ) external returns (uint256[] memory amountsIn); ``` Queries an `addLiquidityProportionalToERC4626Pool` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|-------------------|---------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| wrapUnderlying | bool\[] memory | Flags indicating whether the corresponding token should be wrapped or used as an ERC20 | \| exactBptAmountOut | uint256 | Exact amount of pool tokens to be received | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data required for the query | **Returns:** \| Name | Type | Description | \|-----------|------------------|--------------------------------------------| \| amountsIn | uint256\[] memory | Expected amounts of tokens added to the pool | #### `queryRemoveLiquidityProportionalFromERC4626Pool` ```solidity function queryRemoveLiquidityProportionalFromERC4626Pool( address pool, bool[] memory unwrapWrapped, uint256 exactBptAmountIn, address sender, bytes memory userData ) external returns (uint256[] memory amountsOut); ``` Queries a `removeLiquidityProportionalFromERC4626Pool` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|------------------|---------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| unwrapWrapped | bool\[] memory | Flags indicating whether the corresponding token should be unwrapped or used as an ERC20 | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided for the query | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data required for the query | **Returns:** \| Name | Type | Description | \|------------|------------------|-------------------------------------| \| amountsOut | uint256\[] memory | Expected amounts of tokens to receive | #### `queryAddLiquidityUnbalancedNestedPool` > NB: Nested pool operations will be introduced in CompositeLiquidityRouter V3, which is not yet released (as of January, 2026). ```solidity function queryAddLiquidityUnbalancedNestedPool( address parentPool, address[] memory tokensIn, uint256[] memory exactAmountsIn, address[] memory tokensToWrap, address sender, bytes memory userData ) external returns (uint256 bptAmountOut); ``` Queries an `addLiquidityUnbalancedNestedPool` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|----------------|------------------|-----------------------------------------------------------------------------------------| \| parentPool | address | The address of the parent pool (which contains BPTs of other pools) | \| tokensIn | address\[] memory | An array with all tokens from the child pools, and all non-BPT parent tokens, in arbitrary order | \| exactAmountsIn | uint256\[] memory | An array with the amountIn of each token, sorted in the same order as tokensIn | \| tokensToWrap | address\[] memory | A list of ERC4626 tokens which should be wrapped if encountered during pool traversal | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data required for the operation | **Returns:** \| Name | Type | Description | \|--------------|---------|------------------------------------------------| \| bptAmountOut | uint256 | The expected amount of parent pool tokens to receive | #### `queryRemoveLiquidityProportionalNestedPool` ```solidity function queryRemoveLiquidityProportionalNestedPool( address parentPool, uint256 exactBptAmountIn, address[] memory tokensOut, address[] memory tokensToUnwrap, address sender, bytes memory userData ) external returns (uint256[] memory amountsOut); ``` Queries a `removeLiquidityProportionalNestedPool` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|------------------|------------------|-----------------------------------------------------------------------------------------| \| parentPool | address | The address of the parent pool (which contains BPTs of other pools) | \| exactBptAmountIn | uint256 | The exact amount of `parentPool` tokens provided | \| tokensOut | address\[] memory | An array with all tokens from the child pools, and all non-BPT parent tokens, in arbitrary order | \| tokensToUnwrap | address\[] memory | A list of ERC4626 tokens which should be unwrapped if encountered during pool traversal | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data required for the operation | **Returns:** \| Name | Type | Description | \|------------|------------------|------------------------------------------------------------------------------------------| \| amountsOut | uint256\[] memory | An array with the expected amountOut of each token, sorted in the same order as tokensOut | ## Error Codes Balancer uses custom errors which provide a convenient and gas-efficient way to explain why an operation failed. Comments and context for the specific errors can be found in the tables below. ## governance-scripts ### BalancerContractRegistryInitializer \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AlreadyInitialized | | The initialization can only be done once. | `0x0dc149f0` | \| PermissionNotGranted | | A permission required to complete the initialization was not granted. | `0xe5557e90` | \| VaultMismatch | | The Vault passed in as a sanity check doesn't match the Vault associated with the registry. | `0xc1faacc5` | ### ProtocolFeeControllerMigration \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AlreadyMigrated | | Migration can only be performed once. | `0xca1c3cbc` | \| InvalidFeeController | | Attempt to deploy this contract with invalid parameters. | `0xd6f1cb05` | ## interfaces ### interfaces/oracles #### ILPOracleBase \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidOraclePrice | | Oracle prices must be greater than zero to prevent zero or negative TVL values. | `0x1f8f95a0` | \| UnsupportedDecimals | | A price feed has decimals greater than the maximum allowed. | `0xd4f1d302` | #### ILPOracleFactoryBase \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| OracleAlreadyExists(IBasePool,bool,AggregatorV3Interface\[],ILPOracleBase) | pool: IBasePool, shouldUseBlockTimeForOldestFeedUpdate: bool, feeds: AggregatorV3Interface\[], oracle: ILPOracleBase | Oracle already exists for the given pool. | `0xbcb86005` | \| OracleFactoryIsDisabled | | Oracle factory is disabled. | `0xb110e99d` | #### ISequencerUptimeFeed \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| SequencerDown | | The uptime sequencer has returned a status of "down". | `0x032b3d00` | \| SequencerResyncIncomplete | | A price feed was accessed while still within the resync window (e.g., after a sequencer outage). | `0xed1bba46` | ### interfaces/pool-cow #### ICowPoolFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidTrustedCowRouter | | The trusted CoW router cannot be address zero. | `0xb4d8fbf3` | #### ICowRouter \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InsufficientFunds(IERC20,uint256,uint256) | token: IERC20, senderCredits: uint256, senderDebits: uint256 | The funds transferred to the Vault and the swap tokenOut amount were not enough to pay for the Swap and Donate operation. | `0x0e60796c` | \| InvalidFeeSweeper | | The caller tried to set the zero address as the fee sweeper. | `0x05a399e2` | \| ProtocolFeePercentageAboveLimit(uint256,uint256) | newProtocolFeePercentage: uint256, maxProtocolFeePercentage: uint256 | The `newProtocolFeePercentage` is above the maximum limit. | `0xe76c2b23` | \| SwapDeadline | | The swap transaction was not validated before the specified deadline timestamp. | `0xe08b8af0` | ### interfaces/pool-gyro #### IGyro2CLPPool \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| SqrtParamsWrong | | The informed alpha is greater than beta. | `0x0579e1da` | ### interfaces/pool-hooks #### IECLPSurgeHook \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidImbalanceSlope | | Thrown when an invalid imbalance slope is provided. | `0x450a9fed` | \| InvalidRotationAngle | | The rotation angle is too small or too large for the surge hook to be used. | `0x4988ec15` | #### IMevCaptureHook \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidBalancerContractRegistry | | The `BalancerContractRegistry` set in the constructor is invalid. | `0x5c84f39b` | \| MevCaptureHookNotRegisteredInPool(address) | pool: address | The pool was not registered with the MEV Hook contract. | `0x7501acd8` | \| MevSwapFeePercentageAboveMax(uint256,uint256) | feePercentage: uint256, maxFeePercentage: uint256 | The new max MEV swap fee percentage is above the allowed absolute maximum. | `0x20fb3f00` | \| MevTaxExemptSenderAlreadyAdded(address) | sender: address | The sender is already registered as MEV tax-exempt. | `0x106fa5a4` | \| SenderNotRegisteredAsMevTaxExempt(address) | sender: address | The sender is not registered as MEV tax-exempt. | `0x01147f3f` | #### ISurgeHookCommon \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidPercentage | | The max surge fee and threshold values must be valid percentages. | `0x1f3b85d3` | ### interfaces/pool-weighted #### IFixedPriceLBPool \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidInitializationAmount | | An initialization amount is invalid (e.g., zero token balance, or non-zero reserve). | `0xfc3e9be7` | \| InvalidProjectTokenRate | | The token sale price cannot be zero. | `0x2d889800` | \| TokenSwapsInUnsupported | | All fixed price LBPools are "buy only;" token swaps in are not supported. | `0x0ad2684a` | #### ILBPool \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InsufficientRealReserveBalance(uint256,uint256) | reserveTokenAmountOut: uint256, reserveTokenRealBalance: uint256 | The amount out of the reserve token cannot exceed the real balance. | `0x37153449` | \| SeedlessLBPInitializationWithNonZeroReserve | | If the LBP is seedless, the caller must initialize with 0 reserve tokens. | `0x26704f1c` | #### IWeightedPool \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| MinWeight | | Indicates that one of the pool tokens' weight is below the minimum allowed. | `0xbd393583` | \| NormalizedWeightInvariant | | Indicates that the sum of the pool tokens' weights is not FixedPoint.ONE. | `0x39cf114e` | #### interfaces/solidity-utils/helpers ##### IAuthentication \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| SenderNotAllowed | | The sender does not have permission to call a function. | `0x23dada53` | ### interfaces/standalone-utils #### IBalancerContractRegistry \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ContractAddressAlreadyRegistered(ContractType,address) | contractType: ContractType, contractAddress: address | A contract has already been registered under the given address. | `0x961be8b5` | \| ContractAddressNotRegistered(address) | contractAddress: address | An operation that requires a valid contract specified an unrecognized address. | `0xf5b5d364` | \| ContractAliasInUseAsName(ContractType,string) | contractType: ContractType, contractName: string | The proposed alias has already been registered as a contract. | `0xcc986f2b` | \| ContractAlreadyDeprecated(address) | contractAddress: address | Contracts can only be deprecated once. | `0x1f118c35` | \| ContractNameAlreadyRegistered(ContractType,string) | contractType: ContractType, contractName: string | A contract has already been registered under the given name. | `0x0626a7b0` | \| ContractNameInUseAsAlias(string,address) | contractName: string, contractAddress: address | The proposed contract name has already been added as an alias. | `0x6d4f9990` | \| ContractNameNotRegistered(string) | contractName: string | Thrown when attempting to deregister a contract that was not previously registered. | `0xcd3599f9` | \| InvalidContractAlias | | Cannot add an empty string as an alias. | `0x907f9fd9` | \| InvalidContractName | | Cannot register (or deregister) a contract with an empty string as a name. | `0x830c907e` | \| ZeroContractAddress | | Cannot register or deprecate contracts, or add an alias targeting the zero address. | `0xb4d92c53` | #### IBalancerFeeBurner \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| BufferNotInitialized(address) | wrappedToken: address | Buffer not initialized for the wrapped token. | `0x85f41299` | \| BurnPathDoesNotExist | | Burn path not set for the fee token. | `0xf9aa0315` | \| InvalidBufferTokenOut(IERC20,uint256) | tokenOut: IERC20, step: uint256 | Invalid token out for buffer step. | `0x5a5e9413` | \| TargetTokenOutMismatch | | The last token in the path is not the same as the target token. | `0xa682e903` | \| TokenDoesNotExistInPool(IERC20,uint256) | token: IERC20, step: uint256 | Token does not exist in pool. | `0x9ef7cd5c` | #### ICowConditionalOrder \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| OrderNotValid(string) | reason: string | This error is returned by the `getTradeableOrder` function if the order conditions are not met. | `0xc8fc2725` | \| PollNever(string) | reason: string | The conditional order should not be polled again (i.e., deleted). | `0x981b64cd` | \| PollTryAtBlock(uint256,string) | blockNumber: uint256, reason: string | Polling should be retried at a specific block number. | `0x1fe8506e` | \| PollTryAtEpoch(uint256,string) | timestamp: uint256, reason: string | Polling should be retried at a specific epoch (unix timestamp). | `0x7e334637` | \| PollTryNextBlock(string) | reason: string | Polling should be retried at the next block. | `0xd05f3065` | #### ICowSwapFeeBurner \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InterfaceIsSignatureVerifierMuxer | | Fails on SignatureVerifierMuxer due to compatibility issues with ComposableCow. | `0x32798566` | \| InvalidOrderParameters(string) | reason: string | The order parameters were invalid. | `0x8d8a6110` | \| OrderHasUnexpectedStatus(OrderStatus) | actualStatus: OrderStatus | Attempt to revert an order that had not failed. | `0x3ba126d8` | #### IHyperEVMRateProviderFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| RateProviderAlreadyExists(uint32,uint32,address) | tokenIndex: uint32, pairIndex: uint32, rateProvider: address | A rate provider already exists for the given token and pair. | `0xf4c64ee1` | \| RateProviderFactoryIsDisabled | | The factory is disabled. | `0x42fb89b8` | \| RateProviderNotFound(uint32,uint32) | tokenIndex: uint32, pairIndex: uint32 | The rate provider was not found for the given token and pair. | `0xdc120e77` | #### IPoolHelperCommon \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| IndexOutOfBounds(uint256) | poolSetId: uint256 | An index is beyond the current bounds of the set. | `0x44945fcc` | \| InvalidPoolSetId(uint256) | poolSetId: uint256 | Pool set id associated with an operation is invalid. | `0x98592ddb` | \| InvalidPoolSetManager | | The initial manager of a pool set cannot be zero. | `0x2de5256e` | \| PoolAlreadyInSet(address,uint256) | pool: address, poolSetId: uint256 | Cannot add a pool that is already there. | `0x5a17aa8d` | \| PoolNotInSet(address,uint256) | pool: address, poolSetId: uint256 | Cannot remove a pool that was not added. | `0x80145d72` | \| PoolSetManagerNotUnique(address) | poolSetManager: address | Pool set managers can only manage a single pool set. | `0x2c35aa96` | \| SenderIsNotPoolSetManager | | Permissioned operations on pools can only be performed by the pool set manager. | `0xbcc08f74` | #### IPoolSwapFeeHelper \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| PoolHasSwapManager(address) | pool: address | Cannot add a pool that has a swap manager. | `0xf043494a` | #### IProtocolFeeBurner \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AmountOutBelowMin(IERC20,uint256,uint256) | tokenOut: IERC20, amountOut: uint256, minAmountOut: uint256 | The actual amount out is below the minimum limit specified for the operation. | `0x9eabe649` | \| SwapDeadline | | The swap transaction was not validated before the specified deadline timestamp. | `0xe08b8af0` | #### IProtocolFeeSweeper \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| BurnerDidNotConsumeAllowance | | The burner did not consume its entire allowance. | `0xc5bc8d51` | \| InvalidFeeRecipient | | The fee recipient is invalid. | `0x768dc598` | \| InvalidProtocolFeeBurner | | The protocol fee burner to be added is invalid. | `0x31ec2736` | \| InvalidTargetToken | | The target token is invalid. | `0x8562eb45` | \| ProtocolFeeBurnerAlreadyAdded(address) | protocolFeeBurner: address | Protocol fee burners can only be added to the allowlist once. | `0x6fe47af6` | \| ProtocolFeeBurnerNotAdded(address) | protocolFeeBurner: address | Protocol fee burners must be added to the allowlist before being removed. | `0xbca5ab34` | \| UnsupportedProtocolFeeBurner(address) | protocolFeeBurner: address | The specified fee burner has not been approved. | `0x38553f6c` | \| UnwrapIsNotAllowed | | Unwrapping is not allowed for the operation. | `0xca9e3a1e` | #### ITokenPairRegistry \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| BufferNotInitialized(address) | buffer: address | The given buffer address does not correspond to an initialized buffer. | `0x85f41299` | \| EmptyPath | | The path to add cannot be empty. | `0x20a2d33d` | \| IndexOutOfBounds | | Attempted to remove a path at an index beyond the registered length. | `0x4e23d035` | \| InvalidBufferPath(address,address,address) | buffer: address, tokenIn: address, tokenOut: address | The output token does not match the expected address in a wrap or unwrap operation. | `0x29198c3d` | \| InvalidRemovePath(address,address,address) | poolOrBuffer: address, tokenIn: address, tokenOut: address | The given pool or buffer is not registered as a path for the token pair. | `0x3a9458d9` | \| InvalidSimplePath(address) | path: address | The given address is not a valid pool or buffer. | `0xb309199b` | ### interfaces/vault #### IBasePoolFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| Disabled | | Attempted pool creation after the factory was disabled. | `0x75884cda` | \| IndexOutOfBounds | | A pool index is beyond the current bounds of the array. | `0x4e23d035` | #### ICompositeLiquidityRouterErrors \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| DuplicateTokenIn(address) | duplicateToken: address | The `tokensIn` array contains a duplicate token. | `0x60a054e0` | \| WrongTokensOut(address\[],address\[]) | actualTokensOut: address\[], expectedTokensOut: address\[] | The actual result of the liquidity removal operation does not match the expected set of tokens. | `0x94ae280c` | #### IERC20MultiTokenErrors \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| PoolTotalSupplyTooLow(uint256) | totalSupply: uint256 | The total supply of a pool token can't be lower than the absolute minimum. | `0xd38d20fc` | #### IProtocolFeeController \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| CallerIsNotPoolCreator(address,address) | caller: address, pool: address | Error raised if the wrong account attempts to withdraw pool creator fees. | `0xfbecdbf4` | \| PoolCreatorFeePercentageTooHigh | | Error raised when the pool creator swap or yield fee percentage exceeds the maximum allowed value. | `0x0370da74` | \| PoolCreatorNotRegistered(address) | pool: address | Error raised if there is no pool creator on a withdrawal attempt from the given pool. | `0x8bcbf353` | \| ProtocolSwapFeePercentageTooHigh | | Error raised when the protocol swap fee percentage exceeds the maximum allowed value. | `0x7e6eb7fb` | \| ProtocolYieldFeePercentageTooHigh | | Error raised when the protocol yield fee percentage exceeds the maximum allowed value. | `0xa7849e8e` | #### IProtocolFeePercentagesProvider \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| FactoryFeesNotSet(address) | factory: address | `setFactorySpecificProtocolFeePercentages` has not been called for this factory address. | `0xa589c09e` | \| PoolNotFromFactory(address,address) | pool: address, factory: address | The given pool is not from the expected factory. | `0xf400ce63` | \| UnknownFactory(address) | factory: address | Fees can only be set on recognized factories (i.e., registered in the `BalancerContractRegistry`). | `0xc2a47384` | \| WrongProtocolFeeControllerDeployment | | The protocol fee controller was configured with an incorrect Vault address. | `0x1bbe95c7` | #### IRouterCommon \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| OperationNotSupported | | The operation not supported by the router (e.g., permit2 operation when pre-paid). | `0x29a270f5` | #### ISenderGuard \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| EthTransfer | | Incoming ETH transfer from an address that is not WETH. | `0x0540ddf6` | \| SwapDeadline | | The swap transaction was not validated before the specified deadline timestamp. | `0xe08b8af0` | #### IUnbalancedAddViaSwapRouter \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AmountInAboveMaxAdjustableAmount(uint256,uint256) | amountIn: uint256, maxAdjustableAmount: uint256 | The amountIn for the adjustable token exceeds the maxAdjustableAmount specified. | `0xbe24bb39` | \| AmountInDoesNotMatchExact(uint256,uint256) | amountIn: uint256, exactAmount: uint256 | The amountIn for the exact token does not match the exactAmount specified. | `0xc1820fbb` | \| NotTwoTokenPool | | This router only supports two-token pools. | `0xfc20f864` | #### IVaultErrors \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AfterAddLiquidityHookFailed | | The pool has returned false to the afterAddLiquidity hook, indicating the transaction should revert. | `0xe1249165` | \| AfterInitializeHookFailed | | The pool has returned false to the afterInitialize hook, indicating the transaction should revert. | `0x0f23dbc6` | \| AfterRemoveLiquidityHookFailed | | The pool has returned false to the afterRemoveLiquidity hook, indicating the transaction should revert. | `0x1d3391d8` | \| AfterSwapHookFailed | | The pool has returned false to the afterSwap hook, indicating the transaction should revert. | `0x15a29dec` | \| AmountGivenZero | | The user tried to swap zero tokens. | `0x57a456b7` | \| AmountInAboveMax(IERC20,uint256,uint256) | tokenIn: IERC20, amountIn: uint256, maxAmountIn: uint256 | A required amountIn exceeds the maximum limit specified for the operation. | `0x40e7a003` | \| AmountOutBelowMin(IERC20,uint256,uint256) | tokenOut: IERC20, amountOut: uint256, minAmountOut: uint256 | The actual amount out is below the minimum limit specified for the operation. | `0x9eabe649` | \| BalanceNotSettled | | A transient accounting operation completed with outstanding token deltas. | `0x20f1d86d` | \| BeforeAddLiquidityHookFailed | | The pool has returned false to the beforeAddLiquidity hook, indicating the transaction should revert. | `0x0b2eb652` | \| BeforeInitializeHookFailed | | The pool has returned false to the beforeInitialize hook, indicating the transaction should revert. | `0x60612925` | \| BeforeRemoveLiquidityHookFailed | | The pool has returned false to the beforeRemoveLiquidity hook, indicating the transaction should revert. | `0x2aaf8866` | \| BeforeSwapHookFailed | | The pool has returned false to the beforeSwap hook, indicating the transaction should revert. | `0xe91e17e7` | \| BptAmountInAboveMax(uint256,uint256) | amountIn: uint256, maxAmountIn: uint256 | The required BPT amount in exceeds the maximum limit specified for the operation. | `0x31d38e0b` | \| BptAmountOutBelowMin(uint256,uint256) | amountOut: uint256, minAmountOut: uint256 | The BPT amount received from adding liquidity is below the minimum specified for the operation. | `0x8d261d5d` | \| BufferAlreadyInitialized(IERC4626) | wrappedToken: IERC4626 | The buffer for the given wrapped token was already initialized. | `0xee44489a` | \| BufferNotInitialized(IERC4626) | wrappedToken: IERC4626 | The buffer for the given wrapped token was not initialized. | `0x92998560` | \| BufferSharesInvalidOwner | | Buffer shares were burned from the zero address. | `0x586d06df` | \| BufferSharesInvalidReceiver | | Buffer shares were minted to the zero address. | `0xdbe6b10e` | \| BufferTotalSupplyTooLow(uint256) | totalSupply: uint256 | The total supply of a buffer can't be lower than the absolute minimum. | `0x34bdbfaa` | \| CannotReceiveEth | | The contract should not receive ETH. | `0xf2238896` | \| CannotSwapSameToken | | The user attempted to swap a token for itself. | `0xa54b181d` | \| DoesNotSupportAddLiquidityCustom | | Pool does not support adding liquidity with a customized input. | `0x4876c0bc` | \| DoesNotSupportDonation | | Pool does not support adding liquidity through donation. | `0xefe0265d` | \| DoesNotSupportRemoveLiquidityCustom | | Pool does not support removing liquidity with a customized input. | `0xcf0a95c0` | \| DoesNotSupportUnbalancedLiquidity | | Pool does not support adding / removing liquidity with an unbalanced input. | `0xd4f5779c` | \| DynamicSwapFeeHookFailed | | The pool has returned false to the beforeSwap hook, indicating the transaction should revert. | `0x53f976d4` | \| FeePrecisionTooHigh | | Primary fee percentages result in an aggregate fee that cannot be stored with the required precision. | `0x833fb3ce` | \| HookAdjustedAmountInAboveMax(IERC20,uint256,uint256) | tokenIn: IERC20, amountIn: uint256, maxAmountIn: uint256 | A hook adjusted amountIn exceeds the maximum limit specified for the operation. | `0xe3758c7d` | \| HookAdjustedAmountOutBelowMin(IERC20,uint256,uint256) | tokenOut: IERC20, amountOut: uint256, minAmountOut: uint256 | The hook adjusted amount out is below the minimum limit specified for the operation. | `0xabf6c797` | \| HookAdjustedSwapLimit(uint256,uint256) | amount: uint256, limit: uint256 | A hook adjusted amount in or out has exceeded the limit specified in the swap request. | `0xcc0e4a99` | \| HookRegistrationFailed(address,address,address) | poolHooksContract: address, pool: address, poolFactory: address | A hook contract rejected a pool on registration. | `0xfa93d814` | \| InvalidAddLiquidityKind | | Add liquidity kind not supported. | `0x6c02b395` | \| InvalidRemoveLiquidityKind | | Remove liquidity kind not supported. | `0x137a9a39` | \| InvalidToken | | Invalid tokens (e.g., zero) cannot be registered. | `0xc1ab6dc1` | \| InvalidTokenConfiguration | | The data in a TokenConfig struct is inconsistent or unsupported. | `0xdf450632` | \| InvalidTokenDecimals | | Tokens with more than 18 decimals are not supported. | `0x686d3607` | \| InvalidTokenType | | The token type given in a TokenConfig during pool registration is invalid. | `0xa1e9dd9d` | \| InvalidUnderlyingToken(IERC4626) | wrappedToken: IERC4626 | A wrapped token reported the zero address as its underlying token asset. | `0x4c089bd4` | \| IssuedSharesBelowMin(uint256,uint256) | issuedShares: uint256, minIssuedShares: uint256 | Shares issued during initialization are below the requested amount. | `0xda0cb07e` | \| MaxTokens | | The token count is above the maximum allowed. | `0x707bdf58` | \| MinTokens | | The token count is below the minimum allowed. | `0x5ed4ba8f` | \| NotEnoughBufferShares | | The user is trying to remove more than their allocated shares from the buffer. | `0x98c5dbd6` | \| NotEnoughUnderlying(IERC4626,uint256,uint256) | wrappedToken: IERC4626, expectedUnderlyingAmount: uint256, actualUnderlyingAmount: uint256 | A wrap/unwrap operation consumed more or returned less underlying tokens than it should. | `0xd5f9cbcd` | \| NotEnoughWrapped(IERC4626,uint256,uint256) | wrappedToken: IERC4626, expectedWrappedAmount: uint256, actualWrappedAmount: uint256 | A wrap/unwrap operation consumed more or returned less wrapped tokens than it should. | `0x1e04cc57` | \| NotVaultDelegateCall | | The `VaultExtension` contract was called by an account directly. | `0x9fd25b36` | \| PauseBufferPeriodDurationTooLarge | | The caller specified a buffer period longer than the maximum. | `0x9ea4efee` | \| PercentageAboveMax | | A given percentage is above the maximum (usually a value close to FixedPoint.ONE, or 1e18 wei). | `0x746e5940` | \| PoolAlreadyInitialized(address) | pool: address | A pool has already been initialized. `initialize` may only be called once. | `0x218e3747` | \| PoolAlreadyRegistered(address) | pool: address | A pool has already been registered. `registerPool` may only be called once. | `0xdb771c80` | \| PoolInRecoveryMode(address) | pool: address | Cannot enable recovery mode when already enabled. | `0x346d7607` | \| PoolNotInitialized(address) | pool: address | A referenced pool has not been initialized. | `0x4bdace13` | \| PoolNotInRecoveryMode(address) | pool: address | Cannot disable recovery mode when not enabled. | `0xef029adf` | \| PoolNotPaused(address) | pool: address | Governance tried to unpause the Pool when it was not paused. | `0xfdcd6894` | \| PoolNotRegistered(address) | pool: address | A pool has not been registered. | `0x9e51bd5c` | \| PoolPaused(address) | pool: address | A user tried to perform an operation involving a paused Pool. | `0xd971f597` | \| PoolPauseWindowExpired(address) | pool: address | Governance tried to pause a Pool after the pause period expired. | `0xeb5a1217` | \| ProtocolFeesExceedTotalCollected | | Error raised when there is an overflow in the fee calculation. | `0x4c69ac5d` | \| QueriesDisabled | | A user tried to execute a query operation when they were disabled. | `0x7a198886` | \| QueriesDisabledPermanently | | An admin tried to re-enable queries, but they were disabled permanently. | `0x069f8cbc` | \| QuoteResultSpoofed | | Quote reverted with a reserved error code. | `0x28f95541` | \| RouterNotTrusted | | An unauthorized Router tried to call a permissioned function (i.e., using the Vault's token allowance). | `0xe5d185cf` | \| SenderIsNotVault(address) | sender: address | Error indicating the sender is not the Vault (e.g., someone is trying to call a permissioned function). | `0x089676d5` | \| SwapFeePercentageTooHigh | | Error raised when the swap fee percentage is greater than the maximum allowed value. | `0x7f47834b` | \| SwapFeePercentageTooLow | | Error raised when the swap fee percentage is less than the minimum allowed value. | `0xbfb20688` | \| SwapLimit(uint256,uint256) | amount: uint256, limit: uint256 | An amount in or out has exceeded the limit specified in the swap request. | `0xe2ea151b` | \| TokenAlreadyRegistered(IERC20) | token: IERC20 | A token was already registered (i.e., it is a duplicate in the pool). | `0xcbc7ea2c` | \| TokenNotRegistered(IERC20) | token: IERC20 | The user attempted to operate with a token that is not in the pool. | `0x59674a0c` | \| TokensMismatch(address,address,address) | pool: address, expectedToken: address, actualToken: address | The token list passed into an operation does not match the pool tokens in the pool. | `0xffe261a1` | \| TradeAmountTooSmall | | The amount given or calculated for an operation is below the minimum limit. | `0x1ed4d118` | \| VaultBuffersArePaused | | Buffer operation attempted while vault buffers are paused. | `0x0f27df09` | \| VaultIsNotUnlocked | | A user called a Vault function (swap, add/remove liquidity) outside the lock context. | `0xc09ba736` | \| VaultNotPaused | | Governance tried to unpause the Vault when it was not paused. | `0xf7ff4dca` | \| VaultPaused | | A user tried to perform an operation while the Vault was paused. | `0xda9f8b34` | \| VaultPauseWindowDurationTooLarge | | The caller specified a pause window period longer than the maximum. | `0xcc0e8fe5` | \| VaultPauseWindowExpired | | Governance tried to pause the Vault after the pause period expired. | `0x0e4460b7` | \| WrapAmountTooSmall(IERC4626) | wrappedToken: IERC4626 | The amount given to wrap/unwrap was too small, which can introduce rounding issues. | `0x1a53f97f` | \| WrongProtocolFeeControllerDeployment | | The `ProtocolFeeController` contract was configured with an incorrect Vault address. | `0x1bbe95c7` | \| WrongUnderlyingToken(IERC4626,address) | wrappedToken: IERC4626, underlyingToken: address | The wrapped token asset does not match the underlying token. | `0xd5e7e2a6` | \| WrongVaultAdminDeployment | | The `VaultAdmin` contract was configured with an incorrect Vault address. | `0x82cc28b6` | \| WrongVaultExtensionDeployment | | The `VaultExtension` contract was configured with an incorrect Vault address. | `0x1ab9d9d0` | #### IWrappedBalancerPoolToken \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| VaultIsUnlocked | | The vault is unlocked | `0xbe18e309` | #### IWrappedBalancerPoolTokenFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| BalancerPoolTokenNotRegistered | | The Balancer pool token has not been registered. | `0x916f5d0e` | \| WrappedBPTAlreadyExists(address) | wrappedToken: address | BPT can only be wrapped once, and cannot be overwritten. | `0x957f7dce` | ## oracles ### EclpLPOracle \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| TokenPriceTooSmall | | One of the token prices is too small. | `0x1d2fcef0` | ### StableLPOracle \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| KDidNotConverge | | The `k` parameter did not converge to the positive root. | `0xdc95cdb4` | \| MinPriceTooLow | | The minimum price of the feed array is too low. | `0x478b96d8` | \| PriceRatioTooHigh | | The ratio between the maximum and minimum prices is too high. | `0xb4c522e0` | ## pool-gyro ### Gyro2CLPPoolFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| SupportsOnlyTwoTokens | | 2-CLP pools support 2 tokens only. | `0x34e77320` | ### GyroECLPPoolFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| SupportsOnlyTwoTokens | | E-CLP pools support 2 tokens only. | `0x34e77320` | ### pool-gyro/lib #### Gyro2CLPMath \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AssetBoundsExceeded | | | `0x03ba4186` | #### GyroECLPMath \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AssetBoundsExceeded | | | `0x03ba4186` | \| DerivedDsqWrong | | | `0xfb154af0` | \| DerivedTauAlphaNotNormalized | | | `0xc196e496` | \| DerivedTauAlphaYWrong | | | `0xec13362c` | \| DerivedTauBetaNotNormalized | | | `0x25bbd708` | \| DerivedTauBetaYWrong | | | `0xfa40768d` | \| DerivedTauXWrong | | | `0x4071c5a8` | \| DerivedUWrong | | | `0xf84d4b44` | \| DerivedVWrong | | | `0xcfb498d5` | \| DerivedWWrong | | | `0x83446b36` | \| DerivedZWrong | | | `0x12e3e411` | \| InvariantDenominatorWrong | | | `0xd1c17993` | \| MaxAssetsExceeded | | | `0x2da2a5e5` | \| MaxInvariantExceeded | | | `0xdc10196f` | \| RotationVectorCWrong | | | `0x658639aa` | \| RotationVectorNotNormalized | | | `0xa26d8c2e` | \| RotationVectorSWrong | | | `0xa9587a74` | \| StretchingFactorWrong | | | `0x77dfa312` | #### SignedFixedPoint \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AddOverflow | | | `0xa7f965e3` | \| DivInterval | | | `0xe03f5d57` | \| MulOverflow | | | `0x0cde6c26` | \| SubOverflow | | | `0x8a5d6af4` | \| ZeroDivision | | | `0x0a0c22c7` | ## pool-hooks ### ExitFeeHookExample \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ExitFeeAboveLimit(uint256,uint256) | feePercentage: uint256, limit: uint256 | The exit fee cannot exceed the maximum allowed percentage. | `0x05631b5c` | \| PoolDoesNotSupportDonation | | The pool does not support adding liquidity through donation. | `0xdfcf485a` | ### NftLiquidityPositionExample \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| CannotUseExternalRouter(address) | router: address | Hooks functions called from an external router. | `0x2f51a4f2` | \| PoolDoesNotSupportDonation | | The pool does not support adding liquidity through donation. | `0xdfcf485a` | \| PoolSupportsUnbalancedLiquidity | | The pool supports adding unbalanced liquidity. | `0x228342a4` | \| WithdrawalByNonOwner(address,address,uint256) | withdrawer: address, owner: address, nftId: uint256 | Attempted withdrawal of an NFT-associated position by an address that is not the owner. | `0x92cc6781` | ## pool-stable ### StablePool \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AmplificationFactorTooHigh | | The amplification factor is above the maximum of the range (1 - 5000). | `0x9b80d390` | \| AmplificationFactorTooLow | | The amplification factor is below the minimum of the range (1 - 5000). | `0xab923323` | \| AmpUpdateAlreadyStarted | | Amplification update operations must be done one at a time. | `0x2f301e7e` | \| AmpUpdateDurationTooShort | | The amplification change duration is too short. | `0xcd6b022a` | \| AmpUpdateNotStarted | | Cannot stop an amplification update before it starts. | `0x4673a675` | \| AmpUpdateRateTooFast | | The amplification change rate is too fast. | `0x1c708b92` | ## pool-utils ### BasePoolFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| StandardPoolWithCreator | | A pool creator was specified for a pool type that doesn't support it. | `0x61ee1764` | ## pool-weighted ### WeightedPool \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| WeightedPoolBptRateUnsupported | | `getRate` from `IRateProvider` was called on a Weighted Pool. | `0x18e79a20` | ### pool-weighted/lbp #### BaseLBPFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidTrustedRouter | | The zero address was given for the trusted router. | `0x0307417b` | #### BPTTimeLocker \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| BPTStillLocked(uint256) | unlockTimestamp: uint256 | The caller has a locked BPT balance, but is trying to burn it before the timelock expired. | `0x60489698` | \| NoLockedBPT | | The caller has no balance of the locked BPT. | `0x00e39db1` | #### LBPCommon \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AddingLiquidityNotAllowed | | The pool does not allow adding liquidity except during initialization and before the weight update. | `0x3eee08c7` | \| RemovingLiquidityNotAllowed | | Removing liquidity is not allowed before the end of the sale. | `0xf38b5770` | \| SwapOfProjectTokenIn | | The LBP configuration prohibits selling the project token back into the pool. | `0x1269438a` | \| SwapsDisabled | | Swaps are disabled except during the sale (i.e., between and start and end times). | `0xfdf79845` | \| UnsupportedOperation | | Single token liquidity operations (that call `computeBalance` are unsupported. | `0x9ba6061b` | #### LBPool \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| NotImplemented | | LBPs are WeightedPools by inheritance, but WeightedPool immutable/dynamic getters are wrong for LBPs. | `0xd6234725` | #### LBPValidation \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidOwner | | The owner is the zero address. | `0x49e27cff` | \| InvalidProjectToken | | The project token is the zero address. | `0x59977db3` | \| InvalidReserveToken | | The reserve token is the zero address. | `0xaaee807a` | \| TokensMustBeDifferent | | The project and reserve tokens must be different. | `0xfbfc7a91` | ### pool-weighted/lib #### GradualValueChange \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidStartTime(uint256,uint256) | resolvedStartTime: uint256, endTime: uint256 | Indicates that the start time is after the end time | `0xc9767706` | ## solidity-utils ### solidity-utils/helpers #### CodeDeployer \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| CodeDeploymentFailed | | | `0xfef82207` | #### EVMCallModeHelpers \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| NotStaticCall | | A state-changing transaction was initiated in a context that only allows static calls. | `0x67f84ab2` | #### FactoryWidePauseWindow \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| PoolPauseWindowDurationOverflow | | The factory deployer gave a duration that would overflow the Unix timestamp. | `0x68755a11` | #### InputHelpers \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AllZeroInputs | | No valid input was given for a single token operation. | `0x7e46bddc` | \| InputLengthMismatch | | Arrays passed to a function and intended to be parallel have different lengths. | `0xaaad13f7` | \| MultipleNonZeroInputs | | More than one non-zero value was given for a single token operation. | `0x6b8c3be5` | \| TokensNotSorted | | The tokens supplied to an array argument were not sorted in numerical order. | `0x6e8f1947` | #### PackedTokenBalance \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| BalanceOverflow | | One of the balances is above the maximum value that can be stored. | `0x89560ca1` | #### RevertCodec \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ErrorSelectorNotFound | | Handle the "reverted without a reason" case (i.e., no return data). | `0xa7285689` | \| Result(bytes) | result: bytes | On success of the primary operation in a `quoteAndRevert`, this error is thrown with the return data. | `0x5ab64fb8` | #### TransientStorageHelpers \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| TransientIndexOutOfBounds | | An index is out of bounds on an array operation (e.g., at). | `0x0f4ae0e4` | #### WordCodec \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| CodecOverflow | | Function called with an invalid value. | `0xe4337c05` | \| OutOfBounds | | Function called with an invalid bitLength or offset. | `0xb4120f14` | ### solidity-utils/math #### FixedPoint \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ZeroDivision | | Attempted division by zero. | `0x0a0c22c7` | #### LogExpMath \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| BaseOutOfBounds | | This error is thrown when a base is not within an acceptable range. | `0x022701e0` | \| ExponentOutOfBounds | | This error is thrown when a exponent is not within an acceptable range. | `0xd8317311` | \| InvalidExponent | | This error is thrown when an exponent used in the exp function is not within an acceptable range. | `0xd4794efd` | \| OutOfBounds | | This error is thrown when a variable or result is not within the acceptable bounds defined in the function. | `0xb4120f14` | \| ProductOutOfBounds | | This error is thrown when the exponent \* ln(base) is not within an acceptable range. | `0xa2f9f7e3` | #### StableMath \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| StableComputeBalanceDidNotConverge | | The iterations to calculate the balance didn't converge. | `0xdcbda05c` | \| StableInvariantDidNotConverge | | The iterations to calculate the invariant didn't converge. | `0x010ca320` | #### WeightedMath \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| MaxInRatio | | User attempted to add a disproportionate amountIn of tokens to a pool. | `0x340a4533` | \| MaxOutRatio | | User attempted to extract a disproportionate amountOut of tokens from a pool. | `0x64590b9f` | \| ZeroInvariant | | Error thrown when the calculated invariant is zero, indicating an issue with the invariant calculation. | `0x26543689` | ### solidity-utils/openzeppelin #### EnumerableMap \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| IndexOutOfBounds | | An index is beyond the current bounds of the set. | `0x4e23d035` | \| KeyNotFound | | This error is thrown when attempting to retrieve an entry that is not present in the map. | `0x5f3f479c` | #### EnumerableSet \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ElementNotFound | | An element that is not present in the set. | `0x66af5392` | \| IndexOutOfBounds | | An index is beyond the current bounds of the set. | `0x4e23d035` | #### ReentrancyGuardTransient \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ReentrancyGuardReentrantCall | | Unauthorized reentrant call. | `0x3ee5aeb5` | #### TransientEnumerableSet \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ElementNotFound | | An element that is not present in the set. | `0x66af5392` | \| IndexOutOfBounds | | An index is beyond the current bounds of the set. | `0x4e23d035` | ## standalone-utils ### BalancerContractRegistry \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InconsistentState(string,address) | contractName: string, contractAddress: address | A `_contractRegistry` entry has no corresponding `_contractInfo`. | `0x36a7ac0a` | ### CallAndRevert \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| QuoteResultSpoofed | | | `0x28f95541` | ### ERC4626CowSwapFeeBurner \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| AmountOutIsZero(IERC20) | token: IERC20 | The amount out is zero. | `0xc609fb47` | ### FeeBurnerAuthentication \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidProtocolFeeSweeper | | The fee protocol is invalid. | `0x932c92a5` | \| SenderNotAllowed | | The sender does not have permission to call a function. | `0x23dada53` | ### OwnableAuthentication \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| VaultNotSet | | The vault has not been set. | `0xc8e28160` | ### ProtocolFeeSweeper \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| CannotReceiveEth | | All pool tokens are ERC20, so this contract should not handle ETH. | `0xf2238896` | ### standalone-utils/utils #### HyperSpotPricePrecompile \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| SpotPriceIsZero | | The spot price is zero. | `0x44526c24` | \| SpotPricePrecompileFailed | | The precompile had an error while fetching the spot price. | `0x79827df5` | #### HyperTokenInfoPrecompile \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| TokenInfoPrecompileFailed | | The precompile had an error while fetching the token info. | `0x61c18134` | ## vault ### BalancerPoolToken \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| ERC2612ExpiredSignature(uint256) | deadline: uint256 | Operation failed due to an expired permit signature. | `0x62791302` | \| ERC2612InvalidSigner(address,address) | signer: address, owner: address | Operation failed due to a non-matching signature. | `0x4b800e46` | ### BasePoolMath \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvariantRatioAboveMax(uint256,uint256) | invariantRatio: uint256, maxInvariantRatio: uint256 | An add liquidity operation increased the invariant above the limit. | `0x3e8960dc` | \| InvariantRatioBelowMin(uint256,uint256) | invariantRatio: uint256, minInvariantRatio: uint256 | A remove liquidity operation decreased the invariant below the limit. | `0xe31c95be` | ### CommonAuthentication \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| VaultNotSet | | Vault cannot be address(0). | `0xc8e28160` | ### ProtocolFeeController \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidMigrationSource | | Migration source cannot be this contract. | `0xb82fd5bf` | \| PoolAlreadyRegistered(address) | pool: address | Prevent pool data from being registered more than once. | `0xdb771c80` | ### RouterHooks \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InsufficientPayment(IERC20) | token: IERC20 | The sender has not transferred the correct amount of tokens to the Vault. | `0xabf6c150` | ### VaultFactory \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InvalidBytecode(string) | contractName: string | The bytecode for the given contract does not match the expected bytecode. | `0xc7f4796e` | \| InvalidProtocolFeeController | | The ProtocolFeeController cannot be the zero address. | `0xd8b6cbcf` | \| VaultAddressMismatch | | The given salt does not match the generated address when attempting to create the Vault. | `0xb4c1be7b` | \| VaultAlreadyDeployed(address) | vault: address | The Vault has already been deployed at this target address. | `0xe254a88b` | ### vault/lib #### RouterWethLib \| Error | Arguments | Comment | Signature | \| --- | --- | --- | --- | \| InsufficientEth | | The amount of ETH paid is insufficient to complete this operation. | `0xa01a9df6` | ## Error selector index Catalogue for decoding custom error signatures into their associated error names. Sorted by selector (4-byte). \| Selector | Error | Arguments | Location | \| --- | --- | --- | --- | \| `0x00e39db1` | NoLockedBPT() | | [pool-weighted/lbp/BPTTimeLocker.sol](error-codes.md#bpttimelocker) | \| `0x010ca320` | StableInvariantDidNotConverge() | | [solidity-utils/math/StableMath.sol](error-codes.md#stablemath) | \| `0x01147f3f` | SenderNotRegisteredAsMevTaxExempt(address) | sender: address | [interfaces/pool-hooks/IMevCaptureHook.sol](error-codes.md#imevcapturehook) | \| `0x022701e0` | BaseOutOfBounds() | | [solidity-utils/math/LogExpMath.sol](error-codes.md#logexpmath) | \| `0x0307417b` | InvalidTrustedRouter() | | [pool-weighted/lbp/BaseLBPFactory.sol](error-codes.md#baselbpfactory) | \| `0x032b3d00` | SequencerDown() | | [interfaces/oracles/ISequencerUptimeFeed.sol](error-codes.md#isequenceruptimefeed) | \| `0x0370da74` | PoolCreatorFeePercentageTooHigh() | | [interfaces/vault/IProtocolFeeController.sol](error-codes.md#iprotocolfeecontroller) | \| `0x03ba4186` | AssetBoundsExceeded() | | [pool-gyro/lib/Gyro2CLPMath.sol](error-codes.md#gyro2clpmath) | \| `0x03ba4186` | AssetBoundsExceeded() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x0540ddf6` | EthTransfer() | | [interfaces/vault/ISenderGuard.sol](error-codes.md#isenderguard) | \| `0x05631b5c` | ExitFeeAboveLimit(uint256,uint256) | feePercentage: uint256, limit: uint256 | [pool-hooks/ExitFeeHookExample.sol](error-codes.md#exitfeehookexample) | \| `0x0579e1da` | SqrtParamsWrong() | | [interfaces/pool-gyro/IGyro2CLPPool.sol](error-codes.md#igyro2clppool) | \| `0x05a399e2` | InvalidFeeSweeper() | | [interfaces/pool-cow/ICowRouter.sol](error-codes.md#icowrouter) | \| `0x0626a7b0` | ContractNameAlreadyRegistered(ContractType,string) | contractType: ContractType, contractName: string | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0x069f8cbc` | QueriesDisabledPermanently() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x089676d5` | SenderIsNotVault(address) | sender: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x0a0c22c7` | ZeroDivision() | | [pool-gyro/lib/SignedFixedPoint.sol](error-codes.md#signedfixedpoint) | \| `0x0a0c22c7` | ZeroDivision() | | [solidity-utils/math/FixedPoint.sol](error-codes.md#fixedpoint) | \| `0x0ad2684a` | TokenSwapsInUnsupported() | | [interfaces/pool-weighted/IFixedPriceLBPool.sol](error-codes.md#ifixedpricelbpool) | \| `0x0b2eb652` | BeforeAddLiquidityHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x0cde6c26` | MulOverflow() | | [pool-gyro/lib/SignedFixedPoint.sol](error-codes.md#signedfixedpoint) | \| `0x0dc149f0` | AlreadyInitialized() | | [governance-scripts/BalancerContractRegistryInitializer.sol](error-codes.md#balancercontractregistryinitializer) | \| `0x0e4460b7` | VaultPauseWindowExpired() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x0e60796c` | InsufficientFunds(IERC20,uint256,uint256) | token: IERC20, senderCredits: uint256, senderDebits: uint256 | [interfaces/pool-cow/ICowRouter.sol](error-codes.md#icowrouter) | \| `0x0f23dbc6` | AfterInitializeHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x0f27df09` | VaultBuffersArePaused() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x0f4ae0e4` | TransientIndexOutOfBounds() | | [solidity-utils/helpers/TransientStorageHelpers.sol](error-codes.md#transientstoragehelpers) | \| `0x106fa5a4` | MevTaxExemptSenderAlreadyAdded(address) | sender: address | [interfaces/pool-hooks/IMevCaptureHook.sol](error-codes.md#imevcapturehook) | \| `0x1269438a` | SwapOfProjectTokenIn() | | [pool-weighted/lbp/LBPCommon.sol](error-codes.md#lbpcommon) | \| `0x12e3e411` | DerivedZWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x137a9a39` | InvalidRemoveLiquidityKind() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x15a29dec` | AfterSwapHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x18e79a20` | WeightedPoolBptRateUnsupported() | | [pool-weighted/WeightedPool.sol](error-codes.md#weightedpool) | \| `0x1a53f97f` | WrapAmountTooSmall(IERC4626) | wrappedToken: IERC4626 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x1ab9d9d0` | WrongVaultExtensionDeployment() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x1bbe95c7` | WrongProtocolFeeControllerDeployment() | | [interfaces/vault/IProtocolFeePercentagesProvider.sol](error-codes.md#iprotocolfeepercentagesprovider) | \| `0x1bbe95c7` | WrongProtocolFeeControllerDeployment() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x1c708b92` | AmpUpdateRateTooFast() | | [pool-stable/StablePool.sol](error-codes.md#stablepool) | \| `0x1d2fcef0` | TokenPriceTooSmall() | | [oracles/EclpLPOracle.sol](error-codes.md#eclplporacle) | \| `0x1d3391d8` | AfterRemoveLiquidityHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x1e04cc57` | NotEnoughWrapped(IERC4626,uint256,uint256) | wrappedToken: IERC4626, expectedWrappedAmount: uint256, actualWrappedAmount: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x1ed4d118` | TradeAmountTooSmall() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x1f118c35` | ContractAlreadyDeprecated(address) | contractAddress: address | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0x1f3b85d3` | InvalidPercentage() | | [interfaces/pool-hooks/ISurgeHookCommon.sol](error-codes.md#isurgehookcommon) | \| `0x1f8f95a0` | InvalidOraclePrice() | | [interfaces/oracles/ILPOracleBase.sol](error-codes.md#ilporaclebase) | \| `0x1fe8506e` | PollTryAtBlock(uint256,string) | blockNumber: uint256, reason: string | [interfaces/standalone-utils/ICowConditionalOrder.sol](error-codes.md#icowconditionalorder) | \| `0x20a2d33d` | EmptyPath() | | [interfaces/standalone-utils/ITokenPairRegistry.sol](error-codes.md#itokenpairregistry) | \| `0x20f1d86d` | BalanceNotSettled() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x20fb3f00` | MevSwapFeePercentageAboveMax(uint256,uint256) | feePercentage: uint256, maxFeePercentage: uint256 | [interfaces/pool-hooks/IMevCaptureHook.sol](error-codes.md#imevcapturehook) | \| `0x218e3747` | PoolAlreadyInitialized(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x228342a4` | PoolSupportsUnbalancedLiquidity() | | [pool-hooks/NftLiquidityPositionExample.sol](error-codes.md#nftliquiditypositionexample) | \| `0x23dada53` | SenderNotAllowed() | | [interfaces/solidity-utils/helpers/IAuthentication.sol](error-codes.md#iauthentication) | \| `0x23dada53` | SenderNotAllowed() | | [standalone-utils/FeeBurnerAuthentication.sol](error-codes.md#feeburnerauthentication) | \| `0x25bbd708` | DerivedTauBetaNotNormalized() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x26543689` | ZeroInvariant() | | [solidity-utils/math/WeightedMath.sol](error-codes.md#weightedmath) | \| `0x26704f1c` | SeedlessLBPInitializationWithNonZeroReserve() | | [interfaces/pool-weighted/ILBPool.sol](error-codes.md#ilbpool) | \| `0x28f95541` | QuoteResultSpoofed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x28f95541` | QuoteResultSpoofed() | | [standalone-utils/CallAndRevert.sol](error-codes.md#callandrevert) | \| `0x29198c3d` | InvalidBufferPath(address,address,address) | buffer: address, tokenIn: address, tokenOut: address | [interfaces/standalone-utils/ITokenPairRegistry.sol](error-codes.md#itokenpairregistry) | \| `0x29a270f5` | OperationNotSupported() | | [interfaces/vault/IRouterCommon.sol](error-codes.md#iroutercommon) | \| `0x2aaf8866` | BeforeRemoveLiquidityHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x2c35aa96` | PoolSetManagerNotUnique(address) | poolSetManager: address | [interfaces/standalone-utils/IPoolHelperCommon.sol](error-codes.md#ipoolhelpercommon) | \| `0x2d889800` | InvalidProjectTokenRate() | | [interfaces/pool-weighted/IFixedPriceLBPool.sol](error-codes.md#ifixedpricelbpool) | \| `0x2da2a5e5` | MaxAssetsExceeded() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x2de5256e` | InvalidPoolSetManager() | | [interfaces/standalone-utils/IPoolHelperCommon.sol](error-codes.md#ipoolhelpercommon) | \| `0x2f301e7e` | AmpUpdateAlreadyStarted() | | [pool-stable/StablePool.sol](error-codes.md#stablepool) | \| `0x2f51a4f2` | CannotUseExternalRouter(address) | router: address | [pool-hooks/NftLiquidityPositionExample.sol](error-codes.md#nftliquiditypositionexample) | \| `0x31d38e0b` | BptAmountInAboveMax(uint256,uint256) | amountIn: uint256, maxAmountIn: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x31ec2736` | InvalidProtocolFeeBurner() | | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0x32798566` | InterfaceIsSignatureVerifierMuxer() | | [interfaces/standalone-utils/ICowSwapFeeBurner.sol](error-codes.md#icowswapfeeburner) | \| `0x340a4533` | MaxInRatio() | | [solidity-utils/math/WeightedMath.sol](error-codes.md#weightedmath) | \| `0x346d7607` | PoolInRecoveryMode(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x34bdbfaa` | BufferTotalSupplyTooLow(uint256) | totalSupply: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x34e77320` | SupportsOnlyTwoTokens() | | [pool-gyro/Gyro2CLPPoolFactory.sol](error-codes.md#gyro2clppoolfactory) | \| `0x34e77320` | SupportsOnlyTwoTokens() | | [pool-gyro/GyroECLPPoolFactory.sol](error-codes.md#gyroeclppoolfactory) | \| `0x36a7ac0a` | InconsistentState(string,address) | contractName: string, contractAddress: address | [standalone-utils/BalancerContractRegistry.sol](error-codes.md#balancercontractregistry) | \| `0x37153449` | InsufficientRealReserveBalance(uint256,uint256) | reserveTokenAmountOut: uint256, reserveTokenRealBalance: uint256 | [interfaces/pool-weighted/ILBPool.sol](error-codes.md#ilbpool) | \| `0x38553f6c` | UnsupportedProtocolFeeBurner(address) | protocolFeeBurner: address | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0x39cf114e` | NormalizedWeightInvariant() | | [interfaces/pool-weighted/IWeightedPool.sol](error-codes.md#iweightedpool) | \| `0x3a9458d9` | InvalidRemovePath(address,address,address) | poolOrBuffer: address, tokenIn: address, tokenOut: address | [interfaces/standalone-utils/ITokenPairRegistry.sol](error-codes.md#itokenpairregistry) | \| `0x3ba126d8` | OrderHasUnexpectedStatus(OrderStatus) | actualStatus: OrderStatus | [interfaces/standalone-utils/ICowSwapFeeBurner.sol](error-codes.md#icowswapfeeburner) | \| `0x3e8960dc` | InvariantRatioAboveMax(uint256,uint256) | invariantRatio: uint256, maxInvariantRatio: uint256 | [vault/BasePoolMath.sol](error-codes.md#basepoolmath) | \| `0x3ee5aeb5` | ReentrancyGuardReentrantCall() | | [solidity-utils/openzeppelin/ReentrancyGuardTransient.sol](error-codes.md#reentrancyguardtransient) | \| `0x3eee08c7` | AddingLiquidityNotAllowed() | | [pool-weighted/lbp/LBPCommon.sol](error-codes.md#lbpcommon) | \| `0x4071c5a8` | DerivedTauXWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x40e7a003` | AmountInAboveMax(IERC20,uint256,uint256) | tokenIn: IERC20, amountIn: uint256, maxAmountIn: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x42fb89b8` | RateProviderFactoryIsDisabled() | | [interfaces/standalone-utils/IHyperEVMRateProviderFactory.sol](error-codes.md#ihyperevmrateproviderfactory) | \| `0x44526c24` | SpotPriceIsZero() | | [standalone-utils/utils/HyperSpotPricePrecompile.sol](error-codes.md#hyperspotpriceprecompile) | \| `0x44945fcc` | IndexOutOfBounds(uint256) | poolSetId: uint256 | [interfaces/standalone-utils/IPoolHelperCommon.sol](error-codes.md#ipoolhelpercommon) | \| `0x450a9fed` | InvalidImbalanceSlope() | | [interfaces/pool-hooks/IECLPSurgeHook.sol](error-codes.md#ieclpsurgehook) | \| `0x4673a675` | AmpUpdateNotStarted() | | [pool-stable/StablePool.sol](error-codes.md#stablepool) | \| `0x478b96d8` | MinPriceTooLow() | | [oracles/StableLPOracle.sol](error-codes.md#stablelporacle) | \| `0x4876c0bc` | DoesNotSupportAddLiquidityCustom() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x4988ec15` | InvalidRotationAngle() | | [interfaces/pool-hooks/IECLPSurgeHook.sol](error-codes.md#ieclpsurgehook) | \| `0x49e27cff` | InvalidOwner() | | [pool-weighted/lbp/LBPValidation.sol](error-codes.md#lbpvalidation) | \| `0x4b800e46` | ERC2612InvalidSigner(address,address) | signer: address, owner: address | [vault/BalancerPoolToken.sol](error-codes.md#balancerpooltoken) | \| `0x4bdace13` | PoolNotInitialized(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x4c089bd4` | InvalidUnderlyingToken(IERC4626) | wrappedToken: IERC4626 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x4c69ac5d` | ProtocolFeesExceedTotalCollected() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x4e23d035` | IndexOutOfBounds() | | [interfaces/standalone-utils/ITokenPairRegistry.sol](error-codes.md#itokenpairregistry) | \| `0x4e23d035` | IndexOutOfBounds() | | [interfaces/vault/IBasePoolFactory.sol](error-codes.md#ibasepoolfactory) | \| `0x4e23d035` | IndexOutOfBounds() | | [solidity-utils/openzeppelin/EnumerableMap.sol](error-codes.md#enumerablemap) | \| `0x4e23d035` | IndexOutOfBounds() | | [solidity-utils/openzeppelin/EnumerableSet.sol](error-codes.md#enumerableset) | \| `0x4e23d035` | IndexOutOfBounds() | | [solidity-utils/openzeppelin/TransientEnumerableSet.sol](error-codes.md#transientenumerableset) | \| `0x53f976d4` | DynamicSwapFeeHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x57a456b7` | AmountGivenZero() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x586d06df` | BufferSharesInvalidOwner() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x59674a0c` | TokenNotRegistered(IERC20) | token: IERC20 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x59977db3` | InvalidProjectToken() | | [pool-weighted/lbp/LBPValidation.sol](error-codes.md#lbpvalidation) | \| `0x5a17aa8d` | PoolAlreadyInSet(address,uint256) | pool: address, poolSetId: uint256 | [interfaces/standalone-utils/IPoolHelperCommon.sol](error-codes.md#ipoolhelpercommon) | \| `0x5a5e9413` | InvalidBufferTokenOut(IERC20,uint256) | tokenOut: IERC20, step: uint256 | [interfaces/standalone-utils/IBalancerFeeBurner.sol](error-codes.md#ibalancerfeeburner) | \| `0x5ab64fb8` | Result(bytes) | result: bytes | [solidity-utils/helpers/RevertCodec.sol](error-codes.md#revertcodec) | \| `0x5c84f39b` | InvalidBalancerContractRegistry() | | [interfaces/pool-hooks/IMevCaptureHook.sol](error-codes.md#imevcapturehook) | \| `0x5ed4ba8f` | MinTokens() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x5f3f479c` | KeyNotFound() | | [solidity-utils/openzeppelin/EnumerableMap.sol](error-codes.md#enumerablemap) | \| `0x60489698` | BPTStillLocked(uint256) | unlockTimestamp: uint256 | [pool-weighted/lbp/BPTTimeLocker.sol](error-codes.md#bpttimelocker) | \| `0x60612925` | BeforeInitializeHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x60a054e0` | DuplicateTokenIn(address) | duplicateToken: address | [interfaces/vault/ICompositeLiquidityRouterErrors.sol](error-codes.md#icompositeliquidityroutererrors) | \| `0x61c18134` | TokenInfoPrecompileFailed() | | [standalone-utils/utils/HyperTokenInfoPrecompile.sol](error-codes.md#hypertokeninfoprecompile) | \| `0x61ee1764` | StandardPoolWithCreator() | | [pool-utils/BasePoolFactory.sol](error-codes.md#basepoolfactory) | \| `0x62791302` | ERC2612ExpiredSignature(uint256) | deadline: uint256 | [vault/BalancerPoolToken.sol](error-codes.md#balancerpooltoken) | \| `0x64590b9f` | MaxOutRatio() | | [solidity-utils/math/WeightedMath.sol](error-codes.md#weightedmath) | \| `0x658639aa` | RotationVectorCWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x66af5392` | ElementNotFound() | | [solidity-utils/openzeppelin/EnumerableSet.sol](error-codes.md#enumerableset) | \| `0x66af5392` | ElementNotFound() | | [solidity-utils/openzeppelin/TransientEnumerableSet.sol](error-codes.md#transientenumerableset) | \| `0x67f84ab2` | NotStaticCall() | | [solidity-utils/helpers/EVMCallModeHelpers.sol](error-codes.md#evmcallmodehelpers) | \| `0x686d3607` | InvalidTokenDecimals() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x68755a11` | PoolPauseWindowDurationOverflow() | | [solidity-utils/helpers/FactoryWidePauseWindow.sol](error-codes.md#factorywidepausewindow) | \| `0x6b8c3be5` | MultipleNonZeroInputs() | | [solidity-utils/helpers/InputHelpers.sol](error-codes.md#inputhelpers) | \| `0x6c02b395` | InvalidAddLiquidityKind() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x6d4f9990` | ContractNameInUseAsAlias(string,address) | contractName: string, contractAddress: address | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0x6e8f1947` | TokensNotSorted() | | [solidity-utils/helpers/InputHelpers.sol](error-codes.md#inputhelpers) | \| `0x6fe47af6` | ProtocolFeeBurnerAlreadyAdded(address) | protocolFeeBurner: address | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0x707bdf58` | MaxTokens() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x746e5940` | PercentageAboveMax() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x7501acd8` | MevCaptureHookNotRegisteredInPool(address) | pool: address | [interfaces/pool-hooks/IMevCaptureHook.sol](error-codes.md#imevcapturehook) | \| `0x75884cda` | Disabled() | | [interfaces/vault/IBasePoolFactory.sol](error-codes.md#ibasepoolfactory) | \| `0x768dc598` | InvalidFeeRecipient() | | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0x77dfa312` | StretchingFactorWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x79827df5` | SpotPricePrecompileFailed() | | [standalone-utils/utils/HyperSpotPricePrecompile.sol](error-codes.md#hyperspotpriceprecompile) | \| `0x7a198886` | QueriesDisabled() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x7e334637` | PollTryAtEpoch(uint256,string) | timestamp: uint256, reason: string | [interfaces/standalone-utils/ICowConditionalOrder.sol](error-codes.md#icowconditionalorder) | \| `0x7e46bddc` | AllZeroInputs() | | [solidity-utils/helpers/InputHelpers.sol](error-codes.md#inputhelpers) | \| `0x7e6eb7fb` | ProtocolSwapFeePercentageTooHigh() | | [interfaces/vault/IProtocolFeeController.sol](error-codes.md#iprotocolfeecontroller) | \| `0x7f47834b` | SwapFeePercentageTooHigh() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x80145d72` | PoolNotInSet(address,uint256) | pool: address, poolSetId: uint256 | [interfaces/standalone-utils/IPoolHelperCommon.sol](error-codes.md#ipoolhelpercommon) | \| `0x82cc28b6` | WrongVaultAdminDeployment() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x830c907e` | InvalidContractName() | | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0x833fb3ce` | FeePrecisionTooHigh() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x83446b36` | DerivedWWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0x8562eb45` | InvalidTargetToken() | | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0x85f41299` | BufferNotInitialized(address) | wrappedToken: address | [interfaces/standalone-utils/IBalancerFeeBurner.sol](error-codes.md#ibalancerfeeburner) | \| `0x85f41299` | BufferNotInitialized(address) | buffer: address | [interfaces/standalone-utils/ITokenPairRegistry.sol](error-codes.md#itokenpairregistry) | \| `0x89560ca1` | BalanceOverflow() | | [solidity-utils/helpers/PackedTokenBalance.sol](error-codes.md#packedtokenbalance) | \| `0x8a5d6af4` | SubOverflow() | | [pool-gyro/lib/SignedFixedPoint.sol](error-codes.md#signedfixedpoint) | \| `0x8bcbf353` | PoolCreatorNotRegistered(address) | pool: address | [interfaces/vault/IProtocolFeeController.sol](error-codes.md#iprotocolfeecontroller) | \| `0x8d261d5d` | BptAmountOutBelowMin(uint256,uint256) | amountOut: uint256, minAmountOut: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x8d8a6110` | InvalidOrderParameters(string) | reason: string | [interfaces/standalone-utils/ICowSwapFeeBurner.sol](error-codes.md#icowswapfeeburner) | \| `0x907f9fd9` | InvalidContractAlias() | | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0x916f5d0e` | BalancerPoolTokenNotRegistered() | | [interfaces/vault/IWrappedBalancerPoolTokenFactory.sol](error-codes.md#iwrappedbalancerpooltokenfactory) | \| `0x92998560` | BufferNotInitialized(IERC4626) | wrappedToken: IERC4626 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x92cc6781` | WithdrawalByNonOwner(address,address,uint256) | withdrawer: address, owner: address, nftId: uint256 | [pool-hooks/NftLiquidityPositionExample.sol](error-codes.md#nftliquiditypositionexample) | \| `0x932c92a5` | InvalidProtocolFeeSweeper() | | [standalone-utils/FeeBurnerAuthentication.sol](error-codes.md#feeburnerauthentication) | \| `0x94ae280c` | WrongTokensOut(address\[],address\[]) | actualTokensOut: address\[], expectedTokensOut: address\[] | [interfaces/vault/ICompositeLiquidityRouterErrors.sol](error-codes.md#icompositeliquidityroutererrors) | \| `0x957f7dce` | WrappedBPTAlreadyExists(address) | wrappedToken: address | [interfaces/vault/IWrappedBalancerPoolTokenFactory.sol](error-codes.md#iwrappedbalancerpooltokenfactory) | \| `0x961be8b5` | ContractAddressAlreadyRegistered(ContractType,address) | contractType: ContractType, contractAddress: address | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0x981b64cd` | PollNever(string) | reason: string | [interfaces/standalone-utils/ICowConditionalOrder.sol](error-codes.md#icowconditionalorder) | \| `0x98592ddb` | InvalidPoolSetId(uint256) | poolSetId: uint256 | [interfaces/standalone-utils/IPoolHelperCommon.sol](error-codes.md#ipoolhelpercommon) | \| `0x98c5dbd6` | NotEnoughBufferShares() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x9b80d390` | AmplificationFactorTooHigh() | | [pool-stable/StablePool.sol](error-codes.md#stablepool) | \| `0x9ba6061b` | UnsupportedOperation() | | [pool-weighted/lbp/LBPCommon.sol](error-codes.md#lbpcommon) | \| `0x9e51bd5c` | PoolNotRegistered(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x9ea4efee` | PauseBufferPeriodDurationTooLarge() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x9eabe649` | AmountOutBelowMin(IERC20,uint256,uint256) | tokenOut: IERC20, amountOut: uint256, minAmountOut: uint256 | [interfaces/standalone-utils/IProtocolFeeBurner.sol](error-codes.md#iprotocolfeeburner) | \| `0x9eabe649` | AmountOutBelowMin(IERC20,uint256,uint256) | tokenOut: IERC20, amountOut: uint256, minAmountOut: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0x9ef7cd5c` | TokenDoesNotExistInPool(IERC20,uint256) | token: IERC20, step: uint256 | [interfaces/standalone-utils/IBalancerFeeBurner.sol](error-codes.md#ibalancerfeeburner) | \| `0x9fd25b36` | NotVaultDelegateCall() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xa01a9df6` | InsufficientEth() | | [vault/lib/RouterWethLib.sol](error-codes.md#routerwethlib) | \| `0xa1e9dd9d` | InvalidTokenType() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xa26d8c2e` | RotationVectorNotNormalized() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xa2f9f7e3` | ProductOutOfBounds() | | [solidity-utils/math/LogExpMath.sol](error-codes.md#logexpmath) | \| `0xa54b181d` | CannotSwapSameToken() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xa589c09e` | FactoryFeesNotSet(address) | factory: address | [interfaces/vault/IProtocolFeePercentagesProvider.sol](error-codes.md#iprotocolfeepercentagesprovider) | \| `0xa682e903` | TargetTokenOutMismatch() | | [interfaces/standalone-utils/IBalancerFeeBurner.sol](error-codes.md#ibalancerfeeburner) | \| `0xa7285689` | ErrorSelectorNotFound() | | [solidity-utils/helpers/RevertCodec.sol](error-codes.md#revertcodec) | \| `0xa7849e8e` | ProtocolYieldFeePercentageTooHigh() | | [interfaces/vault/IProtocolFeeController.sol](error-codes.md#iprotocolfeecontroller) | \| `0xa7f965e3` | AddOverflow() | | [pool-gyro/lib/SignedFixedPoint.sol](error-codes.md#signedfixedpoint) | \| `0xa9587a74` | RotationVectorSWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xaaad13f7` | InputLengthMismatch() | | [solidity-utils/helpers/InputHelpers.sol](error-codes.md#inputhelpers) | \| `0xaaee807a` | InvalidReserveToken() | | [pool-weighted/lbp/LBPValidation.sol](error-codes.md#lbpvalidation) | \| `0xab923323` | AmplificationFactorTooLow() | | [pool-stable/StablePool.sol](error-codes.md#stablepool) | \| `0xabf6c150` | InsufficientPayment(IERC20) | token: IERC20 | [vault/RouterHooks.sol](error-codes.md#routerhooks) | \| `0xabf6c797` | HookAdjustedAmountOutBelowMin(IERC20,uint256,uint256) | tokenOut: IERC20, amountOut: uint256, minAmountOut: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xb110e99d` | OracleFactoryIsDisabled() | | [interfaces/oracles/ILPOracleFactoryBase.sol](error-codes.md#ilporaclefactorybase) | \| `0xb309199b` | InvalidSimplePath(address) | path: address | [interfaces/standalone-utils/ITokenPairRegistry.sol](error-codes.md#itokenpairregistry) | \| `0xb4120f14` | OutOfBounds() | | [solidity-utils/helpers/WordCodec.sol](error-codes.md#wordcodec) | \| `0xb4120f14` | OutOfBounds() | | [solidity-utils/math/LogExpMath.sol](error-codes.md#logexpmath) | \| `0xb4c1be7b` | VaultAddressMismatch() | | [vault/VaultFactory.sol](error-codes.md#vaultfactory) | \| `0xb4c522e0` | PriceRatioTooHigh() | | [oracles/StableLPOracle.sol](error-codes.md#stablelporacle) | \| `0xb4d8fbf3` | InvalidTrustedCowRouter() | | [interfaces/pool-cow/ICowPoolFactory.sol](error-codes.md#icowpoolfactory) | \| `0xb4d92c53` | ZeroContractAddress() | | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0xb82fd5bf` | InvalidMigrationSource() | | [vault/ProtocolFeeController.sol](error-codes.md#protocolfeecontroller) | \| `0xbca5ab34` | ProtocolFeeBurnerNotAdded(address) | protocolFeeBurner: address | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0xbcb86005` | OracleAlreadyExists(IBasePool,bool,AggregatorV3Interface\[],ILPOracleBase) | pool: IBasePool, shouldUseBlockTimeForOldestFeedUpdate: bool, feeds: AggregatorV3Interface\[], oracle: ILPOracleBase | [interfaces/oracles/ILPOracleFactoryBase.sol](error-codes.md#ilporaclefactorybase) | \| `0xbcc08f74` | SenderIsNotPoolSetManager() | | [interfaces/standalone-utils/IPoolHelperCommon.sol](error-codes.md#ipoolhelpercommon) | \| `0xbd393583` | MinWeight() | | [interfaces/pool-weighted/IWeightedPool.sol](error-codes.md#iweightedpool) | \| `0xbe18e309` | VaultIsUnlocked() | | [interfaces/vault/IWrappedBalancerPoolToken.sol](error-codes.md#iwrappedbalancerpooltoken) | \| `0xbe24bb39` | AmountInAboveMaxAdjustableAmount(uint256,uint256) | amountIn: uint256, maxAdjustableAmount: uint256 | [interfaces/vault/IUnbalancedAddViaSwapRouter.sol](error-codes.md#iunbalancedaddviaswaprouter) | \| `0xbfb20688` | SwapFeePercentageTooLow() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xc09ba736` | VaultIsNotUnlocked() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xc1820fbb` | AmountInDoesNotMatchExact(uint256,uint256) | amountIn: uint256, exactAmount: uint256 | [interfaces/vault/IUnbalancedAddViaSwapRouter.sol](error-codes.md#iunbalancedaddviaswaprouter) | \| `0xc196e496` | DerivedTauAlphaNotNormalized() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xc1ab6dc1` | InvalidToken() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xc1faacc5` | VaultMismatch() | | [governance-scripts/BalancerContractRegistryInitializer.sol](error-codes.md#balancercontractregistryinitializer) | \| `0xc2a47384` | UnknownFactory(address) | factory: address | [interfaces/vault/IProtocolFeePercentagesProvider.sol](error-codes.md#iprotocolfeepercentagesprovider) | \| `0xc5bc8d51` | BurnerDidNotConsumeAllowance() | | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0xc609fb47` | AmountOutIsZero(IERC20) | token: IERC20 | [standalone-utils/ERC4626CowSwapFeeBurner.sol](error-codes.md#erc4626cowswapfeeburner) | \| `0xc7f4796e` | InvalidBytecode(string) | contractName: string | [vault/VaultFactory.sol](error-codes.md#vaultfactory) | \| `0xc8e28160` | VaultNotSet() | | [standalone-utils/OwnableAuthentication.sol](error-codes.md#ownableauthentication) | \| `0xc8e28160` | VaultNotSet() | | [vault/CommonAuthentication.sol](error-codes.md#commonauthentication) | \| `0xc8fc2725` | OrderNotValid(string) | reason: string | [interfaces/standalone-utils/ICowConditionalOrder.sol](error-codes.md#icowconditionalorder) | \| `0xc9767706` | InvalidStartTime(uint256,uint256) | resolvedStartTime: uint256, endTime: uint256 | [pool-weighted/lib/GradualValueChange.sol](error-codes.md#gradualvaluechange) | \| `0xca1c3cbc` | AlreadyMigrated() | | [governance-scripts/ProtocolFeeControllerMigration.sol](error-codes.md#protocolfeecontrollermigration) | \| `0xca9e3a1e` | UnwrapIsNotAllowed() | | [interfaces/standalone-utils/IProtocolFeeSweeper.sol](error-codes.md#iprotocolfeesweeper) | \| `0xcbc7ea2c` | TokenAlreadyRegistered(IERC20) | token: IERC20 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xcc0e4a99` | HookAdjustedSwapLimit(uint256,uint256) | amount: uint256, limit: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xcc0e8fe5` | VaultPauseWindowDurationTooLarge() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xcc986f2b` | ContractAliasInUseAsName(ContractType,string) | contractType: ContractType, contractName: string | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0xcd3599f9` | ContractNameNotRegistered(string) | contractName: string | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0xcd6b022a` | AmpUpdateDurationTooShort() | | [pool-stable/StablePool.sol](error-codes.md#stablepool) | \| `0xcf0a95c0` | DoesNotSupportRemoveLiquidityCustom() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xcfb498d5` | DerivedVWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xd05f3065` | PollTryNextBlock(string) | reason: string | [interfaces/standalone-utils/ICowConditionalOrder.sol](error-codes.md#icowconditionalorder) | \| `0xd1c17993` | InvariantDenominatorWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xd38d20fc` | PoolTotalSupplyTooLow(uint256) | totalSupply: uint256 | [interfaces/vault/IERC20MultiTokenErrors.sol](error-codes.md#ierc20multitokenerrors) | \| `0xd4794efd` | InvalidExponent() | | [solidity-utils/math/LogExpMath.sol](error-codes.md#logexpmath) | \| `0xd4f1d302` | UnsupportedDecimals() | | [interfaces/oracles/ILPOracleBase.sol](error-codes.md#ilporaclebase) | \| `0xd4f5779c` | DoesNotSupportUnbalancedLiquidity() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xd5e7e2a6` | WrongUnderlyingToken(IERC4626,address) | wrappedToken: IERC4626, underlyingToken: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xd5f9cbcd` | NotEnoughUnderlying(IERC4626,uint256,uint256) | wrappedToken: IERC4626, expectedUnderlyingAmount: uint256, actualUnderlyingAmount: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xd6234725` | NotImplemented() | | [pool-weighted/lbp/LBPool.sol](error-codes.md#lbpool) | \| `0xd6f1cb05` | InvalidFeeController() | | [governance-scripts/ProtocolFeeControllerMigration.sol](error-codes.md#protocolfeecontrollermigration) | \| `0xd8317311` | ExponentOutOfBounds() | | [solidity-utils/math/LogExpMath.sol](error-codes.md#logexpmath) | \| `0xd8b6cbcf` | InvalidProtocolFeeController() | | [vault/VaultFactory.sol](error-codes.md#vaultfactory) | \| `0xd971f597` | PoolPaused(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xda0cb07e` | IssuedSharesBelowMin(uint256,uint256) | issuedShares: uint256, minIssuedShares: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xda9f8b34` | VaultPaused() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xdb771c80` | PoolAlreadyRegistered(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xdb771c80` | PoolAlreadyRegistered(address) | pool: address | [vault/ProtocolFeeController.sol](error-codes.md#protocolfeecontroller) | \| `0xdbe6b10e` | BufferSharesInvalidReceiver() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xdc10196f` | MaxInvariantExceeded() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xdc120e77` | RateProviderNotFound(uint32,uint32) | tokenIndex: uint32, pairIndex: uint32 | [interfaces/standalone-utils/IHyperEVMRateProviderFactory.sol](error-codes.md#ihyperevmrateproviderfactory) | \| `0xdc95cdb4` | KDidNotConverge() | | [oracles/StableLPOracle.sol](error-codes.md#stablelporacle) | \| `0xdcbda05c` | StableComputeBalanceDidNotConverge() | | [solidity-utils/math/StableMath.sol](error-codes.md#stablemath) | \| `0xdf450632` | InvalidTokenConfiguration() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xdfcf485a` | PoolDoesNotSupportDonation() | | [pool-hooks/ExitFeeHookExample.sol](error-codes.md#exitfeehookexample) | \| `0xdfcf485a` | PoolDoesNotSupportDonation() | | [pool-hooks/NftLiquidityPositionExample.sol](error-codes.md#nftliquiditypositionexample) | \| `0xe03f5d57` | DivInterval() | | [pool-gyro/lib/SignedFixedPoint.sol](error-codes.md#signedfixedpoint) | \| `0xe08b8af0` | SwapDeadline() | | [interfaces/pool-cow/ICowRouter.sol](error-codes.md#icowrouter) | \| `0xe08b8af0` | SwapDeadline() | | [interfaces/standalone-utils/IProtocolFeeBurner.sol](error-codes.md#iprotocolfeeburner) | \| `0xe08b8af0` | SwapDeadline() | | [interfaces/vault/ISenderGuard.sol](error-codes.md#isenderguard) | \| `0xe1249165` | AfterAddLiquidityHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xe254a88b` | VaultAlreadyDeployed(address) | vault: address | [vault/VaultFactory.sol](error-codes.md#vaultfactory) | \| `0xe2ea151b` | SwapLimit(uint256,uint256) | amount: uint256, limit: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xe31c95be` | InvariantRatioBelowMin(uint256,uint256) | invariantRatio: uint256, minInvariantRatio: uint256 | [vault/BasePoolMath.sol](error-codes.md#basepoolmath) | \| `0xe3758c7d` | HookAdjustedAmountInAboveMax(IERC20,uint256,uint256) | tokenIn: IERC20, amountIn: uint256, maxAmountIn: uint256 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xe4337c05` | CodecOverflow() | | [solidity-utils/helpers/WordCodec.sol](error-codes.md#wordcodec) | \| `0xe5557e90` | PermissionNotGranted() | | [governance-scripts/BalancerContractRegistryInitializer.sol](error-codes.md#balancercontractregistryinitializer) | \| `0xe5d185cf` | RouterNotTrusted() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xe76c2b23` | ProtocolFeePercentageAboveLimit(uint256,uint256) | newProtocolFeePercentage: uint256, maxProtocolFeePercentage: uint256 | [interfaces/pool-cow/ICowRouter.sol](error-codes.md#icowrouter) | \| `0xe91e17e7` | BeforeSwapHookFailed() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xeb5a1217` | PoolPauseWindowExpired(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xec13362c` | DerivedTauAlphaYWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xed1bba46` | SequencerResyncIncomplete() | | [interfaces/oracles/ISequencerUptimeFeed.sol](error-codes.md#isequenceruptimefeed) | \| `0xee44489a` | BufferAlreadyInitialized(IERC4626) | wrappedToken: IERC4626 | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xef029adf` | PoolNotInRecoveryMode(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xefe0265d` | DoesNotSupportDonation() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xf043494a` | PoolHasSwapManager(address) | pool: address | [interfaces/standalone-utils/IPoolSwapFeeHelper.sol](error-codes.md#ipoolswapfeehelper) | \| `0xf2238896` | CannotReceiveEth() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xf2238896` | CannotReceiveEth() | | [standalone-utils/ProtocolFeeSweeper.sol](error-codes.md#protocolfeesweeper) | \| `0xf38b5770` | RemovingLiquidityNotAllowed() | | [pool-weighted/lbp/LBPCommon.sol](error-codes.md#lbpcommon) | \| `0xf400ce63` | PoolNotFromFactory(address,address) | pool: address, factory: address | [interfaces/vault/IProtocolFeePercentagesProvider.sol](error-codes.md#iprotocolfeepercentagesprovider) | \| `0xf4c64ee1` | RateProviderAlreadyExists(uint32,uint32,address) | tokenIndex: uint32, pairIndex: uint32, rateProvider: address | [interfaces/standalone-utils/IHyperEVMRateProviderFactory.sol](error-codes.md#ihyperevmrateproviderfactory) | \| `0xf5b5d364` | ContractAddressNotRegistered(address) | contractAddress: address | [interfaces/standalone-utils/IBalancerContractRegistry.sol](error-codes.md#ibalancercontractregistry) | \| `0xf7ff4dca` | VaultNotPaused() | | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xf84d4b44` | DerivedUWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xf9aa0315` | BurnPathDoesNotExist() | | [interfaces/standalone-utils/IBalancerFeeBurner.sol](error-codes.md#ibalancerfeeburner) | \| `0xfa40768d` | DerivedTauBetaYWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xfa93d814` | HookRegistrationFailed(address,address,address) | poolHooksContract: address, pool: address, poolFactory: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xfb154af0` | DerivedDsqWrong() | | [pool-gyro/lib/GyroECLPMath.sol](error-codes.md#gyroeclpmath) | \| `0xfbecdbf4` | CallerIsNotPoolCreator(address,address) | caller: address, pool: address | [interfaces/vault/IProtocolFeeController.sol](error-codes.md#iprotocolfeecontroller) | \| `0xfbfc7a91` | TokensMustBeDifferent() | | [pool-weighted/lbp/LBPValidation.sol](error-codes.md#lbpvalidation) | \| `0xfc20f864` | NotTwoTokenPool() | | [interfaces/vault/IUnbalancedAddViaSwapRouter.sol](error-codes.md#iunbalancedaddviaswaprouter) | \| `0xfc3e9be7` | InvalidInitializationAmount() | | [interfaces/pool-weighted/IFixedPriceLBPool.sol](error-codes.md#ifixedpricelbpool) | \| `0xfdcd6894` | PoolNotPaused(address) | pool: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | \| `0xfdf79845` | SwapsDisabled() | | [pool-weighted/lbp/LBPCommon.sol](error-codes.md#lbpcommon) | \| `0xfef82207` | CodeDeploymentFailed() | | [solidity-utils/helpers/CodeDeployer.sol](error-codes.md#codedeployer) | \| `0xffe261a1` | TokensMismatch(address,address,address) | pool: address, expectedToken: address, actualToken: address | [interfaces/vault/IVaultErrors.sol](error-codes.md#ivaulterrors) | ## Hooks Hooks have access to different data shared from the Vault. These allow a developer to build powerful execution logic when additionally utilizing the shared data. #### `onRegister` ```solidity function onRegister( address factory, address pool, TokenConfig[] memory tokenConfig, LiquidityManagement calldata liquidityManagement ) external returns (bool); ``` Hook to be executed when pool is registered. If it returns false, the registration is reverted. Vault address can be accessed with `msg.sender`. **Parameters:** \| Name | Type | Description | \|-----------------------|---------------------------------|--------------------------------------------------------------------| \| factory | address | Address of the pool factory | \| pool | address | Address of the pool | \| tokenConfig | TokenConfig\[] memory | An array of descriptors for the tokens the pool will manage | \| liquidityManagement | LiquidityManagement calldata | Liquidity management flags with implemented methods | **Returns:** \| Name | Type | Description | \|-----------|---------|----------------------------------------------------------| \| success | bool | True if the hook allowed the registration, false otherwise | #### `getHookFlags` ```solidity function getHookFlags() external returns (HookFlags memory hookFlags); ``` Returns flags informing which hooks are implemented in the contract. **Returns:** \| Name | Type | Description | \|-----------|--------------------|----------------------------------------------------------| \| hookFlags | HookFlags memory | Flags indicating which hooks the contract supports | #### `onBeforeInitialize` ```solidity function onBeforeInitialize(uint256[] memory exactAmountsIn, bytes memory userData) external returns (bool); ``` Optional hook to be executed before pool initialization. Note that unlike the swap and liquidity hooks, the initialize hooks are non-reentrant. **Parameters:** \| Name | Type | Description | \|---------------|--------------------|----------------------------------------------------------| \| exactAmountsIn| uint256\[] memory | Exact amounts of input tokens | \| userData | bytes memory | Optional, arbitrary data with the encoded request | **Returns:** \| Name | Type | Description | \|-----------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with initialization | #### `onAfterInitialize` ```solidity function onAfterInitialize( uint256[] memory exactAmountsIn, uint256 bptAmountOut, bytes memory userData ) external returns (bool); ``` Optional hook to be executed after pool initialization. Note that unlike the swap and liquidity hooks, the initialize hooks are non-reentrant. **Parameters:** \| Name | Type | Description | \|---------------|--------------------|----------------------------------------------------------| \| exactAmountsIn| uint256\[] memory | Exact amounts of input tokens | \| bptAmountOut | uint256 | Amount of pool tokens minted during initialization | \| userData | bytes memory | Optional, arbitrary data with the encoded request | **Returns:** \| Name | Type | Description | \|-----------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with initialization | #### `onBeforeAddLiquidity` ```solidity function onBeforeAddLiquidity( address router, address pool, AddLiquidityKind kind, uint256[] memory maxAmountsInScaled18, uint256 minBptAmountOut, uint256[] memory balancesScaled18, bytes memory userData ) external returns (bool success); ``` Optional hook to be executed before adding liquidity. **Parameters:** \| Name | Type | Description | \|-------------------|--------------------|----------------------------------------------------------| \| router | address | The address (usually a router contract) that initiated a swap operation on the Vault | \| pool | address | Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.) | \| kind | AddLiquidityKind | The type of add liquidity operation (e.g., proportional, custom) | \| maxAmountsInScaled18 | uint256\[] memory | Maximum amounts of input tokens | \| minBptAmountOut | uint256 | Minimum amount of output pool tokens | \| balancesScaled18 | uint256\[] memory | Current pool balances in token registration order | \| userData | bytes memory | Optional, arbitrary data with the encoded request | **Returns:** \| Name | Type | Description | \|-----------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with settlement | #### `onAfterAddLiquidity` ```solidity function onAfterAddLiquidity( address router, address pool, uint256[] memory amountsInScaled18, uint256[] memory amountsInRaw, uint256 bptAmountOut, uint256[] memory balancesScaled18, bytes memory userData ) external returns (bool success, uint256[] memory hookAdjustedAmountsInRaw); ``` Optional hook to be executed after adding liquidity. **Parameters:** \| Name | Type | Description | \|-------------------|--------------------|----------------------------------------------------------| \| router | address | The address (usually a router contract) that initiated a swap operation on the Vault | \| pool | address | Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.) | \| amountsInScaled18 | uint256\[] memory| Actual amounts of tokens added, sorted in token registration order \| amountsInRaw | uint256\[] memory | Actual amounts of tokens added, sorted in token registration order \| bptAmountOut | uint256 | Amount of pool tokens minted | \| balancesScaled18 | uint256\[] memory | Current pool balances in token registration order | \| userData | bytes memory | Additional (optional) data provided by the user | **Returns:** \| Name | Type | Description | \|-----------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with settlement | \| hookAdjustedAmountsInRaw | uint256\[] memory | New amountsInRaw, potentially modified by the hook | #### `onBeforeRemoveLiquidity` ```solidity function onBeforeRemoveLiquidity( address router, address pool, RemoveLiquidityKind kind, uint256 maxBptAmountIn, uint256[] memory minAmountsOutScaled18, uint256[] memory balancesScaled18, bytes memory userData ) external returns (bool success); ``` Optional hook to be executed before removing liquidity. **Parameters:** \| Name | Type | Description | \|-------------------|--------------------|----------------------------------------------------------| \| router | address | The address (usually a router contract) that initiated a swap operation on the Vault | \| pool | address | Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.) | \| kind | RemoveLiquidityKind| The type of remove liquidity operation (e.g., proportional, custom) | \| maxBptAmountIn | uint256 | Maximum amount of input pool tokens | \| minAmountsOutScaled18 | uint256\[] memory | Minimum output amounts in token registration order | \| balancesScaled18 | uint256\[] memory | Current pool balances in token registration order | \| userData | bytes memory | Optional, arbitrary data with the encoded request | **Returns:** \| Name | Type | Description | \|-----------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with settlement | #### `onAfterRemoveLiquidity` ```solidity function onAfterRemoveLiquidity( address router, address pool, RemoveLiquidityKind kind, uint256 bptAmountIn, uint256[] memory amountsOutScaled18, uint256[] memory amountsOutRaw, uint256[] memory balancesScaled18, bytes memory userData ) external returns (bool success, uint256[] memory hookAdjustedAmountsOutRaw); ``` Optional hook to be executed after removing liquidity. **Parameters:** \| Name | Type | Description | \|-------------------|--------------------|----------------------------------------------------------| \| router | address | The address (usually a router contract) that initiated a swap operation on the Vault | \| pool | address | Pool address, used to fetch pool information from the Vault (pool config, tokens, etc.) | \| kind | RemoveLiquidityKind| The type of remove liquidity operation (e.g., proportional, custom) | \| bptAmountIn | uint256 | Amount of pool tokens to burn | \| amountsOutScaled18| uint256\[] memory | Scaled amount of tokens to receive, in token registration order | \| amountsOutRaw| uint256\[] memory | Actual amount of tokens to receive, in token registration order | \| balancesScaled18 | uint256\[] memory | Current pool balances in token registration order | \| userData | bytes memory | Additional (optional) data provided by the user | **Returns:** \| Name | Type | Description | \|-----------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with settlement | \| hookAdjustedAmountsOutRaw | uint256\[] memory | New amountsOutRaw, potentially modified by the hook | #### `onBeforeSwap` ```solidity function onBeforeSwap(PoolSwapParams calldata params, address pool) external returns (bool success); ``` Called before a swap to give the Pool an opportunity to perform actions. **Parameters:** \| Name | Type | Description | \|-----------|----------------------------|----------------------------------------------------------| \| params | PoolSwapParams | Swap parameters | \| pool | address | Pool address, used to get pool information from the vault| **Returns:** \| Name | Type | Description | \|-----------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with settlement | #### `onAfterSwap` ```solidity function onAfterSwap( AfterSwapParams calldata params ) external returns (bool success, uint256 hookAdjustedAmountCalculatedRaw); ``` Called after a swap to give the Pool an opportunity to perform actions once the balances have been updated by the swap. **Parameters:** \| Name | Type | Description | \|-----------|--------------------|----------------------------------------------------------| \| params | AfterSwapParams | Swap parameters | **Returns:** \| Name | Type | Description | \|--------------------------------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with settlement | \| hookAdjustedAmountCalculatedRaw| uint256| New amount calculated, modified by the hook | #### `onComputeDynamicSwapFeePercentage` ```solidity function onComputeDynamicSwapFeePercentage( PoolSwapParams calldata params, address pool, uint256 staticSwapFeePercentage ) external view returns (bool success, uint256 dynamicSwapFeePercentage); ``` Called after `onBeforeSwap` and before the main swap operation, if the pool has dynamic fees. **Parameters:** \| Name | Type | Description | \|-------------------------|------------------|----------------------------------------------| \| params | PoolSwapParams | Swap parameters | \| pool | address | Address of the pool | \| staticSwapFeePercentage | uint256 | Value of the static swap fee, for reference | **Returns:** \| Name | Type | Description | \|----------------|--------|----------------------------------------------------------| \| success | bool | True if the pool wishes to proceed with settlement | \| dynamicSwapFeePercentage | uint256| Value of the swap fee | ## ProtocolFeesController The protocol Fees controller is used to manage protocol and pool creator fees for the Vault. Like the authorizer, it can be updated through governance. #### `collectAggregateFees` ```solidity function collectAggregateFees(address pool) external; ``` This function collects accumulated aggregate swap and yield fees for the specified pool. It makes a permissioned call on `collectAggregateFees` in the Vault to calculate the fee amounts, then calls `sendTo` to transfer the tokens, after which they are distributed to the protocol and pool creator balances. As this affects Vault accounting, it is invoked through `unlock` and a local "hook", with the ProtocolFeeController acting as the "Router". The Vault function supplies credit for the tokens to be taken as fees, and the fee controller takes debt (through `sendTo`), ensuring valid settlement. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which all aggregate fees should be collected | #### `getGlobalProtocolSwapFeePercentage` ```solidity function getGlobalProtocolSwapFeePercentage() external view returns (uint256); ``` This function returns the current global protocol swap fee percentage. This is the default value used for new pools deployed by standard factories. #### `getGlobalProtocolYieldFeePercentage` ```solidity function getGlobalProtocolYieldFeePercentage() external view returns (uint256); ``` This function returns the current global protocol yield fee percentage. This is the default value used for new pools deployed by standard factories. #### `getPoolProtocolSwapFeeInfo` ```solidity function getPoolProtocolSwapFeeInfo(address pool) external view returns (uint256, bool); ``` This function returns the current protocol swap fee for a given pool and a boolean indicating whether the protocol fee has been overridden by governance. Only pools whose protocol fees have NOT been overridden can be permissionlessly updated using `updateProtocolSwapFeePercentage`. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool for which to get the protocol swap fee info | #### `getPoolProtocolYieldFeeInfo` ```solidity function getPoolProtocolYieldFeeInfo(address pool) external view returns (uint256, bool); ``` This function returns the current protocol yield fee for a given pool and a boolean indicating if the protocol fee has been overridden by governance. Only pools whose protocol fees have NOT been overridden can be permissionlessly updated using `updateProtocolYieldFeePercentage`. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool for which to get the protocol yield fee info | #### `getProtocolFeeAmounts` ```solidity function getProtocolFeeAmounts(address pool) external view returns (uint256[] memory feeAmounts); ``` This function returns the amount of each pool token allocated to the protocol and available for withdrawal. It includes both swap and yield fees. Calling `collectAggregateFees` will transfer any pending fees from the Vault to the Protocol Fee Controller, and allocate them to the protocol and pool creator. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which fees were collected | #### `getPoolCreatorFeeAmounts` ```solidity function getPoolCreatorFeeAmounts(address pool) external view returns (uint256[] memory feeAmounts); ``` This function returns the amount of each pool token allocated to the pool creator and available for withdrawal. It includes both swap and yield fees. Calling `collectAggregateFees` on the Vault will transfer any pending fees from the Vault to the Protocol Fee Controller, and allocate them to the protocol and pool creator. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which fees were collected | #### `computeAggregateFeePercentage` ```solidity function computeAggregateFeePercentage( uint256 protocolFeePercentage, uint256 poolCreatorFeePercentage ) external pure returns (uint256 aggregateFeePercentage); ``` This function returns a calculated aggregate percentage from protocol and pool creator fee percentages. It's not tied to any particular pool; this just performs the low-level "additive fee" calculation. Note that this respects the Vault's 24-bit aggregate fee precision limit. If you try to set a fee such that the aggregate requires greater than 24-bit precision, this will revert. **Parameters:** \| Name | Type | Description | \|---|---|---| \| protocolFeePercentage | uint256 | The protocol portion of the aggregate fee percentage | \| poolCreatorFeePercentage | uint256 | The pool creator portion of the aggregate fee percentage | #### `updateProtocolSwapFeePercentage` ```solidity function updateProtocolSwapFeePercentage(address pool) external; ``` This function overrides the protocol swap fee percentage for a specific pool. This is a permissionless call, and will set the pool's fee to the current global fee, if it is different from the current value, and the fee is not controlled by governance (i.e., has never been overridden). **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool we are setting the protocol swap fee on | #### `updateProtocolYieldFeePercentage` ```solidity function updateProtocolYieldFeePercentage(address pool) external; ``` This function overrides the protocol yield fee percentage for a specific pool. This is a permissionless call, and will set the pool's fee to the current global fee, if it is different from the current value, and the fee is not controlled by governance (i.e., has never been overridden). **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool we are setting the protocol yield fee on | #### `registerPool` ```solidity function registerPool( address pool, address poolCreator, bool protocolFeeExempt ) external returns (uint256 aggregateSwapFeePercentage, uint256 aggregateYieldFeePercentage); ``` This function adds pool-specific entries for protocol swap and yield percentages. This must be called from the Vault during pool registration. It will initialize the pool to the global protocol fee percentage values (or 0, if the `protocolFeeExempt` flag is set), and return the initial aggregate fee percentages, based on an initial pool creator fee of 0. The idea here is to allow protocols to "test" new pool types with zero fees. Governance can always override the protocol fee percentages: the exemption only sets the starting value, and does not mean the pool creator controls the protocol fee. (The pool creator does exclusively control pool creator fees.) **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool being registered | \| poolCreator | address | The address of the pool creator (or 0 if there won't be a pool creator fee) | \| protocolFeeExempt | bool | If true, the pool is initially exempt from protocol fees | #### `setGlobalProtocolSwapFeePercentage` ```solidity function setGlobalProtocolSwapFeePercentage(uint256 newProtocolSwapFeePercentage) external; ``` This function sets the global protocol swap fee percentage, used by standard pools. This is a permissioned call. **Parameters:** \| Name | Type | Description | \|---|---|---| \| newProtocolSwapFeePercentage | uint256 | The new protocol swap fee percentage | #### `setGlobalProtocolYieldFeePercentage` ```solidity function setGlobalProtocolYieldFeePercentage(uint256 newProtocolYieldFeePercentage) external; ``` This function sets the global protocol yield fee percentage, used by standard pools. This is a permissioned call. **Parameters:** \| Name | Type | Description | \|---|---|---| \| newProtocolYieldFeePercentage | uint256 | The new protocol yield fee percentage | #### `setProtocolSwapFeePercentage` ```solidity function setProtocolSwapFeePercentage(address pool, uint256 newProtocolSwapFeePercentage) external; ``` This function overrides the protocol swap fee percentage for a specific pool. This is a permissioned call. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool we are setting the protocol swap fee on | \| newProtocolSwapFeePercentage | uint256 | The new protocol swap fee percentage for the specific pool | #### `setProtocolYieldFeePercentage` ```solidity function setProtocolYieldFeePercentage(address pool, uint256 newProtocolYieldFeePercentage) external; ``` This function overrides the protocol yield fee percentage for a specific pool. This is a permissioned call. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool we are setting the protocol yield fee on | \| newProtocolYieldFeePercentage | uint256 | The new protocol yield fee percentage | #### `setPoolCreatorSwapFeePercentage` ```solidity function setPoolCreatorSwapFeePercentage(address pool, uint256 poolCreatorSwapFeePercentage) external; ``` This function assigns a new pool creator swap fee percentage to the specified pool. This is a permissioned call; the caller must be the pool creator. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool for which the pool creator fee will be changed | \| poolCreatorSwapFeePercentage | uint256 | The new pool creator swap fee percentage to apply to the pool | #### `setPoolCreatorYieldFeePercentage` ```solidity function setPoolCreatorYieldFeePercentage(address pool, uint256 poolCreatorYieldFeePercentage) external; ``` This function assigns a new pool creator yield fee percentage to the specified pool. This is a permissioned call; the caller must be the pool creator. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool for which the pool creator fee will be changed | \| poolCreatorYieldFeePercentage | uint256 | The new pool creator yield fee percentage to apply to the pool | #### `withdrawProtocolFees` ```solidity function withdrawProtocolFees(address pool, address recipient) external; ``` This function withdraws collected protocol fees for a given pool. It sends swap and yield protocol fees to the recipient. This is a permissioned call. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which fees were collected | \| recipient | address | Address to send the tokens | #### `withdrawProtocolFeesForToken` ```solidity function withdrawProtocolFeesForToken(address pool, address recipient, IERC20 token) external; ``` This function withdraws collected protocol fees for a given pool and token. It sends swap and yield protocol fees to the recipient. This is a permissioned call, intended to cover the rare case when one of the tokens cannot be withdrawn (e.g., it is bricked or blocked). **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which fees were collected | \| recipient | address | Address to send the tokens | \| token | IERC20 | The token to withdraw #### `withdrawPoolCreatorFees` ```solidity function withdrawPoolCreatorFees(address pool, address recipient) external; ``` This function withdraws collected pool creator fees for a given pool. It sends swap and yield pool creator fees to the recipient. This is a permissioned call; the caller must be the pool creator. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which fees were collected | \| recipient | address | Address to send the tokens | #### `withdrawPoolCreatorFees` ```solidity function withdrawPoolCreatorFees(address pool) external; ``` This function withdraws collected pool creator fees for a given pool. It sends swap and yield pool creator fees to the registered pool creator. Since there is no recipient, this is a permissionless call, allowing easier automation of fee collection. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which fees were collected | ## Router API The Router can be used to interact with Balancer onchain via state changing operations or used to query operations in an off-chain context. The main router provides user-friendly interfaces for basic Vault operations: initialization, adding/removing liquidity, and single-pool swaps. ### State-changing functions #### Pool Initialization ##### `initialize` ```solidity function initialize( address pool, IERC20[] memory tokens, uint256[] memory exactAmountsIn, uint256 minBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256 bptAmountOut); ``` Initializes a new liquidity pool with exact token amounts. This is the first liquidity operation that must be performed on any pool. V3 pools have a separate initialization step, which changes pool metadata in the Vault to guarantee that it is only done once. This eliminates any attacks based on 're-initialization' of pools. **Parameters:** \| Name | Type | Description | \|-----------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool to initialize | \| tokens | IERC20\[] memory | Pool tokens in token registration order | \| exactAmountsIn | uint256\[] memory | Exact amounts of tokens to deposit, sorted in token registration order | \| minBptAmountOut | uint256 | Minimum BPT tokens to receive (slippage protection) | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data passed to the pool | **Returns:** \| Name | Type | Description | \|---------------|---------|----------------------------------------| \| bptAmountOut | uint256 | Actual amount of pool tokens minted | #### Add Liquidity Operations ##### `addLiquidityProportional` ```solidity function addLiquidityProportional( address pool, uint256[] memory maxAmountsIn, uint256 exactBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256[] memory amountsIn); ``` Adds liquidity proportionally to receive an exact amount of BPT. The ratio of tokens added matches the current pool composition. **Parameters:** \| Name | Type | Description | \|-------------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| maxAmountsIn | uint256\[] memory | Maximum amounts of tokens to be added, sorted in token registration order | \| exactBptAmountOut | uint256 | Exact amount of pool tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to add liquidity | **Returns:** \| Name | Type | Description | \|-----------|------------------|----------------------------------------------------------------| \| amountsIn | uint256\[] memory | Actual amounts of tokens added, sorted in token registration order | ##### `addLiquidityUnbalanced` ```solidity function addLiquidityUnbalanced( address pool, uint256[] memory exactAmountsIn, uint256 minBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256 bptAmountOut); ``` Adds liquidity with arbitrary token amounts (not necessarily proportional). The pool calculates the appropriate BPT to mint. **Parameters:** \| Name | Type | Description | \|-----------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactAmountsIn | uint256\[] memory | Exact amounts of tokens to be added, sorted in token registration order | \| minBptAmountOut | uint256 | Minimum amount of pool tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to add liquidity | **Returns:** \| Name | Type | Description | \|--------------|---------|-----------------------------------------| \| bptAmountOut | uint256 | Actual amount of pool tokens received | ##### `addLiquiditySingleTokenExactOut` ```solidity function addLiquiditySingleTokenExactOut( address pool, IERC20 tokenIn, uint256 maxAmountIn, uint256 exactBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256 amountIn); ``` Adds liquidity using only one token to receive an exact amount of BPT. Useful for adding liquidity when you only have one of the pool's tokens. **Parameters:** \| Name | Type | Description | \|-------------------|--------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| tokenIn | IERC20 | Token used to add liquidity | \| maxAmountIn | uint256 | Maximum amount of tokens to be added | \| exactBptAmountOut | uint256 | Exact amount of pool tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to add liquidity | **Returns:** \| Name | Type | Description | \|----------|---------|--------------------------------| \| amountIn | uint256 | Actual amount of tokens added | ##### `donate` ```solidity function donate( address pool, uint256[] memory amountsIn, bool wethIsEth, bytes memory userData ) external payable; ``` Donates tokens to a pool without receiving BPT. This increases the value of existing BPT tokens. The pool must have the `enableDonation` flag set to true. **Parameters:** \| Name | Type | Description | \|-----------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| amountsIn | uint256\[] memory | Amounts of tokens to be donated, sorted in token registration order | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to donate liquidity | ##### `addLiquidityCustom` ```solidity function addLiquidityCustom( address pool, uint256[] memory maxAmountsIn, uint256 minBptAmountOut, bool wethIsEth, bytes memory userData ) external payable returns ( uint256[] memory amountsIn, uint256 bptAmountOut, bytes memory returnData ); ``` Adds liquidity with a custom operation defined by the pool. The interpretation of max/min amounts depends on the pool type and userData. **Parameters:** \| Name | Type | Description | \|-----------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| maxAmountsIn | uint256\[] memory | Maximum amounts of tokens to be added, sorted in token registration order | \| minBptAmountOut | uint256 | Minimum amount of pool tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to add liquidity | **Returns:** \| Name | Type | Description | \|--------------|------------------|------------------------------------------------------------------------| \| amountsIn | uint256\[] memory | Actual amounts of tokens added, sorted in token registration order | \| bptAmountOut | uint256 | Actual amount of pool tokens received | \| returnData | bytes memory | Arbitrary (optional) data with an encoded response from the pool | #### Remove Liquidity Operations ##### `removeLiquidityProportional` ```solidity function removeLiquidityProportional( address pool, uint256 exactBptAmountIn, uint256[] memory minAmountsOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256[] memory amountsOut); ``` Removes liquidity proportionally by burning an exact amount of BPT. Receives all pool tokens in proportion to current pool composition. **Parameters:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided | \| minAmountsOut | uint256\[] memory | Minimum amounts of tokens to be received, sorted in token registration order | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to remove liquidity | **Returns:** \| Name | Type | Description | \|------------|------------------|---------------------------------------------------------------------| \| amountsOut | uint256\[] memory | Actual amounts of tokens received, sorted in token registration order | ##### `removeLiquiditySingleTokenExactIn` ```solidity function removeLiquiditySingleTokenExactIn( address pool, uint256 exactBptAmountIn, IERC20 tokenOut, uint256 minAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256 amountOut); ``` Burns an exact amount of BPT to receive a single token. Useful for exiting positions into one specific token. **Parameters:** \| Name | Type | Description | \|------------------|--------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided | \| tokenOut | IERC20 | Token to be received | \| minAmountOut | uint256 | Minimum amount of tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to remove liquidity | **Returns:** \| Name | Type | Description | \|-----------|---------|----------------------------------| \| amountOut | uint256 | Actual amount of tokens received | ##### `removeLiquiditySingleTokenExactOut` ```solidity function removeLiquiditySingleTokenExactOut( address pool, uint256 maxBptAmountIn, IERC20 tokenOut, uint256 exactAmountOut, bool wethIsEth, bytes memory userData ) external payable returns (uint256 bptAmountIn); ``` Burns BPT to receive an exact amount of a single token. You specify how much of the output token you want. **Parameters:** \| Name | Type | Description | \|----------------|--------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| maxBptAmountIn | uint256 | Maximum amount of pool tokens provided | \| tokenOut | IERC20 | Token to be received | \| exactAmountOut | uint256 | Exact amount of tokens to be received | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to remove liquidity | **Returns:** \| Name | Type | Description | \|-------------|---------|--------------------------------------| \| bptAmountIn | uint256 | Actual amount of pool tokens burned | ##### `removeLiquidityCustom` ```solidity function removeLiquidityCustom( address pool, uint256 maxBptAmountIn, uint256[] memory minAmountsOut, bool wethIsEth, bytes memory userData ) external payable returns ( uint256 bptAmountIn, uint256[] memory amountsOut, bytes memory returnData ); ``` Removes liquidity with a custom operation defined by the pool. **Parameters:** \| Name | Type | Description | \|----------------|------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| maxBptAmountIn | uint256 | Maximum amount of pool tokens provided | \| minAmountsOut | uint256\[] memory | Minimum amounts of tokens to be received, sorted in token registration order | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes memory | Additional (optional) data sent with the request to remove liquidity | **Returns:** \| Name | Type | Description | \|-------------|------------------|------------------------------------------------------------------------| \| bptAmountIn | uint256 | Actual amount of pool tokens burned | \| amountsOut | uint256\[] memory | Actual amounts of tokens received, sorted in token registration order | \| returnData | bytes memory | Arbitrary (optional) data with an encoded response from the pool | ##### `removeLiquidityRecovery` ```solidity function removeLiquidityRecovery( address pool, uint256 exactBptAmountIn, uint256[] memory minAmountsOut ) external payable returns (uint256[] memory amountsOut); ``` Emergency exit function available only when a pool is in Recovery Mode. Allows proportional exits even if the pool is in a bad state. **Parameters:** \| Name | Type | Description | \|------------------|------------------|----------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided | \| minAmountsOut | uint256\[] memory | Minimum amounts of tokens to be received, sorted in token registration order | **Returns:** \| Name | Type | Description | \|------------|------------------|---------------------------------------------------------------------| \| amountsOut | uint256\[] memory | Actual amounts of tokens received, sorted in token registration order | #### Swap Operations ##### `swapSingleTokenExactIn` ```solidity function swapSingleTokenExactIn( address pool, IERC20 tokenIn, IERC20 tokenOut, uint256 exactAmountIn, uint256 minAmountOut, uint256 deadline, bool wethIsEth, bytes calldata userData ) external payable returns (uint256 amountOut); ``` Swaps an exact amount of one token for another within a single pool. You specify the input amount. **Parameters:** \| Name | Type | Description | \|---------------|----------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| tokenIn | IERC20 | Token to be swapped from | \| tokenOut | IERC20 | Token to be swapped to | \| exactAmountIn | uint256 | Exact amount of input tokens to send | \| minAmountOut | uint256 | Minimum amount of tokens to be received | \| deadline | uint256 | Deadline for the swap, after which it will revert | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes calldata | Additional (optional) data sent with the swap request | **Returns:** \| Name | Type | Description | \|-----------|---------|------------------------------------------------------------------------------------------| \| amountOut | uint256 | Calculated amount of output tokens to be received in exchange for the given input tokens | ##### `swapSingleTokenExactOut` ```solidity function swapSingleTokenExactOut( address pool, IERC20 tokenIn, IERC20 tokenOut, uint256 exactAmountOut, uint256 maxAmountIn, uint256 deadline, bool wethIsEth, bytes calldata userData ) external payable returns (uint256 amountIn); ``` Swaps tokens to receive an exact amount of the output token. You specify the output amount you want. **Parameters:** \| Name | Type | Description | \|----------------|----------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| tokenIn | IERC20 | Token to be swapped from | \| tokenOut | IERC20 | Token to be swapped to | \| exactAmountOut | uint256 | Exact amount of output tokens to receive | \| maxAmountIn | uint256 | Maximum amount of tokens to be sent | \| deadline | uint256 | Deadline for the swap, after which it will revert | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| userData | bytes calldata | Additional (optional) data sent with the swap request | **Returns:** \| Name | Type | Description | \|----------|---------|-----------------------------------------------------------------------------------------------| \| amountIn | uint256 | Calculated amount of input tokens to be sent in exchange for the requested output tokens | ### Queries #### `queryAddLiquidityProportional` ```solidity function queryAddLiquidityProportional( address pool, uint256 exactBptAmountOut, address sender, bytes memory userData ) external returns (uint256[] memory amountsIn); ``` Queries an `addLiquidityProportional` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|-------------------|--------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactBptAmountOut | uint256 | Exact amount of pool tokens to be received | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|-----------|------------------|----------------------------------------------------------------------| \| amountsIn | uint256\[] memory | Expected amounts of tokens to add, sorted in token registration order | #### `queryAddLiquidityUnbalanced` ```solidity function queryAddLiquidityUnbalanced( address pool, uint256[] memory exactAmountsIn, address sender, bytes memory userData ) external returns (uint256 bptAmountOut); ``` Queries an `addLiquidityUnbalanced` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|----------------|------------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactAmountsIn | uint256\[] memory | Exact amounts of tokens to be added, sorted in token registration order | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|--------------|---------|------------------------------------------| \| bptAmountOut | uint256 | Expected amount of pool tokens to receive | #### `queryAddLiquiditySingleTokenExactOut` ```solidity function queryAddLiquiditySingleTokenExactOut( address pool, IERC20 tokenIn, uint256 exactBptAmountOut, address sender, bytes memory userData ) external returns (uint256 amountIn); ``` Queries an `addLiquiditySingleTokenExactOut` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|-------------------|--------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| tokenIn | IERC20 | Token used to add liquidity | \| exactBptAmountOut | uint256 | Expected exact amount of pool tokens to receive | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|----------|---------|----------------------------------| \| amountIn | uint256 | Expected amount of tokens to add | #### `queryAddLiquidityCustom` ```solidity function queryAddLiquidityCustom( address pool, uint256[] memory maxAmountsIn, uint256 minBptAmountOut, address sender, bytes memory userData ) external returns ( uint256[] memory amountsIn, uint256 bptAmountOut, bytes memory returnData ); ``` Queries an `addLiquidityCustom` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|-----------------|------------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| maxAmountsIn | uint256\[] memory | Expected maximum amounts of tokens to add, sorted in token registration order | \| minBptAmountOut | uint256 | Expected minimum amount of pool tokens to receive | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|--------------|------------------|--------------------------------------------------------------------| \| amountsIn | uint256\[] memory | Expected amounts of tokens to add, sorted in token registration order | \| bptAmountOut | uint256 | Expected amount of pool tokens to receive | \| returnData | bytes memory | Arbitrary (optional) data with an encoded response from the pool | #### `queryRemoveLiquidityProportional` ```solidity function queryRemoveLiquidityProportional( address pool, uint256 exactBptAmountIn, address sender, bytes memory userData ) external returns (uint256[] memory amountsOut); ``` Queries a `removeLiquidityProportional` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|------------------|--------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|------------|------------------|-------------------------------------------------------------------------| \| amountsOut | uint256\[] memory | Expected amounts of tokens to receive, sorted in token registration order | #### `queryRemoveLiquiditySingleTokenExactIn` ```solidity function queryRemoveLiquiditySingleTokenExactIn( address pool, uint256 exactBptAmountIn, IERC20 tokenOut, address sender, bytes memory userData ) external returns (uint256 amountOut); ``` Queries a `removeLiquiditySingleTokenExactIn` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|------------------|--------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided | \| tokenOut | IERC20 | Token to be received | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|-----------|---------|--------------------------------------| \| amountOut | uint256 | Expected amount of tokens to receive | #### `queryRemoveLiquiditySingleTokenExactOut` ```solidity function queryRemoveLiquiditySingleTokenExactOut( address pool, IERC20 tokenOut, uint256 exactAmountOut, address sender, bytes memory userData ) external returns (uint256 bptAmountIn); ``` Queries a `removeLiquiditySingleTokenExactOut` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|----------------|--------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| tokenOut | IERC20 | Token to be received | \| exactAmountOut | uint256 | Exact amount of tokens to be received | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|-------------|---------|------------------------------------------| \| bptAmountIn | uint256 | Expected amount of pool tokens to burn | #### `queryRemoveLiquidityCustom` ```solidity function queryRemoveLiquidityCustom( address pool, uint256 maxBptAmountIn, uint256[] memory minAmountsOut, address sender, bytes memory userData ) external returns ( uint256 bptAmountIn, uint256[] memory amountsOut, bytes memory returnData ); ``` Queries a `removeLiquidityCustom` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|----------------|------------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| maxBptAmountIn | uint256 | Maximum amount of pool tokens provided | \| minAmountsOut | uint256\[] memory | Expected minimum amounts of tokens to receive, sorted in token registration order | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes memory | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|-------------|------------------|----------------------------------------------------------------------------| \| bptAmountIn | uint256 | Expected amount of pool tokens to burn | \| amountsOut | uint256\[] memory | Expected amounts of tokens to receive, sorted in token registration order | \| returnData | bytes memory | Arbitrary (optional) data with an encoded response from the pool | #### `queryRemoveLiquidityRecovery` ```solidity function queryRemoveLiquidityRecovery( address pool, uint256 exactBptAmountIn ) external returns (uint256[] memory amountsOut); ``` Queries a `removeLiquidityRecovery` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|------------------|---------|------------------------------------------| \| pool | address | Address of the liquidity pool | \| exactBptAmountIn | uint256 | Exact amount of pool tokens provided for the query | **Returns:** \| Name | Type | Description | \|------------|------------------|-------------------------------------------------------------------------| \| amountsOut | uint256\[] memory | Expected amounts of tokens to receive, sorted in token registration order | #### `querySwapSingleTokenExactIn` ```solidity function querySwapSingleTokenExactIn( address pool, IERC20 tokenIn, IERC20 tokenOut, uint256 exactAmountIn, address sender, bytes calldata userData ) external returns (uint256 amountOut); ``` Queries a swap operation specifying an exact input token amount without actually executing it. **Parameters:** \| Name | Type | Description | \|---------------|----------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| tokenIn | IERC20 | Token to be swapped from | \| tokenOut | IERC20 | Token to be swapped to | \| exactAmountIn | uint256 | Exact amount of input tokens to send | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes calldata | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|-----------|---------|----------------------------------------------------------------------------------------------| \| amountOut | uint256 | Calculated amount of output tokens to be received in exchange for the given input tokens | #### `querySwapSingleTokenExactOut` ```solidity function querySwapSingleTokenExactOut( address pool, IERC20 tokenIn, IERC20 tokenOut, uint256 exactAmountOut, address sender, bytes calldata userData ) external returns (uint256 amountIn); ``` Queries a swap operation specifying an exact output token amount without actually executing it. **Parameters:** \| Name | Type | Description | \|----------------|----------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| tokenIn | IERC20 | Token to be swapped from | \| tokenOut | IERC20 | Token to be swapped to | \| exactAmountOut | uint256 | Exact amount of output tokens to receive | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| userData | bytes calldata | Additional (optional) data sent with the query request | **Returns:** \| Name | Type | Description | \|----------|---------|---------------------------------------------------------------------------------------------------| \| amountIn | uint256 | Calculated amount of input tokens to be sent in exchange for the requested output tokens | ## Security :::info This page will gradually be updated with more information. ::: ### Bug Bounty For more information of Balancer's Bug Bounty program, please visit our [Immunefi page](https://immunefi.com/bounty/balancer/). \::: warning **Bounties only apply to protocol smart contracts**. Bug reports pertaining to Balancer's web interfaces, both in terms of UI/UX or servers/infrastructure, are not eligible. \::: For security reports outside of the scope of the bug bounty program, please reach out via Discord. ### Audits See the [audits section](https://github.com/balancer/balancer-v3-monorepo/tree/main/audits) of the repo for the published audit reports. ### Code Immutability The core contracts that make up the Balancer v2 Protocol, such as the Vault and Pools (Weighted, Stable, LBP, Managed, Linear, etc), are immutable by design. Any pool updates are made by deploying brand new factories/pools and require users to electively migrate. ### Balancer x Certora Accelerator On the 10th [of October 2022](https://medium.com/balancer-protocol/balancer-and-certora-launch-security-accelerator-420d3b839a37), Balancer launched the Balancer Certora Security Accelerator in partnership with [Certora](https://www.certora.com/). The Security Accelerator helps projects building on Balancer increase their code security. The Accelerator provides code reviews and grants access to Certora's formal verification Prover. This alignment strengthens the soundness of the code base and streamlines the go-to-market process for projects building on Balancer. The Balancer x Certora Security Accelerator offers the following benefits: * Two weeks of manual code review by Certora engineers familiar with Balancer’s codebase * Set up and introduction of Certora's formal verification Prover * $10.000 USD worth of credits for Certora's formal verification Prover * Integration assistance by Balancer on code functionality and business logic ## Unbalanced Add Via Swap Router API The Unbalanced Add Via Swap Router can be used to interact with Balancer onchain via state changing operations or used to query operations in an off-chain context. Specialized router for adding unbalanced liquidity to two-token pools by combining a proportional add with a swap. **Use Case:** You want to add liquidity with an exact amount of one token and an approximate amount of another, but regular unbalanced adds might be inefficient or unavailable. ### State-changing functions #### `addLiquidityUnbalanced` ```solidity function addLiquidityUnbalanced( address pool, uint256 deadline, bool wethIsEth, AddLiquidityAndSwapParams calldata params ) external payable returns (uint256[] memory amountsIn); ``` Adds liquidity to a two-token pool with one exact amount and one adjustable amount by combining a proportional add with a swap in the same transaction. **How it works:** 1. Performs a proportional add with calculated amounts 2. Swaps the difference in the pool to achieve the exact desired amount 3. Results in exact `exactAmount` of `exactToken` and up to `maxAdjustableAmount` of the other token **Restrictions:** * Only works with two-token pools * Final `exactToken` amount must exactly match `exactAmount` * Final other token amount must not exceed `maxAdjustableAmount` **Parameters:** \| Name | Type | Description | \|-----------|----------------------------|----------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| deadline | uint256 | Timestamp after which the transaction will revert | \| wethIsEth | bool | If true, incoming ETH will be wrapped to WETH and outgoing WETH will be unwrapped to ETH | \| params | AddLiquidityAndSwapParams | Parameters for the add liquidity and swap operation | **Returns:** \| Name | Type | Description | \|-----------|------------------|--------------------------------------------------------------------------------| \| amountsIn | uint256\[] memory | Array of amounts in for each token added to the pool, sorted in token registration order | ### Queries #### `queryAddLiquidityUnbalanced` ```solidity function queryAddLiquidityUnbalanced( address pool, address sender, AddLiquidityAndSwapParams calldata params ) external returns (uint256[] memory amountsIn); ``` Queries an `addUnbalancedLiquidityViaSwap` operation without actually executing it. **Parameters:** \| Name | Type | Description | \|--------|---------------------------|-----------------------------------------------------------------------------------------| \| pool | address | Address of the liquidity pool | \| sender | address | The sender passed to the operation. It can influence results (e.g., with user-dependent hooks) | \| params | AddLiquidityAndSwapParams | Parameters for the add liquidity and swap operation | **Returns:** \| Name | Type | Description | \|-----------|------------------|--------------------------------------------------------------------------------| \| amountsIn | uint256\[] memory | Array of amounts in for each token added to the pool, sorted in token registration order | ### Data Structures #### `AddLiquidityAndSwapParams` ```solidity struct AddLiquidityAndSwapParams { uint256 exactBptAmountOut; // Exact BPT to receive IERC20 exactToken; // Token with exact amount uint256 exactAmount; // Exact amount of exactToken uint256 maxAdjustableAmount; // Max amount of other token bytes addLiquidityUserData; // Data for add operation bytes swapUserData; // Data for swap operation } ``` Parameters for adding liquidity via a combination of proportional add and swap. **Fields:** \| Name | Type | Description | \|---------------------|--------------|-----------------------------------------------------------------| \| exactBptAmountOut | uint256 | Exact amount of BPT tokens to receive | \| exactToken | IERC20 | Token that must have exactly `exactAmount` spent | \| exactAmount | uint256 | Exact amount of `exactToken` to use | \| maxAdjustableAmount | uint256 | Maximum amount of the other token to use | \| addLiquidityUserData | bytes | Additional (optional) data for the add liquidity operation | \| swapUserData | bytes | Additional (optional) data for the swap operation | ## The Vault \:::info Use the Router for swap, add liquidity and remove liquidity operations The [Router](../router/overview.html) is the primary entry-point for the Balancer Protocol. It exposes developer friendly interfaces for complex protocol interactions. \::: \:::info Interacting with the Vault on-chain The Ethereum Virtual Machine (EVM) imposes bytecode restrictions that limit the size of deployed contracts. In order to achieve the desired functionality, the Vault exceeds the bytecode limit of 24.576 kb. To overcome this, the Vault inherits from OpenZeppelin's Proxy contract and leverages delegate calls, allowing for the vault to utilize the functionality of more than one deployed smart contract. When interacting with the Balancer Vault via solidity, it is recommended to cast the Vaults address to an `IVault`. You can find the interface [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IVault.sol). \::: \:::info Vault Explorer Because of the constraints imposed by the Proxy pattern, the Vault contract itself doesn't expose much to blockchain explorers like Etherscan. You can see the extended functions by visiting the `VaultExtension` and `VaultAdmin` contracts, but any direct call on those contracts will revert. To provide access to the Vault through Etherscan in a user-friendly manner, there is a Vault "wrapper" contract called the `VaultExplorer`. This contract allows calling all permissionless Vault functions (e.g., `getPoolTokens`) through Etherscan. The explorer can also be used to call non-view permissionless functions that are defined in the code extensions. The most important use case is `enableRecoveryMode`, which can be called by any account whenever a pool is paused. All normal state-changing operations are disabled when a pool is paused, but Recovery Mode enables a simple proportional withdrawal path that cannot fail, so that LPs can always withdraw their funds: even under emergency conditions. Normally governance (or the pool's pause manager) should enable recovery mode in the same transaction as pausing, but in case they don't, the normally permissioned `enableRecoveryMode` function becomes permissionless whenever a pool is paused. \::: ### Transient accounting #### unlock ```solidity function unlock(bytes calldata data) external returns (bytes memory result); ``` This `Vault` function creates a context for a sequence of operations, effectively "unlocking" the Vault. It performs a callback on `msg.sender` with arguments provided in `data`. The callback is `transient`, meaning all balances for the caller have to be settled at the end. **Parameters:** \| Name | Type | Description | \|---|---|---| \| data | bytes | Contains function signature and args to be passed to the msg.sender | **Returns:** \| Name | Type | Description | \|---|---|---| \| result | bytes | Resulting data from the call | #### settle ```solidity function settle(IERC20 token, uint256 amountHint) external returns (uint256 credit); ``` This `Vault` function settles deltas for a token. This operation must be successful for the current lock to be released. It returns the credit supplied by the Vault, which can be calculated as `min(reserveDifference, amountHint)`, where the reserve difference equals current balance of the token minus existing reserves of the token when the function is called. The purpose of the hint is to protect against "donation DDoS attacks," where someone sends extra tokens to the Vault during the transaction (e.g., using reentrancy), which otherwise would cause settlement to fail. If the `reserveDifference` > `amountHint`, any "extra" tokens will simply be absorbed by the Vault (and reflected in the reserves), and not affect settlement. (The tokens will not be recoverable, as in V2.) **Parameters:** \| Name | Type | Description | \|---|---|---| \| token | IERC20 | Token's address | \| amountHint | uint256 | Amount the caller expects to be credited | **Returns:** \| Name | Type | Description | \|---|---|---| \| credit | uint256 | Amount credited to the caller for settlement | #### sendTo ```solidity function sendTo(IERC20 token, address to, uint256 amount) external; ``` This `Vault` function sends tokens to a recipient. There is no inverse operation for this function. To cancel debts, transfer funds to the Vault and call `settle`. **Parameters:** \| Name | Type | Description | \|---|---|---| \| token | IERC20 | Token's address | \| to | address | Recipient's address | \| amount | uint256 | Amount of tokens to send | #### `isUnlocked` ```solidity function isUnlocked() external view returns (bool); ``` This `VaultExtension` function returns True if the Vault is unlocked, false otherwise. **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if the Vault is unlocked, false otherwise | #### `getNonzeroDeltaCount` ```solidity function getNonzeroDeltaCount() external view returns (uint256); ``` This `VaultExtension` function returns the count of non-zero deltas. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The current value of \_nonzeroDeltaCount | #### `getTokenDelta` ```solidity function getTokenDelta(IERC20 token) external view returns (int256); ``` This `VaultExtension` function retrieves the token delta for a specific user and token. **Parameters:** \| Name | Type | Description | \|---|---|---| \| token | IERC20 | The token for which the delta is being fetched | **Returns:** \| Name | Type | Description | \|---|---|---| \| | int256 | The delta of the specified token for the specified user | #### `getReservesOf` ```solidity function getReservesOf(IERC20 token) external view returns (uint256); ``` This `VaultExtension` function retrieves the reserve (i.e., total Vault balance) of a given token. **Parameters:** \| Name | Type | Description | \|---|---|---| \| token | IERC20 | The token for which to retrieve the reserve | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The amount of reserves for the given token | #### `getAddLiquidityCalledFlag` ```solidity function getAddLiquidityCalledFlag(address pool) external view returns (bool); ``` This `VaultExtension` function retrieves the value of the flag used to detect and tax "round trip" transactions (adding and removing liquidity in the same pool). **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which to check the flag | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if addLiquidity has been called on this pool in the current transaction | ### Swaps #### `swap` ```solidity function swap( VaultSwapParams memory vaultSwapParams ) external returns (uint256 amountCalculatedRaw, uint256 amountInRaw, uint256 amountOutRaw); ``` This `Vault` function swaps tokens based on provided parameters. All parameters are given in raw token decimal encoding. **Parameters:** \| Name | Type | Description | \|---|---|---| \| params | VaultSwapParams | Parameters for the swap operation | [VaultSwapParams](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L277\:L295) is defined as: ```solidity /** * @notice Data passed into primary Vault `swap` operations. * @param kind Type of swap (Exact In or Exact Out) * @param pool The pool with the tokens being swapped * @param tokenIn The token entering the Vault (balance increases) * @param tokenOut The token leaving the Vault (balance decreases) * @param amountGivenRaw Amount specified for tokenIn or tokenOut (depending on the type of swap) * @param limitRaw Minimum or maximum value of the calculated amount (depending on the type of swap) * @param userData Additional (optional) user data */ struct VaultSwapParams { SwapKind kind; address pool; IERC20 tokenIn; IERC20 tokenOut; uint256 amountGivenRaw; uint256 limitRaw; bytes userData; } enum SwapKind { EXACT_IN, EXACT_OUT } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| amountCalculatedRaw | uint256 | Calculated swap amount | \| amountInRaw | uint256 | Amount of input tokens for the swap | \| amountOutRaw | uint256 | Amount of output tokens from the swap | ### Add Liquidity #### addLiquidity ```solidity function addLiquidity( AddLiquidityParams memory params ) external returns (uint256[] memory amountsIn, uint256 bptAmountOut, bytes memory returnData); ``` This `Vault` function adds liquidity to a pool. Caution should be exercised when adding liquidity because the Vault has the capability to transfer tokens from any user, given that it holds all allowances. It returns the actual amounts of input tokens, the output pool token amount, and optional data with an encoded response from the pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| params | AddLiquidityParams | Parameters for the add liquidity operation | [AddLiquidityParams](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L368-L375) is defined as: ```solidity /** * @notice Data for an add liquidity operation. * @param pool Address of the pool * @param to Address of user to mint to * @param maxAmountsIn Maximum amounts of input tokens * @param minBptAmountOut Minimum amount of output pool tokens * @param kind Add liquidity kind * @param userData Optional user data */ struct AddLiquidityParams { address pool; address to; uint256[] maxAmountsIn; uint256 minBptAmountOut; AddLiquidityKind kind; bytes userData; } enum AddLiquidityKind { PROPORTIONAL, UNBALANCED, SINGLE_TOKEN_EXACT_OUT, DONATION, CUSTOM } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| amountsIn | uint256\[] | Actual amounts of input tokens | \| bptAmountOut | uint256 | Output pool token amount | \| returnData | bytes | Arbitrary (optional) data with encoded response from the pool | ### Remove liquidity #### `removeLiquidity` ```solidity function removeLiquidity( RemoveLiquidityParams memory params ) external returns (uint256 bptAmountIn, uint256[] memory amountsOut, bytes memory returnData); ``` This `Vault` function removes liquidity from a pool. Trusted routers can burn pool tokens belonging to any user and require no prior approval from the user. Untrusted routers require prior approval from the user. This is the only function allowed to call `_queryModeBalanceIncrease` (and only in a query context). **Parameters:** \| Name | Type | Description | \|---|---|---| \| params | RemoveLiquidityParams | Parameters for the remove liquidity operation | [RemoveLiquidityParams](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/VaultTypes.sol#L397-L404) is defined as: ```solidity /** * @notice Data for an remove liquidity operation. * @param pool Address of the pool * @param from Address of user to burn from * @param maxBptAmountIn Maximum amount of input pool tokens * @param minAmountsOut Minimum amounts of output tokens * @param kind Remove liquidity kind * @param userData Optional user data */ struct RemoveLiquidityParams { address pool; address from; uint256 maxBptAmountIn; uint256[] minAmountsOut; RemoveLiquidityKind kind; bytes userData; } enum RemoveLiquidityKind { PROPORTIONAL, SINGLE_TOKEN_EXACT_IN, SINGLE_TOKEN_EXACT_OUT, CUSTOM } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| bptAmountIn | uint256 | Actual amount of BPT burnt | \| amountsOut | uint256\[] | Actual amounts of output tokens | \| returnData | bytes | Arbitrary (optional) data with encoded response from the pool | ### Pool information #### `getPoolTokenCountAndIndexOfToken` ```solidity function getPoolTokenCountAndIndexOfToken(address pool, IERC20 token) external view returns (uint256, uint256); ``` This `Vault` function gets the index of a token in a given pool. It reverts if the pool is not registered, or if the token does not belong to the pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool | \| token | IERC20 | Address of the token | **Returns:** \| Name | Type | Description | \|---|---|---| \| tokenCount | uint256 | Number of tokens in the pool | \| index | uint256 | Index corresponding to the given token in the pool's token list | #### `isPoolInitialized` ```solidity function isPoolInitialized(address pool) external view returns (bool); ``` This `VaultExtension` function checks whether a pool is initialized. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool to check | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if the pool is initialized, false otherwise | #### `getPoolTokens` ```solidity function getPoolTokens(address pool) external view returns (IERC20[] memory); ``` This `VaultExtension` function gets the tokens registered to a pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool | **Returns:** \| Name | Type | Description | \|---|---|---| \| tokens | IERC20\[] | List of tokens in the pool | #### `getPoolTokenRates` ```solidity function getPoolTokenRates(address pool) external view returns (uint256[] memory); ``` This `VaultExtension` function retrieves the scaling factors from a pool's rate providers. Tokens without rate providers will always return FixedPoint.ONE (1e18). **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256\[] | The rate scaling factors from the pool's rate providers | #### `getPoolData` ```solidity function getPoolData(address pool) external view returns (PoolData memory); ``` This `VaultExtension` function retrieves a PoolData structure, containing comprehensive information about the pool, including the PoolConfig, tokens, tokenInfo, balances, rates and scaling factors. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool | [PoolData](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L244-L252) is defined as: ```solidity /** * @notice Data structure used to represent the current pool state in memory * @param poolConfigBits Custom type to store the entire configuration of the pool. * @param tokens Pool tokens, sorted in token registration order * @param tokenInfo Configuration data for each token, sorted in token registration order * @param balancesRaw Token balances in native decimals * @param balancesLiveScaled18 Token balances after paying yield fees, applying decimal scaling and rates * @param tokenRates 18-decimal FP values for rate tokens (e.g., yield-bearing), or FP(1) for standard tokens * @param decimalScalingFactors Conversion factor used to adjust for token decimals for uniform precision in * calculations. It is 1e18 (FP 1) for 18-decimal tokens */ struct PoolData { PoolConfigBits poolConfigBits; IERC20[] tokens; TokenInfo[] tokenInfo; uint256[] balancesRaw; uint256[] balancesLiveScaled18; uint256[] tokenRates; uint256[] decimalScalingFactors; } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| | PoolData | A struct with data describing the current state of the pool | #### `getPoolTokenInfo` ```solidity function getPoolTokenInfo( address pool ) external view returns ( IERC20[] memory tokens, TokenInfo[] memory tokenInfo, uint256[] memory balancesRaw, uint256[] memory lastLiveBalances ); ``` This `VaultExtension` function gets the raw data for a pool: tokens, raw and last live balances. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool | [TokenInfo](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L227-L231) is defined as: ```solidity /** * @notice This data structure is stored in `_poolTokenInfo`, a nested mapping from pool -> (token -> TokenInfo). * @dev Since the token is already the key of the nested mapping, it would be redundant (and an extra SLOAD) to store * it again in the struct. When we construct PoolData, the tokens are separated into their own array. * * @param tokenType The token type (see the enum for supported types) * @param rateProvider The rate provider for a token (see further documentation above) * @param paysYieldFees Flag indicating whether yield fees should be charged on this token */ struct TokenInfo { TokenType tokenType; IRateProvider rateProvider; bool paysYieldFees; } enum TokenType { STANDARD, WITH_RATE } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| tokens | IERC20\[] | The pool tokens, sorted in registration order | \| tokenInfo | TokenInfo\[] | Token info, sorted in token registration order | \| balancesRaw | uint256\[] | Raw balances, sorted in token registration order | \| lastLiveBalances | uint256\[] | Last saved live balances, sorted in token registration order | #### `getCurrentLiveBalances` ```solidity function getCurrentLiveBalances(address pool) external view returns (uint256[] memory balancesLiveScaled18); ``` This `VaultExtension` function retrieves the current live balances: i.e., token balances after paying yield fees, applying decimal scaling and rates. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool | **Returns:** \| Name | Type | Description | \|---|---|---| \| balancesLiveScaled18 | uint256\[] | Current live balances, sorted in token registration order | #### `getPoolConfig` ```solidity function getPoolConfig(address pool) external view returns (PoolConfig memory); ``` This `VaultExtension` function gets the configuration parameters of a pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool | [PoolConfig](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L40-L51) is defined as: ```solidity /** * @notice Represents a pool's configuration (hooks configuration are separated in another struct). * @param liquidityManagement Flags related to adding/removing liquidity * @param staticSwapFeePercentage The pool's native swap fee * @param aggregateSwapFeePercentage The total swap fee charged, including protocol and pool creator components * @param aggregateYieldFeePercentage The total swap fee charged, including protocol and pool creator components * @param tokenDecimalDiffs Compressed storage of the token decimals of each pool token * @param pauseWindowEndTime Timestamp after which the pool cannot be paused * @param isPoolRegistered If true, the pool has been registered with the Vault * @param isPoolInitialized If true, the pool has been initialized with liquidity, and is available for trading * @param isPoolPaused If true, the pool has been paused (by governance or the pauseManager) * @param isPoolInRecoveryMode If true, the pool has been placed in recovery mode, enabling recovery mode withdrawals */ struct PoolConfig { LiquidityManagement liquidityManagement; uint256 staticSwapFeePercentage; uint256 aggregateSwapFeePercentage; uint256 aggregateYieldFeePercentage; uint40 tokenDecimalDiffs; uint32 pauseWindowEndTime; bool isPoolRegistered; bool isPoolInitialized; bool isPoolPaused; bool isPoolInRecoveryMode; } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| | PoolConfig | Pool configuration | #### `getHooksConfig` ```solidity function getHooksConfig(address pool) external view returns (HooksConfig memory); ``` This `VaultExtension` function gets the hooks configuration parameters of a pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool | [HooksConfig](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L73-L85) is defined as: ```solidity /// @notice Represents a hook contract configuration for a pool (HookFlags + hooksContract address). struct HooksConfig { bool enableHookAdjustedAmounts; bool shouldCallBeforeInitialize; bool shouldCallAfterInitialize; bool shouldCallComputeDynamicSwapFee; bool shouldCallBeforeSwap; bool shouldCallAfterSwap; bool shouldCallBeforeAddLiquidity; bool shouldCallAfterAddLiquidity; bool shouldCallBeforeRemoveLiquidity; bool shouldCallAfterRemoveLiquidity; address hooksContract; } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| | HooksConfig | Hooks configuration | #### `getBptRate` ```solidity function getBptRate(address pool) external view returns (uint256 rate); ``` This `VaultExtension` function gets the current bpt rate of a pool, by dividing the current invariant by the total supply of BPT. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool | **Returns:** \| Name | Type | Description | \|---|---|---| \| rate | uint256 | BPT rate | ### ERC4626 Buffers #### `erc4626BufferWrapOrUnwrap` ```solidity function erc4626BufferWrapOrUnwrap( BufferWrapOrUnwrapParams memory params ) external returns (uint256 amountCalculatedRaw, uint256 amountInRaw, uint256 amountOutRaw); ``` This `Vault` function wraps/unwraps tokens based on provided parameters, using the buffer of the wrapped token when it has enough liquidity to avoid external calls. All parameters are given in raw token decimal encoding. **Parameters:** \| Name | Type | Description | \|---|---|---| \| params | BufferWrapOrUnwrapParams | Parameters for the wrap/unwrap operation | **Returns:** \| Name | Type | Description | \|---|---|---| \| amountCalculatedRaw | uint256 | Calculated swap amount | \| amountInRaw | uint256 | Amount of input tokens for the swap | \| amountOutRaw | uint256 | Amount of output tokens from the swap | #### `areBuffersPaused` ```solidity function areBuffersPaused() external view returns (bool); ``` This `VaultAdmin` function indicates whether ERC4626 buffers are paused. When buffers are paused, all buffer operations (i.e., calls on the Router with `isBuffer` true) will revert. Pausing buffers is reversible. Note that ERC4626 buffers and the Vault have separate and independent pausing mechanisms. Pausing the Vault does not also pause buffers (though we anticipate they would likely be paused and unpaused together). Call `isVaultPaused` to check the pause state of the Vault. **Returns:** \| Name | Type | Description | \|---|---|---| \| buffersPaused | bool | True if ERC4626 buffers are paused | #### `pauseVaultBuffers` ```solidity function pauseVaultBuffers() external; ``` This `VaultAdmin` function pauses native vault buffers globally. When buffers are paused, it's not possible to add liquidity or wrap/unwrap tokens using Vault's `erc4626BufferWrapOrUnwrap` primitive. However, it's still possible to remove liquidity. Currently it's not possible to pause vault buffers individually. This is a permissioned call, and is reversible (see `unpauseVaultBuffers`). Note that the Vault has a separate and independent pausing mechanism. It is possible to pause the Vault (i.e. pool operations), without affecting buffers, and vice versa. #### `unpauseVaultBuffers` ```solidity function unpauseVaultBuffers() external; ``` This `VaultAdmin` function unpauses native vault buffers globally. When buffers are paused, it's not possible to add liquidity or wrap/unwrap tokens using Vault's `erc4626BufferWrapOrUnwrap` primitive. However, it's still possible to remove liquidity. This is a permissioned call. #### `initializeBuffer` ```solidity function initializeBuffer( IERC4626 wrappedToken, uint256 amountUnderlyingRaw, uint256 amountWrappedRaw, uint256 minIssuedShares, address sharesOwner ) external returns (uint256 issuedShares); ``` This `VaultAdmin` function adds liquidity to an internal ERC4626 buffer in the Vault for the first time. And operations involving the buffer will revert until it is initialized. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| amountUnderlyingRaw | uint256 | Amount of underlying tokens that will be deposited into the buffer | \| amountWrappedRaw | uint256 | Amount of wrapped tokens that will be deposited into the buffer | \| minIssuedShares | uint256 | Minimum amount of shares to receive from the buffer, expressed in underlying token native decimals | \| sharesOwner | address | Address of the contract that will own the liquidity. Only this contract will be able to remove liquidity from the buffer | #### `addLiquidityToBuffer` ```solidity function addLiquidityToBuffer( IERC4626 wrappedToken, uint256 maxAmountUnderlyingInRaw, uint256 maxAmountWrappedInRaw, uint256 exactSharesToIssue, address sharesOwner ) external returns (uint256 amountUnderlyingRaw, uint256 amountWrappedRaw); ``` This `VaultAdmin` function adds liquidity proportionally to an internal ERC4626 buffer in the Vault. Reverts if the buffer has not been initialized. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| maxAmountUnderlyingInRaw | uint256 | Amount of underlying tokens that will be deposited into the buffer | \| maxAmountWrappedInRaw | uint256 | Amount of wrapped tokens that will be deposited into the buffer | \| exactSharesToIssue | uint256 | The value in underlying tokens that `sharesOwner` wants to add to the buffer in underlying token decimals | \| sharesOwner | address | Address of the contract that will own the liquidity. Only this contract will be able to remove liquidity from the buffer | #### `removeLiquidityFromBuffer` ```solidity function removeLiquidityFromBuffer( IERC4626 wrappedToken, uint256 sharesToRemove, uint256 minAmountUnderlyingOutRaw, uint256 minAmountWrappedOutRaw ) external returns (uint256 removedUnderlyingBalanceRaw, uint256 removedWrappedBalanceRaw); ``` This `VaultAdmin` function removes liquidity from an internal ERC4626 buffer in the Vault. Only proportional exits are supported. Note that the `sharesOwner` here is the msg.sender; unlike initialize, add, and other buffer operations, the entrypoint for this function is the Vault itself. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | \| sharesToRemove | uint256 | Amount of shares to remove from the buffer. Cannot be greater than sharesOwner total shares | \| minAmountUnderlyingOutRaw | uint256 | Minimum amount of underlying tokens to receive from the buffer. It is expressed in underlying token native decimals | \| minAmountWrappedOutRaw | uint256 | Minimum amount of wrapped tokens to receive from the buffer. It is expressed in wrapped token native decimals | #### `getBufferOwnerShares` ```solidity function getBufferOwnerShares( IERC4626 wrappedToken, address liquidityOwner ) external view returns (uint256 ownerShares); ``` This `VaultAdmin` function returns the shares (internal buffer BPT) of a liquidity owner: a user that deposited assets in the buffer. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC20 | Address of the wrapped token that implements IERC4626 | \| liquidityOwner | address | Address of the user that owns liquidity in the wrapped token's buffer | **Returns:** \| Name | Type | Description | \|---|---|---| \| ownerShares | uint256 | Amount of shares allocated to the liquidity owner, in native underlying token decimals | #### `getBufferAsset` ```solidity function getBufferAsset( IERC4626 wrappedToken ) external view returns (address underlyingToken); ``` This `VaultAdmin` function returns the shares (internal buffer BPT) of a liquidity owner: a user that deposited assets in the buffer. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | **Returns:** \| Name | Type | Description | \|---|---|---| \| underlyingToken | address | Address of the underlying token for the buffer | #### `getBufferTotalShares` ```solidity function getBufferTotalShares(IERC4626 wrappedToken) external view returns (uint256 bufferShares); ``` This `VaultAdmin` function returns the supply shares (internal buffer BPT) of the ERC4626 buffer. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | #### `getBufferBalance` ```solidity function getBufferBalance( IERC4626 wrappedToken ) external view returns (uint256 underlyingBalanceRaw, uint256 wrappedBalanceRaw); ``` This `VaultAdmin` function returns the amount of underlying and wrapped tokens deposited in the internal buffer of the vault. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | **Returns:** \| Name | Type | Description | \|---|---|---| \| underlyingBalanceRaw | uint256 | Amount of underlying tokens deposited into the buffer, in native token decimals | \| wrappedBalanceRaw | uint256 | Amount of wrapped tokens deposited into the buffer, in native token decimals | ### Authentication #### `getAuthorizer` ```solidity function getAuthorizer() external view returns (IAuthorizer); ``` This `VaultExtension` function returns the Vault's Authorizer. **Returns:** \| Name | Type | Description | \|---|---|---| \| | IAuthorizer | Address of the authorizer | #### `setAuthorizer` ```solidity function setAuthorizer(IAuthorizer newAuthorizer) external; ``` This `VaultAdmin` function sets a new Authorizer for the Vault. This is a permissioned call. It emits an `AuthorizerChanged` event. **Parameters:** \| Name | Type | Description | \|---|---|---| \| newAuthorizer | IAuthorizer | The new Authorizer for the Vault | ### Pool registration #### `registerPool` ```solidity function registerPool( address pool, TokenConfig[] memory tokenConfig, uint256 swapFeePercentage, uint32 pauseWindowEndTime, bool protocolFeeExempt, PoolRoleAccounts calldata roleAccounts, address poolHooksContract, LiquidityManagement calldata liquidityManagement ) external; ``` This `VaultExtension` function registers a pool, associating it with its factory and the tokens it manages. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool being registered | \| tokenConfig | TokenConfig\[] | An array of descriptors for the tokens the pool will manage | \| swapFeePercentage | uint256 | The initial static swap fee percentage of the pool | \| pauseWindowEndTime | uint32 | The timestamp after which it is no longer possible to pause the pool | \| protocolFeeExempt | bool | If true, the pool's initial aggregate fees will be set to 0 | \| roleAccounts | PoolRoleAccounts | Addresses the Vault will allow to change certain pool settings | \| poolHooksContract | address | Contract that implements the hooks for the pool | \| liquidityManagement | LiquidityManagement | Liquidity management flags with implemented methods | [TokenConfig](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L211-L216), [PoolRoleAccounts](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L119-L123) and [LiquidityManagement](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L17-L22) is defined as: ```solidity /** * @notice Encapsulate the data required for the Vault to support a token of the given type. * @dev For STANDARD tokens, the rate provider address must be 0, and paysYieldFees must be false. All WITH_RATE tokens * need a rate provider, and may or may not be yield-bearing. * * At registration time, it is useful to include the token address along with the token parameters in the structure * passed to `registerPool`, as the alternative would be parallel arrays, which would be error prone and require * validation checks. `TokenConfig` is only used for registration, and is never put into storage (see `TokenInfo`). * * @param token The token address * @param tokenType The token type (see the enum for supported types) * @param rateProvider The rate provider for a token (see further documentation above) * @param paysYieldFees Flag indicating whether yield fees should be charged on this token */ struct TokenConfig { IERC20 token; TokenType tokenType; IRateProvider rateProvider; bool paysYieldFees; } /** * @notice Represents the accounts holding certain roles for a given pool. This is passed in on pool registration. * @param pauseManager Account empowered to pause/unpause the pool (note that governance can always pause a pool) * @param swapFeeManager Account empowered to set static swap fees for a pool (or 0 to delegate to governance) * @param poolCreator Account empowered to set the pool creator fee (or 0 if all fees go to the protocol and LPs) */ struct PoolRoleAccounts { address pauseManager; address swapFeeManager; address poolCreator; } /** * @notice Represents a pool's liquidity management configuration. * @param disableUnbalancedLiquidity If set, liquidity can only be added or removed proportionally * @param enableAddLiquidityCustom If set, the pool has implemented `onAddLiquidityCustom` * @param enableRemoveLiquidityCustom If set, the pool has implemented `onRemoveLiquidityCustom` * @param enableDonation If set, the pool will not revert if liquidity is added with AddLiquidityKind.DONATION */ struct LiquidityManagement { bool disableUnbalancedLiquidity; bool enableAddLiquidityCustom; bool enableRemoveLiquidityCustom; bool enableDonation; } ``` #### `isPoolRegistered` ```solidity function isPoolRegistered(address pool) external view returns (bool); ``` This `VaultExtension` function checks whether a pool is registered. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool to check | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if the pool is registered, false otherwise | #### `initialize` ```solidity function initialize( address pool, address to, IERC20[] memory tokens, uint256[] memory exactAmountsIn, uint256 minBptAmountOut, bytes memory userData ) external returns (uint256 bptAmountOut); ``` This `VaultExtension` function initializes a registered pool by adding liquidity; mints BPT tokens for the first time in exchange. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool to initialize | \| to | address | Address that will receive the output BPT | \| tokens | IERC20\[] | Tokens used to seed the pool (must match the registered tokens) | \| exactAmountsIn | uint256\[] | Exact amounts of input tokens | \| minBptAmountOut | uint256 | Minimum amount of output pool tokens | \| userData | bytes | Additional (optional) data required for adding initial liquidity | **Returns:** \| Name | Type | Description | \|---|---|---| \| bptAmountOut | uint256 | Output pool token amount | ### Balancer Pool tokens #### `totalSupply` ```solidity function totalSupply(address token) external view returns (uint256); ``` This `VaultExtension` function gets the total supply of a given ERC20 token. **Parameters:** \| Name | Type | Description | \|---|---|---| \| token | address | Token's address | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | Total supply of the token | #### `balanceOf` ```solidity function balanceOf(address token, address account) external view returns (uint256); ``` This `VaultExtension` function gets the balance of an account for a given ERC20 token. **Parameters:** \| Name | Type | Description | \|---|---|---| \| token | address | Token's address | \| account | address | Account's address | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | Balance of the account for the token | #### `allowance` ```solidity function allowance(address token, address owner, address spender) external view returns (uint256); ``` This `VaultExtension` function gets the allowance of a spender for a given ERC20 token and owner. **Parameters:** \| Name | Type | Description | \|---|---|---| \| token | address | Token's address | \| owner | address | Owner's address | \| spender | address | Spender's address | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | Amount of tokens the spender is allowed to spend | #### `approve` ```solidity function approve(address owner, address spender, uint256 amount) external returns (bool); ``` This `VaultExtension` function approves a spender to spend pool tokens on behalf of sender. **Parameters:** \| Name | Type | Description | \|---|---|---| \| owner | address | Owner's address | \| spender | address | Spender's address | \| amount | uint256 | Amount of tokens to approve | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if successful, false otherwise | #### `transfer` ```solidity function transfer(address owner, address to, uint256 amount) external returns (bool); ``` This `Vault` function transfers pool token from owner to a recipient. **Parameters:** \| Name | Type | Description | \|---|---|---| \| owner | address | Owner's address | \| to | address | Recipient's address | \| amount | uint256 | Amount of tokens to transfer | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if successful, false otherwise | #### `transferFrom` ```solidity function transferFrom(address spender, address from, address to, uint256 amount) external returns (bool); ``` This `Vault` function transfers pool token from a sender to a recipient using an allowance. **Parameters:** \| Name | Type | Description | \|---|---|---| \| spender | address | Address allowed to perform the transfer | \| from | address | Sender's address | \| to | address | Recipient's address | \| amount | uint256 | Amount of tokens to transfer | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if successful, false otherwise | ### pool pausing #### `isPoolPaused` ```solidity function isPoolPaused(address pool) external view returns (bool); ``` This `VaultExtension` function indicates whether a pool is paused. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool to be checked | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if the pool is paused | #### `getPoolPausedState` ```solidity function getPoolPausedState(address pool) external view returns (bool, uint32, uint32, address); ``` This `VaultExtension` function returns the paused status, and end times of the Pool's pause window and buffer period. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool whose data is requested | **Returns:** \| Name | Type | Description | \|---|---|---| \| paused | bool | True if the Pool is paused | \| poolPauseWindowEndTime | uint32 | The timestamp of the end of the Pool's pause window | \| poolBufferPeriodEndTime | uint32 | The timestamp after which the Pool unpauses itself (if paused) | \| pauseManager | address | The pause manager, or the zero address | ### ERC4626 Buffers #### `isERC4626BufferInitialized` ```solidity function isERC4626BufferInitialized(IERC4626 wrappedToken) external view returns (bool isBufferInitialized); ``` This `VaultExtension` function checks whether `initializeBuffer` has been called on the given `wrappedToken`. Buffers must be initialized before use. **Parameters:** \| Name | Type | Description | \|---|---|---| \| wrappedToken | IERC4626 | Address of the wrapped token that implements IERC4626 | **Returns:** \| Name | Type | Description | \|---|---|---| \| isBufferInitialized | bool | True if the ERC4626 buffer is initialized | ### Fees #### `getAggregateSwapFeeAmount` ```solidity function getAggregateSwapFeeAmount(address pool, IERC20 token) external view returns (uint256); ``` This `VaultExtension` function returns the accumulated swap fees (including aggregate fees) in `token` collected by the pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool for which aggregate fees have been collected | \| token | IERC20 | The address of the token in which fees have been accumulated | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The total amount of fees accumulated in the specified token | #### `getAggregateYieldFeeAmount` ```solidity function getAggregateYieldFeeAmount(address pool, IERC20 token) external view returns (uint256); ``` This `VaultExtension` function returns the accumulated yield fees (including aggregate fees) in `token` collected by the pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool for which aggregate fees have been collected | \| token | IERC20 | The address of the token in which fees have been accumulated | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The total amount of fees accumulated in the specified token | #### `getStaticSwapFeePercentage` ```solidity function getStaticSwapFeePercentage(address pool) external view returns (uint256); ``` This `VaultExtension` function fetches the static swap fee percentage for a given pool. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool whose static swap fee percentage is being queried | **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The current static swap fee percentage for the specified pool | #### `getPoolRoleAccounts` ```solidity function getPoolRoleAccounts(address pool) external view returns (PoolRoleAccounts memory); ``` This `VaultExtension` function fetches the role accounts for a given pool (pause manager, swap manager, pool creator). **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool whose roles are being queried | **Returns:** \| Name | Type | Description | \|---|---|---| \| roleAccounts | PoolRoleAccounts | A struct containing the role accounts for the pool (or 0 if unassigned) | #### `computeDynamicSwapFeePercentage` ```solidity function computeDynamicSwapFee( address pool, PoolSwapParams memory swapParams ) external view returns (uint256); ``` This `VaultExtension` function queries the current dynamic swap fee of a pool, given a set of swap parameters. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool | \| swapParams | PoolSwapParams | The swap parameters used to compute the fee | The [PoolSwapParams](https://github.com/balancer/balancer-v3-monorepo/blob/2d6ae6a3d0082cafcdb9a963421bcd31858a106c/pkg/interfaces/contracts/vault/VaultTypes.sol#L307-L315) is defined as: ```solidity /** * @notice Data for a swap operation, used by contracts implementing `IBasePool`. * @param kind Type of swap (exact in or exact out) * @param amountGivenScaled18 Amount given based on kind of the swap (e.g., tokenIn for EXACT_IN) * @param balancesScaled18 Current pool balances * @param indexIn Index of tokenIn * @param indexOut Index of tokenOut * @param router The address (usually a router contract) that initiated a swap operation on the Vault * @param userData Additional (optional) data required for the swap */ struct PoolSwapParams { SwapKind kind; uint256 amountGivenScaled18; uint256[] balancesScaled18; uint256 indexIn; uint256 indexOut; address router; bytes userData; } ``` **Returns:** \| Name | Type | Description | \|---|---|---| \| dynamicSwapFeePercentage | uint256 | The dynamic swap fee percentage | #### `getProtocolFeeController` ```solidity function getProtocolFeeController() external view returns (IProtocolFeeController); ``` This `VaultExtension` function returns the Protocol Fee Controller address. **Returns:** \| Name | Type | Description | \|---|---|---| \| | IProtocolFeeController | Address of the ProtocolFeeController | #### `setStaticSwapFeePercentage` ```solidity function setStaticSwapFeePercentage(address pool, uint256 swapFeePercentage) external; ``` This `VaultAdmin` function assigns a new static swap fee percentage to the specified pool. This is a permissioned function, disabled if the pool is paused. The swap fee percentage must be within the bounds specified by the pool's implementation of `ISwapFeePercentageBounds`. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool for which the static swap fee will be changed | \| swapFeePercentage | uint256 | The new swap fee percentage to apply to the pool | #### `collectAggregateFees` ```solidity function collectAggregateFees(address pool) public returns (uint256[] memory totalSwapFees, uint256[] memory totalYieldFees); ``` This function collects accumulated aggregate swap and yield fees for the specified pool. It can only be called from the `ProtocolFeeController`, which unlocks the Vault, acting as a Router. In the Vault, it clears the `aggregateFeeAmounts` storage, supplying credit for each amount which must be settled at the end of the fee controller action. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool on which all aggregate fees should be collected | #### `updateAggregateSwapFeePercentage` ```solidity function updateAggregateSwapFeePercentage(address pool, uint256 newAggregateSwapFeePercentage) external; ``` This `VaultAdmin` function updates an aggregate swap fee percentage. Can only be called by the current protocol fee controller. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool whose fee will be updated | \| newAggregateSwapFeePercentage | uint256 | The new aggregate swap fee percentage | #### `updateAggregateYieldFeePercentage` ```solidity function updateAggregateYieldFeePercentage(address pool, uint256 newAggregateYieldFeePercentage) external; ``` This `VaultAdmin` function updates an aggregate yield fee percentage. Can only be called by the current protocol fee controller. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool whose fee will be updated | \| newAggregateYieldFeePercentage | uint256 | The new aggregate yield fee percentage | #### `setProtocolFeeController` ```solidity function setProtocolFeeController(IProtocolFeeController newProtocolFeeController) external; ``` This `VaultAdmin` function sets a new Protocol Fee Controller for the Vault. This is a permissioned call. **Parameters:** \| Name | Type | Description | \|---|---|---| \| newProtocolFeeController | IProtocolFeeController | The new Protocol Fee Controller for the Vault | ### Recovery mode #### `isPoolInRecoveryMode` ```solidity function isPoolInRecoveryMode(address pool) external view returns (bool); ``` This `VaultExtension` function checks whether a pool is in recovery mode. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool to check | **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if the pool is initialized, false otherwise | #### `removeLiquidityRecovery` ```solidity function removeLiquidityRecovery( address pool, address from, uint256 exactBptAmountIn, uint256[] memory minAmountsOut ) external returns (uint256[] memory amountsOut); ``` This `VaultExtension` function removes liquidity from a pool specifying exact pool tokens in, with proportional token amounts out. The request is implemented by the Vault without any interaction with the pool, ensuring that it works the same for all pools, and cannot be disabled by a new pool type. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | Address of the pool | \| from | address | Address of user to burn pool tokens from | \| exactBptAmountIn | uint256 | Input pool token amount | \| minAmountsOut | uint256\[] | Minimum amounts of tokens to be received, sorted in token registration order | **Returns:** \| Name | Type | Description | \|---|---|---| \| amountsOut | uint256\[] | Actual calculated amounts of output tokens, sorted in token registration order | #### `enableRecoveryMode` ```solidity function enableRecoveryMode(address pool) external; ``` This `VaultAdmin` function enables recovery mode for a pool. This is a permissioned function, but becomes permissionless if the Vault or pool is paused. Hint: this function will not show in some block explorers in the `Vault` page because it's defined in the `VaultExtension`. But it still can be easily called via a block explorer using the `VaultExplorer` contract. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool | #### `disableRecoveryMode` ```solidity function disableRecoveryMode(address pool) external; ``` This `VaultAdmin` function disables recovery mode for a pool. This is a permissioned function. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The pool | ### Queries #### `quote` ```solidity function quote(bytes calldata data) external returns (bytes memory result); ``` This `VaultExtension` function performs a callback on `msg.sender` with arguments provided in `data`. It is used to query a set of operations on the Vault. Only off-chain `eth_call` are allowed, anything else will revert. Also note that it is non-payable, as the Vault does not allow ETH. **Parameters:** \| Name | Type | Description | \|---|---|---| \| data | bytes | Contains function signature and args to be passed to the `msg.sender` | **Returns:** \| Name | Type | Description | \|---|---|---| \| result | bytes | Resulting data from the call | #### `quoteAndRevert` ```solidity function quoteAndRevert(bytes calldata data) external; ``` This `VaultExtension` function performs a callback on `msg.sender` with arguments provided in `data`. It is used to query a set of operations on the Vault. Only off-chain `eth_call` are allowed, anything else will revert. This call always reverts, returning the result in the revert reason. Also note that it is non-payable, as the Vault does not allow ETH. **Parameters:** \| Name | Type | Description | \|---|---|---| \| data | bytes | Contains function signature and args to be passed to the `msg.sender` | #### `isQueryDisabled` ```solidity function isQueryDisabled() external view returns (bool); ``` This `VaultExtension` function checks if the queries reversibly disabled on the Vault. **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | If true, then queries are disabled | #### `isQueryDisabledPermanently` ```solidity function isQueryDisabledPermanently() external view returns (bool); ``` This `VaultExtension` function checks if the queries are permanently disabled on the Vault. **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | If true, then queries are disabled | #### `emitAuxiliaryEvent` ```solidity function emitAuxiliaryEvent(string calldata eventKey, bytes calldata eventData) external; ``` This `VaultExtension` function checks if the queries are permanently disabled on the Vault. **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | If true, then queries are disabled | #### `disableQuery` ```solidity function disableQuery() external; ``` This `VaultAdmin` function reversibly disables query functionality on the Vault. It can only be called by governance. #### `disableQueryPermanently` ```solidity function disableQueryPermanently() external; ``` This `VaultAdmin` function permanently disables query functionality on the Vault. It can only be called by governance. #### `enableQuery` ```solidity function enableQuery() external; ``` This `VaultAdmin` function re-enables reversibly disabled query functionality on the Vault. It can only be called by governance. ### Constants #### `getPauseWindowEndTime` ```solidity function getPauseWindowEndTime() external view returns (uint32); ``` This `VaultAdmin` function returns Vault's pause window end time. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint32 | The end time of the Vault's pause window | #### `getBufferPeriodDuration` ```solidity function getBufferPeriodDuration() external view returns (uint32); ``` This `VaultAdmin` function returns Vault's buffer period duration. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint32 | The duration of the Vault's buffer period | #### `getBufferPeriodEndTime` ```solidity function getBufferPeriodEndTime() external view returns (uint32); ``` This `VaultAdmin` function returns Vault's buffer period end time. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint32 | The end time of the Vault's buffer period | #### `getMinimumPoolTokens` ```solidity function getMinimumPoolTokens() external pure returns (uint256); ``` This `VaultAdmin` function gets the minimum number of tokens in a pool. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The minimum token count of a pool | #### `getMaximumPoolTokens` ```solidity function getMaximumPoolTokens() external pure returns (uint256); ``` This `VaultAdmin` function gets the maximum number of tokens in a pool. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The maximum token count of a pool | #### `getPoolMinimumTotalSupply` ```solidity function getPoolMinimumTotalSupply() external pure returns (uint256); ``` This `VaultAdmin` function gets the minimum total supply of pool tokens (BPT) for an initialized pool. This prevents pools from being completely drained. When the pool is initialized, this minimum amount of BPT is minted to the zero address. This is an 18-decimal floating point number; BPT are always 18 decimals. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The minimum total supply a pool can have after initialization | #### `getBufferMinimumTotalSupply` ```solidity function getBufferMinimumTotalSupply() external pure returns (uint256); ``` This `VaultAdmin` function gets the minimum total supply of an ERC4626 wrapped token buffer in the Vault. This prevents buffers from being completely drained. When the buffer is initialized, this minimum number of shares is added to the shares resulting from the initial deposit. Buffer total supply accounting is internal to the Vault, as buffers are not tokenized. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The minimum total supply a buffer can have after initialization | #### `getMinimumTradeAmount` ```solidity function getMinimumTradeAmount() external view returns (uint256); ``` This `VaultAdmin` function gets the minimum trade amount in a pool operation. This limit is applied to the 18-decimal "upscaled" amount in any operation (swap, add/remove liquidity). **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The minimum trade amount as an 18-decimal floating point number | #### `getMinimumWrapAmount` ```solidity function getMinimumWrapAmount() external view returns (uint256); ``` This `VaultAdmin` function gets the minimum wrap amount in a buffer operation. This limit is applied to the wrap operation amount, in native underlying token decimals. **Returns:** \| Name | Type | Description | \|---|---|---| \| | uint256 | The minimum wrap amount in native underlying token decimals | #### `vault` ```solidity function vault() external view returns (IVault); ``` This function (defined on both `VaultExtension` and `VaultAdmin`) returns the main Vault address. **Returns:** \| Name | Type | Description | \|---|---|---| \| | IVault | The main Vault address | ### Vault pausing #### `isVaultPaused` ```solidity function isVaultPaused() external view returns (bool); ``` This `VaultAdmin` function indicates whether the Vault is paused. Note that ERC4626 buffers and the Vault have separate and independent pausing mechanisms. Pausing the Vault does not also pause buffers (though we anticipate they would likely be paused and unpaused together). Call `areBuffersPaused` to check the pause state of the buffers. **Returns:** \| Name | Type | Description | \|---|---|---| \| | bool | True if the Vault is paused | #### `getVaultPausedState` ```solidity function getVaultPausedState() external view returns (bool, uint32, uint32); ``` This `VaultAdmin` function returns the paused status, and end times of the Vault's pause window and buffer period. **Returns:** \| Name | Type | Description | \|---|---|---| \| paused | bool | True if the Vault is paused | \| vaultPauseWindowEndTime | uint32 | The timestamp of the end of the Vault's pause window | \| vaultBufferPeriodEndTime | uint32 | The timestamp of the end of the Vault's buffer period | #### `pauseVault` ```solidity function pauseVault() external; ``` This `VaultAdmin` function pauses the Vault: an emergency action which disables all operational state-changing functions on pools. This is a permissioned function that will only work during the Pause Window set during deployment. Note that ERC4626 buffer operations have an independent pause mechanism, which is not affected by pausing the Vault. Custom routers could still wrap/unwrap using buffers while the Vault is paused, unless buffers are also paused (with `pauseVaultBuffers`). #### `unpauseVault` ```solidity function unpauseVault() external; ``` This `VaultAdmin` function reverses a `pause` operation, and restores Vault pool operations to normal functionality. This is a permissioned function that will only work on a paused Vault within the Buffer Period set during deployment. Note that the Vault will automatically unpause after the Buffer Period expires. And as noted above, ERC4626 buffers and Vault operations on pools are independent. Unpausing the Vault does not reverse `pauseVaultBuffers`. If buffers were also paused, they will remain in that state until explicitly unpaused. ### Pool pausing #### `pausePool` ```solidity function pausePool(address pool) external; ``` This `VaultAdmin` function pauses the Pool: an emergency action which disables all pool functions. This is a permissioned function that will only work during the Pause Window set during pool factory deployment. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool | #### `unpausePool` ```solidity function unpausePool(address pool) external; ``` This `VaultAdmin` function reverses a `pause` operation, and restores the Pool to normal functionality. This is a permissioned function that will only work on a paused Pool within the Buffer Period set during deployment. Note that the Pool will automatically unpause after the Buffer Period expires. **Parameters:** \| Name | Type | Description | \|---|---|---| \| pool | address | The address of the pool | ### Miscellaneous #### `getVaultExtension` ```solidity function getVaultExtension() external view returns (address); ``` This `Vault` function returns the Vault Extension address. **Returns:** \| Name | Type | Description | \|---|---|---| \| | address | Address of the VaultExtension | #### `getVaultAdmin` ```solidity function getVaultAdmin() external view returns (address); ``` This `VaultExtension` function returns the Vault Admin contract address. **Returns:** \| Name | Type | Description | \|---|---|---| \| | address | The address of the Vault Admin contract | ## Vault Constants (defined in code) ### Pool Minimum Total Supply `_POOL_MINIMUM_TOTAL_SUPPLY` is set to 1e6, and corresponds to the "MINIMUM\_BPT" in v2. It is a system guardrail designed to 1) prevent a single LP from owning the entire pool; and 2) prevent pools from being completely drained (i.e., it's a more general and principled alternative to the minimum balances of v1) BPT (Balancer Pool Tokens) are always 18-decimals, so this corresponds directly to an amount of Wei. The system burns this amount on initialization, such that the initializer receives slightly less than the initial total supply. It is also checked on remove liquidity, ensuring that the total supply never drops below this minimum. ### Buffer Minimum Total Supply `_BUFFER_MINIMUM_TOTAL_SUPPLY` is set to 1e4, and corresponds to a value in native underlying token decimals. Unlike with regular pools, buffer liquidity is not tokenized, but tracked internally. Nevertheless, we have a buffer guardrail analogous to the Pool Minimum Total Supply, with the same purpose and implementation. The system assigns this amount of buffer shares to address 0 (equivalent to "burning" them), and credits the initializer with slightly less. As with regular pools, the system will revert on liquidity removal if the resulting total shares would drop below this value. For reference, this minimum value would be 1 cent for USDC (which has 6 decimals). ### Minimum/Maximum Tokens v3 supports the same token count as v2. Pools must have between 2 (`MIN_TOKENS`) and 8 (`MAX_TOKENS`) tokens. Note that this is a constraint on all pools imposed by the Vault, but individual pool types might be more restrictive. For instance, Gyro 2CLP pools must be 2-token, and Stable pools have a lower maximum of 5 tokens. ### Maximum Token Decimals As with v2, v3 supports up to 18 decimals (`_MAX_TOKEN_DECIMALS`). (Tokens must also implement `IERC20Metadata.decimals`.) v3 reverts with the specific custom error `InvalidTokenDecimals` if this value is exceeded. (v2 reverts with a general numeric error.) ### Maximum Pause Window Duration `_MAX_PAUSE_WINDOW_DURATION` is set to 4 years, so the deployed value is equal to the maximum. Note that this only applies to the Vault. Pools may have longer pause windows, up to the maximum 32-bit timestamp. ### Maximum Buffer Period Duration `_MAX_BUFFER_PERIOD_DURATION` is set to 180 days (6 months), so the deployed value is equal to the maximum. This does apply to pools, since they use the Vault's buffer period. ## Vault Immutables (set on deployment) ### Pause window duration This was set to 4 years. The Vault can be reversibly paused (i.e., all state-changing operations blocked, except recovery mode withdrawals) at any time during the pause window. The idea is to enable fast action if we *suspect* something is going on. We can pause for safety, investigate the issue, and unpause at leisure if it turns out to be a false alarm. While it's certainly important for security to be able to pause the Vault, it must also be non-custodial and permissionless: that is a core feature of Balancer, and just as important. If governance could pause the Vault forever, it would undermine this principle, as we can't know how governance might evolve over long time periods, and can't be sure it will never be compromised in some way. Accordingly, the pause window is not perpetual. After it expires, the Vault can no longer be paused, and will remain permissionless forever. In v2, we thought of this as the "burn in" period, during which we could pause the Vault in case we discovered a critical vulnerability after launch. It seems hopelessly naive in retrospect, but we thought 3 months would be sufficient: surely any serious vulnerabilities would be found within three months! One of the big lessons from v2 was that vulnerabilities can be found *much* later than three months after launch (for v2, it took over two years). Based on that, we set the pause window to equal the maximum expected life of v3. We also strengthened the non-custodial guarantees in v3, by building Recovery Mode (can't fail proportional withdrawal) into the Vault, so that it is supported for all current and future pools. (In v2, it was done at the pool level, and only added in later versions, so a new pool type could implement it differently, or opt out entirely.) Not only that, Recovery Mode becomes permissionless if the Vault or Pool is paused, so that funds can never be locked by governance action (e.g., if a compromised governance were to pause the Vault but *not* enable Recovery Mode; on v2, this would lock funds). `_vaultPauseWindowEndTime` and `_vaultBufferPeriodEndTime` are set based on the pause window and buffer period; the pause window duration itself isn't stored directly. ### Buffer period duration `_vaultBufferPeriodDuration` was set to 6 months (twice the v2 value of 3 months). What if a vulnerability is found one day before the pause window expires? We could pause the Vault on the last day - but we'd only have one day to fix the issue before the Vault became permanently unpaused. This is the purpose of the Buffer Period. If the Vault is in the paused state when the window expires, it will *remain* paused for this additional buffer period, to allow enough time to investigate and correct whatever led to the pause. The Vault can still be unpaused at any point (and will unpause itself when the buffer expires), but it can no longer be *paused*, since the primary window has expired. There was no particular reason for setting this to 6 months; it was just thought that given the longer pause window, the buffer period should also be longer: and it does make sense. We have had issues in the past where the problem is an interaction between the Vault and another protocol (e.g., the Synthetix double-entry point vulnerability). If we need to wait for another protocol to change something, that could easily be a lengthy process. ### Minimum trade amount `_MINIMUM_TRADE_AMOUNT` was set to 1e6. Along with minimum pool fees and other similar measures, it is a "guardrail" - an additional safeguard meant to guard against "unknown unknowns." We know that many attacks involve exploiting rounding errors. A common pattern is for an attacker to first push a pool to its limits (e.g., greatly unbalance the liquidity, possibly using flash loans), then makes repeated transactions that exploit rounding, which typically is only possible with very small trade amounts. In practice, there is really no valid use case for trading tiny values. It obviously makes no sense to buy $0.00001 of ETH with USDC, as the gas costs would swamp any profits. If someone is trying to do this, either 1) they are using Etherscan and forgot about token decimals; or 2) it's some kind of attack. Accordingly, we simply disallow it. Note that this is a "scaled" value - a number of Wei *after* token decimal and rate scaling, and it applies to either side of a swap. It also applies to adding/removing liquidity (with the caveat that for single or exact token operations, 0 is allowed for tokens that aren't participating). ### Minimum wrap amount `_MINIMUM_WRAP_AMOUNT` was set to 1e4. This is another guardrail, similar to the minimum trade amount, but for ERC4626 buffers. Note that this is an "unscaled" value, corresponding to an amount in underlying token decimals. It prohibits any wrap or unwrap operation that yields less than this amount, for the same reasons noted above. Buffer operations are essentially internal swaps, and are subject to this analogous guardrail. This corresponds to 1 cent of USDC (as USDC has 6 decimals), or $10 of BTC at current prices (\~ 100k). ## Protocol Fee Controller Constants ### Maximum Protocol Swap Fee Percentage `MAX_PROTOCOL_SWAP_FEE_PERCENTAGE` was set to 50%, same as v2. This protects users and pool creators from governance risk; protocol fees can never be set above this value. Also, protocol fees are taken from the swap fee, so it's also limited by the swap fee. It is also possible for new pools to opt out of protocol fees entirely, at least initially. ### Maximum Protocol Yield Fee Percentage `MAX_PROTOCOL_YIELD_FEE_PERCENTAGE` was set to 50%. Since v3 is a yield-bearing token hub, it also has a maximum yield fee percentage, set to the same value as the swap fee. It is possible for new pools to opt out of protocol fees entirely, at least initially. ### Maximum Pool Creator Fee Percentage `MAX_CREATOR_FEE_PERCENTAGE` was set to 99.999%. This limit arises from the math (particularly ExactOut swaps), where a 100% fee would cause the swap to revert. In practice, final pool creator fees are expected to be much less than this. However, there are use cases (e.g., MEV reduction) where very high pool creator fees are appropriate (as they are promptly returned to LPs). ## Weighted Pool Constants ### Minimum Swap Fee Percentage `_MIN_SWAP_FEE_PERCENTAGE` is set to 0.001%. The minimum swap fee is a system guardrail designed to ensure that all operations are "lossy" (i.e., that there is no possible operation that can receive more tokens than it deposits). The system is designed to round correctly in all cases, and adjust the final results as necessary to fully compensate for any possible precision loss (e.g., in the invariant calculation, which uses the `pow` function and has a relatively wide margin of error). This means the system should work properly and prevent any leakage of value even with zero fees. Nevertheless, this guardrail is applied as an extra measure to ensure any attack is unprofitable. The value is higher than v2's 0.0001%, and was derived from rigorous analysis of Weighted Math and extensive fuzz testing. This higher value makes sense, as v3's [Liquidity invariant approximation](/concepts/vault/liquidity-invariant-approximation.html) introduces an additional component of the "error bar" that was not present in v2. ### Maximum Swap Fee Percentage `_MAX_SWAP_FEE_PERCENTAGE` is set to 10%, the same as in v2. This is there to protect users, and is similar to the Protocol Fee Controllers limits on fees. It ensures a malicious pool operator cannot raise fees to confiscatory levels -- at least static swap fees. Pools that support dynamic swap fees can raise them much higher, but core pools do not support this. ### Minimum Weight `_MIN_WEIGHT` is set to 1%, same as in v2. This is a somewhat arbitrary constraint placed on Weighted Pools, which was then used to set boundary conditions and determine other limits in a principled fashion (e.g., the In/Out and Invariant Ratios). Since AMMs have trouble at the "extremes" of the price curve, supporting lower weights didn't seem practical. ### Maximum In/Out Ratios `_MAX_IN_RATIO` and `_MAX_OUT_RATIO` are set to 30%, same as v2. v3 uses the same Weighted Math as v2, and the same minimum weight, so the same limits apply. Essentially, you cannot increase an individual token balance by more than 30% in a single operation. ### Minimum/Maximum Invariant Ratios `_MIN_INVARIANT_RATIO` and `_MAX_INVARIANT_RATIO` are set to 70% and 300% respectively, same as v2. v3 uses the same Weighted Math as v2, and the same minimum weight, so the same limits apply. Essentially, the total invariant cannot increase more than 3x, or decrease to less than 0.7x of the current value in a single operation. ## Stable Pool Constants ### Minimum/Maximum Amplification Parameter `MIN_AMP` is 1; `MAX_AMP` is 50,000 (previously, and in v2, the limit was 5,000). These values ultimately arise from the math in Curve's StableSwap. Higher values "flatten" the price curve (i.e., have a greater range were the tokens trade at essentially 1:1), and lower values make it more sensitive to the balances, and behave more like the Weighted Math price curve. Higher liquidity and lower volatility pools can generally have higher Amplification Parameters. There is also an `AMP_PRECISION` constant, set to 1000. The integer 1-5000 values are multiplied by this factor for greater precision in calculation; the Amplification Parameter is used for the invariant computation. ### Minimum Amplification Parameter Update Time, and Maximum Daily Rate `_MIN_UPDATE_TIME` is set to 1 day, and `_MAX_AMP_UPDATE_DAILY_RATE` to 2, same as v2. Stable Pools allow changing the Amplification Parameter, but limit the rate of change, as instantaneous changes would allow price manipulation. These limits mean that any change must take at least one day, and the Amplification Parameter cannot change more than a factor of two in a single day, in either direction. In other words, if the initial parameter is 200, it cannot decrease below 100 (200 / 2) or above 400 (200 \* 2) within a day. ### Minimum Swap Fee Percentage `_MIN_SWAP_FEE_PERCENTAGE` is set to 1e12 (0.0001%), same as v2. As with Weighted Pools, this is a guardrail to protect LPs, and ensures at least minimal revenue from swaps. Since the StableSwap invariant has a much lower error, the minimum fee can be much lower. ### Maximum Swap Fee Percentage `_MAX_SWAP_FEE_PERCENTAGE` is set to 10%, same as v2 (and same as the Weighted Pool), for all the same reasons. ### Minimum/Maximum Invariant Ratios `MIN_INVARIANT_RATIO` is set to 60%, and `MAX_INVARIANT_RATIO` is set to 500%. These have no analog in v2 (which had no limits). They may not be necessary in v3 either, but were added as an additional safety guardrail. ### Maximum Tokens `MAX_STABLE_TOKENS` is set to 5, same as v2. This limit arises from the StableSwap math, and is the maximum number of tokens for which the invariant approximation holds. ## Router Constants ### Maximum Token Amount `_MAX_AMOUNT` is set to 2^128 - 1, or type(uint128).max in Solidity. This is the maximum balance of a token within a pool, which is constrained by the packed balance storage. (The Vault stores both the raw and scaled balances in a single slot, in 128 bits each.) v2 additionally stored a timestamp (for use with oracles), so the maximum balance there was lower: 2^112 - 1. ## Weighted/Stable Pool Immutables ### PauseWindowDuration: These were set to 4 years for both Weighted and Stable pools. This is the same value as the Vault, as was done in v2. Note that the pause window is factory-specific, and the time period is relative to the *factory* deployment (not the pool). All pools become permissionless at the same time. New pool factories can use different values, or even opt out by setting it to zero (not recommended). Note also that the buffer period duration is not configurable at the pool factory level: the pools always use the Vault's buffer period. ## SDK API Reference ### AddLiquidity This class provides functionality to: * Perform on-chain queries to see the result of an addLiquidity operation * Build an addLiquidity transaction, with slippage, for a consumer to submit * Supported add types: SingleToken, Unbalanced, Proportional * Supports Balancer v2 and v3 #### Example See the [addLiquidity guide](/integration-guides/add-liquidity/overview.md) and the [addLiquidity example](/integration-guides/add-liquidity/sdk-tutorial.html#example-script). #### Constructor ```typescript const addLiquidity = new AddLiquidity(); ``` #### Methods #### query Simulate addLiquidity operation by using an onchain call. ```typescript query( input: AddLiquidityInput, poolState: PoolState ): Promise ``` **Parameters** \| Name | Type | Description | \| --------- | ------------------------------------------------------------------------------------------------------- | ------------------------------------------------------ | \| input | [AddLiquidityInput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L38) | User defined inputs | \| poolState | [PoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L5) | Current state of pool that liquidity is being added to | **Returns** ```typescript Promise; ``` [AddLiquidityQueryOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L54) - Data that can be passed to `buildCall`. Includes updated `bptOut` amount. *** #### buildCall Builds the addLiquidity transaction using user defined slippage. ```typescript buildCall(input: AddLiquidityBuildCallInput): AddLiquidityBuildCallOutput ``` **Parameters** \| Name | Type | Description | \| ----- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | \| input | [AddLiquidityBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L63) | Parameters required to build the call including user defined slippage | **Returns** ```typescript AddLiquidityBuildCallOutput; ``` [AddLiquidityBuildCallOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L75) - Encoded call data for addLiquidity that user can submit. *** #### buildCallWithPermit2 Builds the addLiquidity transaction using user defined slippage and Permit2 signature for token approval. \:::info Permit2 Signature Check out [Permit2 Helper section](#permit2-helper) on how to generate a Permit2 signature. \::: ```typescript buildCallWithPermit2(input: AddLiquidityBuildCallInput, permit2: Permit2): AddLiquidityBuildCallOutput ``` **Parameters** \| Name | Type | Description | \| ------- | ---------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------- | \| input | [AddLiquidityBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L63) | Parameters required to build the call including user defined slippage | \| permit2 | [Permit2](https://github.com/balancer/b-sdk/tree/main/src/entities/permit2Helper/index.ts#L35) | Permit2 object with metadata and encoded signature | **Returns** ```typescript AddLiquidityBuildCallOutput; ``` [AddLiquidityBuildCallOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L75) - Encoded call data for addLiquidity that user can submit. *** ### RemoveLiquidity This class provides functionality to: * Perform on-chain queries to see the result of an removeLiquidity operation * Build a removeLiquidity transaction, with slippage, for a consumer to submit * Supported remove types: Unbalanced, SingleTokenExactOutInput, SingleTokenExactInInput, Proportional * Supports Balancer v2 and v3 #### Example See the [removeLiquidity guide](/integration-guides/remove-liquidity/overview.md) and the [removeLiquidity example](/integration-guides/remove-liquidity/sdk-tutorial.html#example-script). #### Constructor ```typescript const removeLiquidity = new RemoveLiquidity(); ``` #### Methods #### query Simulate removeLiquidity operation by using an onchain call. ```typescript query( input: RemoveLiquidityInput, poolState: PoolState, ): Promise ``` **Parameters** \| Name | Type | Description | \| --------- | ------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | \| input | [RemoveLiquidityInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L52) | User defined inputs | \| poolState | [PoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L5) | Current state of pool that liquidity is being removed from | **Returns** ```typescript Promise; ``` [RemoveLiquidityQueryOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L70) - Data that can be passed to `buildCall`. Includes updated `amountsOut` amount. *** #### queryRemoveLiquidityRecovery Calculates proportional exit using pool state. Note - this does not do an onchain query. ```typescript queryRemoveLiquidityRecovery( input: RemoveLiquidityRecoveryInput, poolState: PoolState, ): Promise ``` **Parameters** \| Name | Type | Description | \| --------- | --------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | \| input | [RemoveLiquidityRecoveryInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L47) | User defined inputs | \| poolState | [PoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L5) | Current state of pool that liquidity is being removed from | **Returns** ```typescript Promise; ``` [RemoveLiquidityQueryOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L70) - Data that can be passed to `buildCall`. Includes updated `amountsOut` amount. *** #### buildCall Builds the removeLiquidity transaction using user defined slippage. ```typescript buildCall( input: RemoveLiquidityBuildCallInput, ): RemoveLiquidityBuildCallOutput ``` **Parameters** \| Name | Type | Description | \| ----- | ---------------------------------------------------------------------------------------------------------------------- | -------------------------------- | \| input | [RemoveLiquidityBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L79) | Input with user defined slippage | **Returns** ```typescript RemoveLiquidityBuildCallOutput; ``` [RemoveLiquidityBuildCallOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L83) - Encoded call data for addLiquidity that user can submit. *** #### buildCallWithPermit Builds the removeLiquidity transaction using user defined slippage and Permit signature for token approval. \:::info Permit Signature Check out [Permit Helper section](#permit-helper) on how to generate a Permit signature. \::: ```typescript buildCallWithPermit( input: RemoveLiquidityBuildCallInput, permit: Permit, ): RemoveLiquidityBuildCallOutput ``` **Parameters** \| Name | Type | Description | \| ------ | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------- | \| input | [RemoveLiquidityBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L79) | Input with user defined slippage | \| permit | [Permit](https://github.com/balancer/b-sdk/tree/main/src/entities/permitHelper/index.ts#L30) | Permit object with metadata and encoded signature | **Returns** ```typescript RemoveLiquidityBuildCallOutput; ``` [RemoveLiquidityBuildCallOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L83) - Encoded call data for addLiquidity that user can submit. *** ### Swap This class provides functionality to: * Perform on-chain queries to see the result of a Swap operation * Build a Swap transaction, with slippage, for a consumer to submit * Supports Balancer v2 and v3 #### Example See the [swap guide](../../integration-guides/swapping/swaps-with-sor-sdk.md) and [swap examples](https://github.com/MattPereira/v3-pool-operation-examples/tree/main/scripts/hardhat/swap). #### Constructor ```typescript const swap = new Swap(swapInput: SwapInput); ``` \| Name | Type | Description | \| --------- | -------------------------------------------------------------------------------------- | -------------------------------------- | \| swapInput | [SwapInput](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L8) | Swap input including path information. | Note: `SwapInput` data is normally returned from an API SOR query but may be constructed manually. #### Methods #### query Gets up to date swap result by querying onchain. ```typescript query( rpcUrl?: string, block?: bigint, ): Promise ``` **Parameters** \| Name | Type | Description | \| ----------------- | ------ | ----------------------------- | \| rpcUrl (optional) | string | RPC URL, e.g. Infura/Alchemy | \| block (optional) | bigint | Block no to perform the query | **Returns** ```typescript Promise; ``` [ExactInQueryOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L44) [ExactOutQueryOutput](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L49) The updated return for the given swap, either `expectedAmountOut` or `expectedAmountIn` depending on swap kind. *** #### buildCall Builds the swap transaction using user defined slippage. ```typescript buildCall( input: SwapBuildCallInput, ): SwapBuildOutputExactIn | SwapBuildOutputExactOut ``` **Parameters** \| Name | Type | Description | \| ----- | ------------------------------------------------------------------------------------------------ | -------------------------------- | \| input | [SwapBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L21) | Input with user defined slippage | **Returns** ```typescript SwapBuildOutputExactIn | SwapBuildOutputExactOut; ``` [SwapBuildOutputExactIn](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L31) | [SwapBuildOutputExactOut](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L35) - Encoded call data for swap that user can submit. Includes `minAmountOut` or `maxAmountIn` depending on swap kind. *** #### buildCallWithPermit2 Builds the swap transaction using user defined slippage and Permit2 signature for token approval. \:::info Permit2 Signature Check out [Permit2 Helper section](#permit2-helper) on how to generate a Permit2 signature. \::: ```typescript buildCallWithPermit2( input: SwapBuildCallInput, permit2: Permit2, ): SwapBuildOutputExactIn | SwapBuildOutputExactOut ``` **Parameters** \| Name | Type | Description | \| ------- | ------------------------------------------------------------------------------------------------ | -------------------------------------------------- | \| input | [SwapBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L21) | Input with user defined slippage | \| permit2 | [Permit2](https://github.com/balancer/b-sdk/tree/main/src/entities/permit2Helper/index.ts#L35) | Permit2 object with metadata and encoded signature | **Returns** ```typescript SwapBuildOutputExactIn | SwapBuildOutputExactOut; ``` [SwapBuildOutputExactIn](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L31) | [SwapBuildOutputExactOut](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L35) - Encoded call data for swap that user can submit. Includes `minAmountOut` or `maxAmountIn` depending on swap kind. *** #### quote Gives the combined return amount for all paths. Note - this always uses the original path amounts provided in constructor and does not get updated. ```typescript public get quote(): TokenAmount ``` **Returns** ```typescript TokenAmount; ``` [TokenAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/tokenAmount.ts) - Gives the combined return amount for all paths (output amount for givenIn, input amount for givenOut). *** #### inputAmount ```typescript public get inputAmount(): TokenAmount ``` **Returns** ```typescript TokenAmount; ``` [TokenAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/tokenAmount.ts) - Gives the combined input amount for all paths. *** #### outputAmount ```typescript public get outputAmount(): TokenAmount ``` **Returns** ```typescript TokenAmount; ``` [TokenAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/tokenAmount.ts) - Gives the combined output amount for all paths. *** #### queryCallData ```typescript public queryCallData(): string ``` **Returns** ```typescript string; ``` Encoded query data for swap that a user can call to get an updated amount. *** ### PriceImpact This class provides helper functions to calculate Price Impact for add/remove/swap actions. * Supports Balancer v2 and v3 #### Example See the [price impact example](https://github.com/balancer/b-sdk/tree/main/examples/priceImpact/addLiquidity.ts). #### Methods #### addLiquiditySingleToken Calculate price impact on add liquidity single token operations. ```typescript addLiquiditySingleToken( input: AddLiquiditySingleTokenInput, poolState: PoolState, ): Promise ``` **Parameters** \| Name | Type | Description | \| --------- | ------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------ | \| input | [AddLiquiditySingleTokenInput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L27) | Same input used in the corresponding add liquidity operation | \| poolState | [PoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L5) | Current state of pool that liquidity is being added to | **Returns** ```typescript Promise; ``` [PriceImpactAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/priceImpactAmount.ts) - Price impact for operation. *** #### addLiquidityUnbalanced Calculate price impact on add liquidity unbalanced operations. ```typescript addLiquidityUnbalanced = async ( input: AddLiquidityUnbalancedInput, poolState: PoolState, ): Promise ``` **Parameters** \| Name | Type | Description | \| --------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------ | \| input | AddLiquidityUnbalancedInput | Same input used in the corresponding add liquidity operation | \| poolState | [PoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L5) | Current state of pool that liquidity is being added to | **Returns** ```typescript Promise; ``` [PriceImpactAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/priceImpactAmount.ts) - Price impact for operation. *** #### addLiquidityNested Calculate price impact on add liquidity nested token operations. ```typescript addLiquidityNested = async ( input: AddLiquidityNestedInput, nestedPoolState: NestedPoolState, ): Promise ``` **Parameters** \| Name | Type | Description | \| --------------- | ---------------------------------------------------------------------------------------- | ------------------------------------------------------------ | \| input | AddLiquidityNestedInput | Same input used in the corresponding add liquidity operation | \| nestedPoolState | [NestedPoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L43) | Current state of nested pools | **Returns** ```typescript Promise; ``` [PriceImpactAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/priceImpactAmount.ts) - Price impact for operation. *** #### removeLiquidity Calculate price impact on remove liquidity operations. ```typescript removeLiquidity = async ( input: | RemoveLiquiditySingleTokenExactInInput | RemoveLiquidityUnbalancedInput, poolState: PoolState, ): Promise ``` **Parameters** \| Name | Type | Description | \| --------- | ------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------- | \| input | [RemoveLiquiditySingleTokenExactInInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L35) | Same input used in the corresponding remove liquidity operation | \| input | [RemoveLiquidityUnbalancedInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L24) | Same input used in the corresponding remove liquidity operation | \| poolState | [PoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L5) | Current state of pool that liquidity is being removed from | **Returns** ```typescript Promise; ``` [PriceImpactAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/priceImpactAmount.ts) - Price impact for operation. *** #### removeLiquidityNested Calculate price impact on remove liquidity single token nested operations. ```typescript removeLiquidityNested = async ( input: RemoveLiquidityNestedSingleTokenInput, nestedPoolState: NestedPoolState, ): Promise ``` **Parameters** \| Name | Type | Description | \| --------------- | ------------------------------------------------------------------------------------------------------------------------------------ | --------------------------------------------------------------- | \| input | [RemoveLiquidityNestedSingleTokenInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidityNested/types.ts#L15) | Same input used in the corresponding remove liquidity operation | \| nestedPoolState | [NestedPoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L43) | Current state of nested pools | **Returns** ```typescript Promise; ``` [PriceImpactAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/priceImpactAmount.ts) - Price impact for operation. *** #### swap Calculate price impact on swap operations. ```typescript swap = async ( swapInput: SwapInput, rpcUrl?: string, block?: bigint, ): Promise ``` **Parameters** \| Name | Type | Description | \| ----------------- | -------------------------------------------------------------------------------------- | -------------------------------------- | \| swapInput | [SwapInput](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/types.ts#L8) | Swap input including path information. | \| rpcUrl (optional) | string | RPC URL, e.g. Infura/Alchemy | \| block (optional) | bigint | Block no to perform the query | Note: `SwapInput` data is normally returned from an API SOR query but may be constructed manually. **Returns** ```typescript Promise; ``` [PriceImpactAmount](https://github.com/balancer/b-sdk/tree/main/src/entities/priceImpactAmount.ts) - Price impact for operation. *** ### BalancerApi This class provides helper functions for interacting with the Balancer API. #### Example See the examples for add/remove/swap linked above as these use BalancerApi to fetch required data. #### Constructor ```typescript const balancerApi = new BalancerApi(balancerApiUrl: string, chainId: ChainId); ``` \| Name | Type | Description | \| -------------- | --------------------------------------------------------------------------------- | -------------------------- | \| balancerApiUrl | string | Url of Balancer API | \| chainId | [ChainId](https://github.com/balancer/b-sdk/tree/main/src/utils/constants.ts#L54) | Chain that will be queried | #### Methods #### pools.fetchPoolState Finds state of given pool. ```typescript pools.fetchPoolState(id: string): Promise ``` **Parameters** \| Name | Type | Description | \| ---- | ------ | --------------------------------- | \| id | string | ID of pool, v2=poolId, v3=address | **Returns** ```typescript Promise; ``` [PoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L5) - State of given pool. *** #### pools.fetchPoolStateWithBalances Finds state of given pool including token balances and pool shares. ```typescript fetchPoolStateWithBalances( id: string, ): Promise ``` **Parameters** \| Name | Type | Description | \| ---- | ------ | --------------------------------- | \| id | string | ID of pool, v2=poolId, v3=address | **Returns** ```typescript Promise; ``` [PoolStateWithBalances](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L13) - State of given pool including token balances and pool shares. *** #### nestedPools.fetchPoolState Finds state of a set of nested pools. ```typescript fetchNestedPoolState(id: string): Promise ``` **Parameters** \| Name | Type | Description | \| ---- | ------ | --------------------------------- | \| id | string | ID of pool, v2=poolId, v3=address | **Returns** ```typescript Promise; ``` [NestedPoolState](https://github.com/balancer/b-sdk/tree/main/src/entities/types.ts#L43) - state of a set of nested pools. *** #### sorSwapPaths.fetchSorSwapPaths Finds optimized swap paths for a given swap config. ```typescript fetchSorSwapPaths(sorInput: SorInput): Promise ``` **Parameters** \| Name | Type | Description | \| -------- | ------------------------------------------------------------------------------------------------------------------------ | ------------ | \| sorInput | [SorInput](https://github.com/balancer/b-sdk/tree/main/src/data/providers/balancer-api/modules/sorSwapPaths/index.ts#L8) | Swap configs | **Returns** ```typescript Promise; ``` [Path\[\]](https://github.com/balancer/b-sdk/tree/main/src/entities/swap/paths/types.ts#L6) - optimized swap paths for the given swap. *** ### Utils Helper functions. #### calculateProportionalAmounts Given pool balances (including BPT) and a reference token amount, it calculates all other amounts proportional to the reference amount. **Example** See [calculateProportionalAmounts example](https://github.com/balancer/b-sdk/tree/main/examples/utils/calculateProportionalAmounts.ts). ```typescript calculateProportionalAmounts( pool: { address: Address; totalShares: HumanAmount; tokens: { address: Address; balance: HumanAmount; decimals: number }[]; }, referenceAmount: InputAmount, ): { tokenAmounts: InputAmount[]; bptAmount: InputAmount; } ``` **Parameters** \| Name | Type | Description | \| --------------- | --------------------------------------------------------------------------- | ---------------- | \| pool | See above | Pool state | \| referenceAmount | [InputAmount](https://github.com/balancer/b-sdk/tree/main/src/types.ts#L43) | Ref token amount | **Returns** ```typescript { tokenAmounts: InputAmount[]; bptAmount: InputAmount; } ``` Amounts proportional to the reference amount. *** #### Permit2 Helper Balancer v3 handles token approval through Pemit2 and this helper facilitates Permit2 signature generation. Each operation (i.e. `addLiquidity`, `addLiquidityNested`, `addLiquidityBoosted` and `swap`) has its own method that leverages the same input type of the operation itself in order to simplify signature generation. **Example** See [addLiquidityWithPermit2Signature example](https://github.com/balancer/b-sdk/tree/main/examples/addLiquidity/addLiquidityWithPermit2Signature.ts). **Function** ```typescript static async signAddLiquidityApproval( input: AddLiquidityBaseBuildCallInput & { client: PublicWalletClient; owner: Address; nonces?: number[]; expirations?: number[]; }, ): Promise ``` **Parameters** \| Name | Type | Description | \| ---------------------- | -------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | \| input | [AddLiquidityBaseBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/addLiquidity/types.ts#L62) | Add Liquidity Input | \| client | [PublicWalletClient](https://github.com/balancer/b-sdk/tree/main/src/utils/types.ts#L3) | Viem's wallet client with public actions | \| owner | Address | User address | \| nonces (optional) | number\[] | Nonces for each token | \| expirations (optional) | number\[] | Expirations for each token | **Returns** ```typescript Promise; ``` [Permit2](https://github.com/balancer/b-sdk/tree/main/src/entities/permit2Helper/index.ts#L35) - Permit2 object with metadata and encoded signature *** #### Permit Helper Balancer v3 conforms with EIP-2612 and this helper facilitates Permit signature generation. Each operation (i.e. `removeLiquidity`, `removeLiquidityNested` and `removeLiquidityBoosted`) has its own method that leverages the same input type of the operation itself in order to simplify signature generation. **Example** See [removeLiquidityWithPermitSignature example](https://github.com/balancer/b-sdk/tree/main/examples/removeLiquidity/removeLiquidityWithPermitSignature.ts). **Function** ```typescript static signRemoveLiquidityApproval = async ( input: RemoveLiquidityBaseBuildCallInput & { client: PublicWalletClient; owner: Hex; nonce?: bigint; deadline?: bigint; }, ): Promise ``` **Parameters** \| Name | Type | Description | \| ---------------------- | -------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | \| input | [RemoveLiquidityBaseBuildCallInput](https://github.com/balancer/b-sdk/tree/main/src/entities/removeLiquidity/types.ts#L81) | Remove Liquidity Input | \| client | [PublicWalletClient](https://github.com/balancer/b-sdk/tree/main/src/utils/types.ts#L3) | Viem's wallet client with public actions | \| owner | Address | User address | \| nonces (optional) | number\[] | Nonces for each token | \| expirations (optional) | number\[] | Expirations for each token | **Returns** ```typescript Promise; ``` [Permit](https://github.com/balancer/b-sdk/tree/main/src/entities/permitHelper/index.ts#L30) - Permit object with metadata and encoded signature *** ## Balancer SDK The Balancer SDK is a Typescript/Javascript library for interfacing with the Balancer protocol. This includes common contract interactions such as add/remove liquidity and swaps. [![npm version](https://img.shields.io/npm/v/@balancer/sdk/latest.svg)](https://www.npmjs.com/package/@balancer/sdk/v/latest) Smart Order Router functionality is accessed via the Balancer API. Find details in the [API Section](/data-and-analytics/data-and-analytics/balancer-api/balancer-api.md). ### Installation Install the package with \::: code-tabs#shell @tab pnpm ```bash pnpm add @balancer/sdk ``` @tab yarn ```bash yarn add @balancer/sdk ``` @tab npm ```bash npm install @balancer/sdk ``` \::: ### Guides and Examples For detailed guides explaining how to use the SDK for common actions please see [Developer Guides](../../integration-guides/). There are also detailed examples that run against a local fork in the [pool operation examples repo](https://github.com/MattPereira/v3-pool-operation-examples). ## Add Liquidity Guide Balancer v3 supports several different [types of add liquidity operations](https://docs.balancer.fi/concepts/vault/add-remove-liquidity-types.html#add-remove-liquidity-types) ### Core Concepts The core concepts of adding liquidity are the same for any programming language or framework: * The sender must do a permit2 approval with the Router as the spender for each token * Token amount inputs/outputs are always in the raw token scale, e.g. `1 USDC` should be sent as `1000000` because it has 6 decimals * If a pool's tokens include an ERC4626 with an initialized buffer, you have the option to add liquidity using the `asset()` of the ERC4626, which we refer to as the "underlying" token. * Transactions are always sent to the appropriate [Router](../../concepts/router/overview.md) * Use the standard `Router` to add liquidity with pool tokens * Use the `CompositeLiquidityRouter` to add liquidity with a pool's underlying tokens * In exchange for providing liquidity the sender will receive [Balancer Pool Tokens](../../concepts/core-concepts/balancer-pool-tokens.md) (BPTs) which represents their share of the pool and can be used to remove liquidity at any time ### Example Scripts Run any of the scripts listed below against a local fork of Ethereum mainnet using the [v3 pool operation examples repo](https://github.com/MattPereira/v3-pool-operation-examples/tree/main?tab=readme-ov-file#balancer-v3-pool-operation-examples) ##### TypeScript SDK * [addLiquidityUnbalanced.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/add-liquidity/addLiquidityUnbalanced.ts) * [addLiquidityProportional.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/add-liquidity/addLiquidityProportional.ts) * [addLiquidityUnbalancedToERC4626.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/add-liquidity/addLiquidityUnbalancedToERC4626Pool.ts) * [addLiquidityProportionalToERC4626.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/add-liquidity/addLiquidityProportionalToERC4626Pool.ts) ##### Solidity * [AddLiquidityUnbalanced.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/add-liquidity/AddLiquidityUnbalanced.s.sol) * [AddLiquidityProportional.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/add-liquidity/AddLiquidityProportional.s.sol) * [AddLiquidityUnbalancedToERC4626.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/add-liquidity/AddLiquidityUnbalancedToERC4626Pool.s.sol) * [AddLiquidityProportionalToERC4626.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/add-liquidity/AddLiquidityProportionalToERC4626Pool.s.sol) ### Beginner Tutorials

### Add Liquidity with Typescript SDK This guide demonstrates how to add liquidity to a pool. We will use the addLiquidityUnbalanced method, since it allows exact amounts of any pool token to be added to a pool, avoiding unnecessary dust in the user's wallet. See the [Router API](/developer-reference/contracts/router-api.html) for other supported add methods. *This guide is for adding liquidity to Balancer v3 with the [b-sdk](https://github.com/balancer/b-sdk). This sdk supports adding liquidity to Balancer v3, Balancer v2 as well as Cow-AMMs* #### Install the Balancer SDK The [Balancer SDK](https://github.com/balancer/b-sdk) is a Typescript/Javascript library for interfacing with the Balancer protocol and can be installed with: \::: code-tabs#shell @tab pnpm ```bash pnpm add @balancer/sdk ``` @tab yarn ```bash yarn add @balancer/sdk ``` @tab npm ```bash npm install @balancer/sdk ``` \::: #### Example Script Run this example script on a local fork of Ethereum mainnet using our [v3 pool operation examples repo](https://github.com/MattPereira/v3-pool-operation-examples/tree/main?tab=readme-ov-file#balancer-v3-pool-operation-examples) The four main helper classes we use from the SDK are: * `BalancerApi` - to simplify retrieving pool data from the Pools API * `AddLiquidity` - to build addLiquidity queries and transactions * `Slippage` - to simplify creating limits with user defined slippage * `Permit2Helper` - to simplify creating a permit2 signature #### Fetch pool data In this example we use the BalancerApi `fetchPoolState` function to fetch the pool data required for the addLiquidityUnbalanced `poolState` parameter. ```typescript const balancerApi = new BalancerApi('https://api-v3.balancer.fi/', chainId); const poolState = await balancerApi.pools.fetchPoolState(pool); ``` To see the full query used to fetch pool state refer to the code [here](https://github.com/balancer/b-sdk/blob/41d2623743ab7fa466ed4d0f5f5c7e5aa16b7d91/src/data/providers/balancer-api/modules/pool-state/index.ts#L7). #### Query add liquidity [Router queries](../../concepts/router/queries.md) allow for simulation of operations without execution. In this example, when the `query` function is called: ```typescript const queryOutput = await addLiquidity.query(addLiquidityInput, poolState); // queryOutput.bptOut ``` The Routers [queryAddLiquidityUnbalanced](../../developer-reference/contracts/router-api.md#queryaddliquidityunbalanced) function is used to find the amount of BPT that would be received, `bptOut`. #### Build the call with permit2 and slippage The `Permit2Helper` abstracts away the complexity involved with creating a permit2 signature ```typescript const permit2 = await Permit2Helper.signAddLiquidityApproval({ ...queryOutput, slippage, client: walletClient.extend(publicActions), owner: walletClient.account, }); ``` Then `buildCallWithPermit2` uses the `bptOut` and the user defined `slippage` to calculate the `minBptAmountOut`: ```typescript const call = addLiquidity.buildCallWithPermit2( { ...queryOutput, slippage }, permit2 ); ``` In the full example above, we defined our slippage as `Slippage.fromPercentage('1')`, meaning that we if we do not receive at least 99% of our expected `bptOut`, the transaction should revert. Internally, the SDK subtracts 1% from the query output, as shown in `Slippage.applyTo` below: ```typescript /** * Applies slippage to an amount in a given direction * * @param amount amount to apply slippage to * @param direction +1 adds the slippage to the amount, and -1 will remove the slippage from the amount * @returns */ public applyTo(amount: bigint, direction: 1 | -1 = 1): bigint { return MathSol.mulDownFixed( amount, BigInt(direction) * this.amount + WAD, ); } ``` #### Send the call The output of the `buildCall` function provides all that is needed to submit the addLiquidity transaction: * `to` - the address of the Router * `callData` - the encoded call data * `value` - the native asset value to be sent It also returns the `minBptOut` amount which can be useful to display/validation purposes before the transaction is sent. ```typescript const hash = await walletClient.sendTransaction({ account: walletClient.account, data: call.callData, to: call.to, value: call.value, }); ``` ### Add Liquidity with Solidity \::: info This page is a work in progress \::: The following code snippet shows how to add liquidity from a smart contract. \::: warning Queries should not be used onchain to set minAmountOut due to possible manipulation via frontrunning. \::: ### Boosted Pools Intro In V3, [Boosted Pools](/concepts/explore-available-balancer-pools/boosted-pool.md) are pools containing ERC4626 yield bearing tokens. These allow for highly capital efficient pools (up to 100%). For example a stable pool could be 100% boosted by containing stataDAI and stataUSDC. The V3 Vault enables gas-efficient swaps by leveraging the underlying ERC4626 tokens (e.g., DAI and USDC in the previous 100% boosted token). This is accomplished through the use of [ERC4626 Liquidity Buffers](/concepts/vault/buffer.md), an internal mechanism of the Vault designed to optimize efficiency. When a swap involves an underlying token, the Vault automatically utilizes the available liquidity buffer, bypassing the need to wrap or unwrap tokens through the lending protocol. This significantly reduces gas costs. If no suitable buffer is available, the Vault will default to wrapping or unwrapping tokens as needed to complete the swap. Any type of pool can be boosted and any ERC4626 token can be used. As an example, consider two pools: 1. weth/USDC 2. stataUSDC/stataDAI We can now facilitate a gas efficient swap from weth to dai which would take the following steps: 1. Swap weth to USDC through pool 1 2. Use buffer to swap USDC to stataUSDC 3. Swap stataUSDC to stataDAI through pool 2 4. Use buffer to swap stataDAI to DAI ### Coding A Swap The swapper has the responsibility to decide whether a specific swap route should use buffers. In the `SwapPathStep` struct the boolean, `isBuffer` should be set true and the `pool` param is the ERC4626 token address: ```solidity struct SwapPathStep { address pool; // Should be ERC4626 token address IERC20 tokenOut; bool isBuffer; // Should be set to true } ``` In the case of trading DAI to USDC via a stataDAI/stataUSDC boosted pool the `SwapPathExactAmountIn` would look like: ```solidity SwapPathExactAmountIn({ tokenIn: address(DAI), steps: [ SwapPathStep({ ------ Buffer/Wrap step, DAI > stataDAI pool: address(stataDAI), tokenOut: (address(stataDAI)), isBuffer: true }), SwapPathStep({ ----- Swap step, stataDAI > stataUSDC pool: address(boostedPool) tokenOut: address(stataUSDC), isBuffer: false }), SwapPathStep({ ---- Buffer/Unwrap step, stataUSDC > USDC pool: address(stataUSDC) tokenOut: address(USDC), isBuffer: true }) ], exactAmountIn: myExactAmountIn // your defined amount minAmountOut: myMinAmountOut // min amount out }) ``` The trade will execute regardless of whether the Buffer has enough liquidity or not. Remember: If the buffer does not have enough liquidity it will simply additionally wrap or unwrap (and incur additional gas cost). ### Core Concepts * In the Balancer v3 architecture [Routers](/concepts/router/overview.md) serve as the pivotal interface for users (not the Vault) * Single swaps: [Balancer Router](/developer-reference/contracts/router-api.md) * Multi-hop swaps: [Batch Router](/developer-reference/contracts/batch-router-api.md) * Sender should use Permit2 to approve the Router to spend each swap input token * Token amount inputs/outputs are always in the raw token scale, e.g. 1 USDC should be sent as 1000000 because it has 6 decimals * There are two different swap kinds: * ExactIn: Where the user provides an exact input token amount. * ExactOut: Where the user provides an exact output token amount. * There are two subsets of a swap: * Single Swap: A swap, tokenIn > tokenOut, using a single pool. This is the most gas efficient option for a swap of this kind. * Multi-path Swaps: Swaps involving multiple paths but all executed in the same transaction. Each path can have its own (or the same) tokenIn/tokenOut. * Boosted Pools and Liquidity Buffers enable gas effective swaps through capital efficient pools (see [Boosted Pool section](./boosted-pools.md)) * Balancer v2 used the concept of poolIds, this is no longer used in v3 which always uses pool address * Balancers new [API](/data-and-analytics/data-and-analytics/balancer-api/balancer-api.md) can be used to access pool data ## Events All Vault Events are detailed in [IVaultEvents](https://github.com/balancer/balancer-v3-monorepo/blob/4b85fc30fbbffc3a0b7aa84e1dc0b63082d0768e/pkg/interfaces/contracts/vault/IVaultEvents.sol) For event based systems the following Vault events can be useful to construct/track pool state: * Swap(address indexed pool, IERC20 indexed tokenIn, IERC20 indexed tokenOut, uint256 amountIn, uint256 amountOut, uint256 swapFeePercentage, uint256 swapFeeAmount); * A swap has occurred. * LiquidityAdded(address indexed pool, address indexed liquidityProvider, AddLiquidityKind indexed kind, uint256 totalSupply, uint256\[] amountsAddedRaw, uint256\[] swapFeeAmountsRaw); * Liquidity has been added to a pool (including initialization). * LiquidityRemoved( address indexed pool, address indexed liquidityProvider, RemoveLiquidityKind indexed kind, uint256 totalSupply, uint256\[] amountsRemovedRaw, uint256\[] swapFeeAmountsRaw ); * Liquidity has been removed from a pool. * AggregateSwapFeePercentageChanged(address indexed pool, uint256 aggregateSwapFeePercentage) * A protocol or pool creator fee has changed, causing an update to the aggregate swap fee. * SwapFeePercentageChanged(address indexed pool, uint256 swapFeePercentage); * Emitted when the swap fee percentage of a pool is updated. * PoolPausedStateChanged(address indexed pool, bool paused) * A Pool's pause status has changed. ## Fetching Pool List ### Using Balancers API The [Balancer API](/data-and-analytics/data-and-analytics/balancer-api/balancer-api.md) can be used to retrieve a list of v3 pools and immutable data for calculating swaps. The API is running as a graphql server and is deployed at . The following query can be used to fetch any pools with the required immutable data used for swap calculations: ``` query MyQuery { aggregatorPools( where: {chainIn: [BASE], protocolVersionIn: [3], includeHooks: [STABLE_SURGE]} ) { id type poolTokens { address weight isErc4626 underlyingToken { address } } } } ``` Various filters can be applied. ### Onchain It is possible to query a list of pools from each pool factory using the following: ```solidity /** * @notice Return a subset of the list of pools deployed by this factory. * @dev `start` must be a valid index, but if `count` exceeds the total length, it will not revert, but simply * stop at the end and return fewer results than requested. * * @param start The index of the first pool to return * @param count The maximum number of pools to return * @return pools The list of pools deployed by this factory, starting at `start` and returning up to `count` pools */ function getPoolsInRange(uint256 start, uint256 count) external view returns (address[] memory pools); /** * @notice Return the complete list of pools deployed by this factory. * @return pools The list of pools deployed by this factory */ function getPools() external view returns (address[] memory pools); ``` ## Fetching Pool Data Swap calculations require a combination of data that can be considered as immutable and dynamic. The API can provide both but dynamic data will be subject to a 5minute cache which may not provide the required accuracy. Pools also expose useful view functions that can be used to retrieve this data. These functions follow the format: ``` getPOOLTYPEPoolDynamicData e.g.: - function getStablePoolDynamicData() external view returns (StablePoolDynamicData memory data); getPOOLTYPEImmutableData e.g.: - function getStablePoolImmutableData() external view returns (StablePoolImmutableData memory data); ``` If a pool is not exposing the helper functions or you prefer to query for specific data the following view functions are available on the [Vault](/developer-reference/contracts/vault-api.md) and can be used to find onchain pool state: ``` getPoolTokenRates getCurrentLiveBalances getPoolConfig totalSupply isPoolPaused getHooksConfig ``` ## Hooks Reference Explore our [GitHub repository](https://github.com/balancer/balancer-maths) containing reference mathematical implementations, in Javascript and Python, for supported Balancer hook types. Designed to assist developers and integrators in understanding the underlying swap calculations, these implementations can be imported as a packages into your project or serve as a reference for your own implementation. For more details about Balancer V3 Hooks implementation see [Hooks Core Concepts](/concepts/core-concepts/hooks.md). ## Supported Hook Types ### Stable Surge Hook * This uses the [onComputeDynamicSwapFeePercentage](/developer-reference/contracts/hooks-api.md#oncomputedynamicswapfeepercentage) hook. * [Intro blog post](https://medium.com/balancer-protocol/balancers-stablesurge-hook-09d2eb20f219). * Pools with StableSurge hook will be deployed from a dedicated [factory](https://github.com/balancer/balancer-v3-monorepo/blob/2f088c6b8f66ad55885d257c1e3debe2a6e21e97/pkg/pool-hooks/contracts/StableSurgePoolFactory.sol). * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories). * See SC code implementation [here](https://github.com/balancer/balancer-v3-monorepo/blob/2f088c6b8f66ad55885d257c1e3debe2a6e21e97/pkg/pool-hooks/contracts/StableSurgeHook.sol). * [Typescript maths reference](https://github.com/balancer/balancer-maths/blob/eeff3ef8cf1105a0aaa6d96a4c0f8b7a62135256/typescript/src/hooks/stableSurgeHook.ts) * [Python maths reference](https://github.com/balancer/balancer-maths/blob/main/python/src/hooks/stable_surge/stable_surge_hook.py). * Maths requires the configurable `maxSurgeFeePercentage` and `thresholdPercentage` values which can be fetched and tracked using the following functions and events: ```solidity function getMaxSurgeFeePercentage(address pool) external view returns (uint256); event ThresholdSurgePercentageChanged(address indexed pool, uint256 newSurgeThresholdPercentage); function getSurgeThresholdPercentage(address pool) external view returns (uint256); event MaxSurgeFeePercentageChanged(address indexed pool, uint256 newMaxSurgeFeePercentage); ``` * API Support: Can use the filter: `includeHooks: [STABLE_SURGE]`, to include all pools using this hook type: ```graphql query MyQuery { aggregatorPools( where: {chainIn: SEPOLIA, includeHooks: [STABLE_SURGE], protocolVersionIn: 3} ) { address type hook { dynamicData { maxSurgeFeePercentage surgeThresholdPercentage } } } } ``` ### AkronWeightedLVRFee Hook * This uses the [onComputeDynamicSwapFeePercentage](/developer-reference/contracts/hooks-api.md#oncomputedynamicswapfeepercentage) hook. * Uses adapted weighted maths to calculate fee - does not need to track any hook state. * Only used with Weighted pools. * [Pools](https://balancer.fi/pools?textSearch=akron) * [Akron Docs](https://crocus-sidewalk-9c5.notion.site/Balancer-Weighted-Pool-implementing-Akron-LVR-linked-Dynamic-Swap-Fee-Hook-integration-guide-inclu-1697cd41d8b880e1840be00404df2e3a). * See SC code implementation [here](https://github.com/Akron-admin/balancer-v3-monorepo/blob/Weighted-Hook/pkg/pool-hooks/contracts/AkronWeightedLVRFeeHook.sol) * [Typescript maths reference](https://github.com/balancer/balancer-maths/tree/main/typescript/src/hooks/akron) * Deployment Addresses: ``` Base: hookAddress: '0xA45570815dbE7BF7010c41f1f74479bE322D02bd' Arbitrum: hookAddress: '0xD221aFFABdD3C1281ea14C5781DEc6B0fCA8937E' ``` * API Support: Can use the filter: `includeHooks: [AKRON]`, to include all pools using this hook type: ```graphql query MyQuery { aggregatorPools( where: {chainIn: BASE, includeHooks: [AKRON], protocolVersionIn: 3} ) { address type hook { type } dynamicData { swapFee } } } ``` ## Integrating Balancer Liquidity For Swap Aggregators This secion serves as a central hub for aggregators seeking vital information and resources to seamlessly integrate with Balancer v3 liquidity. Should you require additional assistance or find any gaps in the provided information, our team is readily available to support you. * [Core Concepts](./core-concepts.md) * [Fetching Pools And Data](./fetching-pools-and-data.md) * [Events](./events.md) * [Making And Querying Swaps](./making-and-querying-swaps.md) * [Pool Maths And Details](./pool-maths-and-details.md) * [Boosted Pools](./boosted-pools.md) * [Swap Fees](./swap-fees.md) * [Useful Resources](./useful-resources.md) ### Single Swaps For a token to token swap through a single pool the following [Balancer Router](/developer-reference/contracts/router-api.md) functions should be used: [swapSingleTokenExactIn](/developer-reference/contracts/router-api.md#swapsingletokenexactin) and [swapSingleTokenExactOut](/developer-reference/contracts/router-api.md#swapsingletokenexactout) are the most gas efficient functions to use. Checkout Javascript and Solidity examples [here](/integration-guides/swapping/swapping-custom-paths-with-router.md#single-swap). ### Multi-path Swaps Swaps paths constructed of steps through multiple pools/tokens the following [Batch Router](/developer-reference/contracts/batch-router-api.md) functions should be used: [swapExactIn](/developer-reference/contracts/batch-router-api.md#swapexactin) and [swapExactOut](/developer-reference/contracts/batch-router-api.md#swapexactout) functions. A `SwapPathStep` is defined as: ``` struct SwapPathStep { address pool; IERC20 tokenOut; // If true, the "pool" is an ERC4626 Buffer. Used to wrap/unwrap tokens if pool doesn't have enough liquidity. bool isBuffer; } ``` and paths can include add/remove liquidity steps by using the address of the respective pool. For example, the following `SwapPathExactAmountIn` would execute a swap of USDC to BAL then add liquidity to the 80/20 BAL/WETH pool. ```solidity // Note - pseudo code SwapPathExactAmountIn { tokenIn: USDC // for each step: // if tokenIn == pool use removeLiquidity SINGLE_TOKEN_EXACT_IN // if tokenOut == pool use addLiquidity UNBALANCED steps: [ { pool: '0xBAL_USDC_POOL', tokenOut: '0xBAL', isBuffer: false }, { pool: '0xB-80BAL-20WETH_POOL', tokenOut: '0xB-80BAL-20WETH_POOL', isBuffer: false } ] exactAmountIn: 1000000, minAmountOut: 100000 } ``` Checkout Javascript and Solidity examples [here](/integration-guides/swapping/swapping-custom-paths-with-router.md#multi-path-swap). ### Simulating Swaps Using Query Functions [Queries](/concepts/router/queries.md) provide the ability to simulate an operation and find its result without executing a transaction. Balancer Routers provide a query for all state changing liquidity operations including single and multi-path swap functions, e.g. `querySwapSingleTokenExactIn`. \::: warning Note - for onchain integrations queries should not be used to set limits due to possible manipulation via frontrunning. \::: ## Pool Maths Reference Explore our [GitHub repository](https://github.com/balancer/balancer-maths) containing reference mathematical implementations, in Javascript and Python, for supported Balancer pool types. Designed to assist developers and integrators in understanding the underlying swap calculations, these implementations can be imported as a packages into your project or serve as a reference for your own implementation. ## Supported Pool Types ### Weighted Pool Pools that swap tokens by enforcing a Constant Weighted Product invariant. * See SC code implementation [here](https://github.com/balancer/balancer-v3-monorepo/tree/main/pkg/pool-weighted). * [Typescript maths reference](https://github.com/balancer/balancer-maths/tree/main/typescript/src/weighted) * [Python maths reference](https://github.com/balancer/balancer-maths/tree/main/python/src/pools/weighted) * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories) - See `WeightedPoolFactory` ### Stable Pool Pools that swap tokens by enforcing a Stable Math invariant, based on Curve. * See SC code implementation [here](https://github.com/balancer/balancer-v3-monorepo/tree/main/pkg/pool-stable). * [Typescript maths reference](https://github.com/balancer/balancer-maths/tree/main/typescript/src/stable) * [Python maths reference](https://github.com/balancer/balancer-maths/tree/main/python/src/pools/stable) * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories) - See `StablePoolFactory` * Amplification factor can be dynamic; see: * `getAmplificationParameter()` view function * `AmpUpdateStarted` & `AmpUpdateStopped` events ### Stable Surge Pool Stable Pools that use the Stable Surge Hook, a dynamic fee implementation that increases fees on transactions that unbalance the pool. The pool itself is exactly the same - a standard Stable Pool. The only difference is the hook, which is attached to the pool by the factory. * See SC code implementation [here](https://github.com/balancer/balancer-v3-monorepo/tree/main/pkg/pool-hooks/contracts/StableSurgePoolFactory.sol). * [Typescript maths reference](https://github.com/balancer/balancer-maths/tree/main/typescript/src/stable) * [Python maths reference](https://github.com/balancer/balancer-maths/tree/main/python/src/pools/stable) * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories) - See `StableSurgePoolFactory` * Amplification factor can be dynamic; see: * `getAmplificationParameter()` view function * `AmpUpdateStarted` & `AmpUpdateStopped` events ### Liquidity Bootstrapping Pool Liquidity Bootstrapping pools have linearly changing weights but use weighted math to determine prices. * [LBP docs](https://docs.balancer.fi/concepts/explore-available-balancer-pools/liquidity-bootstrapping-pool) * See SC code implementation [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/lbp/LBPool.sol) * [Typescript maths reference](https://github.com/balancer/balancer-maths/blob/main/typescript/src/liquidityBootstrapping) * [Python maths reference](https://github.com/balancer/balancer-maths/tree/main/python/src/pools/liquidity_bootstrapping) * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories) - See `LBPoolFactory ` * [LB pools on Balancer App](https://balancer.fi/pools?poolTypes=LBP\&protocolVersion=3) * weight calculation requires the following parameters: ``` projectTokenIndex currentTime startTime endTime projectTokenStartWeight projectTokenEndWeight ``` * pool tokens are always sorted alphanumerically. * Data can be fetched onchain using the following helpers (see [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/lbp/LBPool.sol#L267-L279) and [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-weighted/contracts/lbp/LBPool.sol#L282-L306)): * The pool interface is available [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/pool-weighted/ILBPool.sol). ```solidity function getLBPoolDynamicData() external view override returns (LBPoolDynamicData memory data) { data.balancesLiveScaled18 = _vault.getCurrentLiveBalances(address(this)); data.normalizedWeights = _getNormalizedWeights(); data.staticSwapFeePercentage = _vault.getStaticSwapFeePercentage((address(this))); data.totalSupply = totalSupply(); PoolConfig memory poolConfig = _vault.getPoolConfig(address(this)); data.isPoolInitialized = poolConfig.isPoolInitialized; data.isPoolPaused = poolConfig.isPoolPaused; data.isPoolInRecoveryMode = poolConfig.isPoolInRecoveryMode; data.isSwapEnabled = _isSwapEnabled(); } struct LBPoolDynamicData { uint256[] balancesLiveScaled18; uint256[] normalizedWeights; uint256 staticSwapFeePercentage; uint256 totalSupply; bool isPoolInitialized; bool isPoolPaused; bool isPoolInRecoveryMode; bool isSwapEnabled; } /// @inheritdoc ILBPool function getLBPoolImmutableData() external view override returns (LBPoolImmutableData memory data) { data.tokens = _vault.getPoolTokens(address(this)); data.projectTokenIndex = _projectTokenIndex; data.reserveTokenIndex = _reserveTokenIndex; (data.decimalScalingFactors, ) = _vault.getPoolTokenRates(address(this)); data.isProjectTokenSwapInBlocked = _blockProjectTokenSwapsIn; data.startTime = _startTime; data.endTime = _endTime; data.startWeights = new uint256[](_TWO_TOKENS); data.startWeights[_projectTokenIndex] = _projectTokenStartWeight; data.startWeights[_reserveTokenIndex] = _reserveTokenStartWeight; data.endWeights = new uint256[](_TWO_TOKENS); data.endWeights[_projectTokenIndex] = _projectTokenEndWeight; data.endWeights[_reserveTokenIndex] = _reserveTokenEndWeight; } struct LBPoolImmutableData { IERC20[] tokens; uint256[] decimalScalingFactors; uint256[] startWeights; uint256[] endWeights; uint256 startTime; uint256 endTime; uint256 projectTokenIndex; uint256 reserveTokenIndex; bool isProjectTokenSwapInBlocked; } ``` * [API](/integration-guides/aggregators/fetching-pools-and-data.md#using-balancers-api) Support: Pool will show as `LIQUIDITY_BOOTSTRAPPING` type and immutable params are available: A sample graphql query is below returning information about a LBP and docs on the `GqlPoolLiquidityBootstrapping` is available at . ```graphql query { poolGetPool(id: "0x812C1217EA39c5242eD1C6D1015EbeD31261E28A", chain: BASE) { id ... on GqlPoolLiquidityBootstrapping { address startTime endTime isProjectTokenSwapInBlocked lbpOwner projectTokenIndex projectToken reserveToken reserveTokenIndex projectTokenStartWeight reserveTokenStartWeight projectTokenEndWeight reserveTokenEndWeight } } } ``` ### Gyro 2-CLP Gyroscope two-token pools that concentrate liquidity in a fungible manner, and can have uncorrelated assets. * [Gyro Docs](https://docs.gyro.finance/pools/2-clps.html) * See SC code implementation [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/Gyro2CLPPool.sol) * [Typescript maths reference](https://github.com/balancer/balancer-maths/blob/main/typescript/src/gyro/gyro2CLPPool.ts) * [Python maths reference](https://github.com/balancer/balancer-maths/blob/main/python/src/pools/gyro/gyro_2clp.py) * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories) - See `Gyro2CLPPoolFactory` * [Gyro pools on Balancer App](https://balancer.fi/pools?poolTypes=GYRO\&protocolVersion=3) * Maths requires the following pool specific immutable parameters: ``` paramsAlpha paramsBeta ``` * These are set at creation and are immutable. * Data can be fetched onchain using the following helpers (see [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/Gyro2CLPPool.sol#L224-L235) and [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/Gyro2CLPPool.sol#L238-L243)): ```solidity function getGyro2CLPPoolDynamicData() external view returns (Gyro2CLPPoolDynamicData memory data); struct Gyro2CLPPoolDynamicData { uint256[] balancesLiveScaled18; uint256[] tokenRates; uint256 staticSwapFeePercentage; uint256 totalSupply; uint256 bptRate; bool isPoolInitialized; bool isPoolPaused; bool isPoolInRecoveryMode; } function getGyro2CLPPoolImmutableData() external view returns (Gyro2CLPPoolImmutableData memory data); struct Gyro2CLPPoolImmutableData { IERC20[] tokens; uint256[] decimalScalingFactors; uint256 sqrtAlpha; uint256 sqrtBeta; } ``` ### Gyro E-CLP Elliptic CLPs, or E-CLPs, allow trading along the curve of an ellipse. Suitable for correlated assets that would be used with Stable Pools. * [Gyro Docs](https://docs.gyro.finance/pools/e-clps.html) * See SC code implementation [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/GyroECLPPool.sol) * [Typescript maths reference](https://github.com/balancer/balancer-maths/blob/main/typescript/src/gyro/gyroECLPPool.ts) * [Python maths reference](https://github.com/balancer/balancer-maths/blob/main/python/src/pools/gyro/gyro_eclp.py) * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories) - See `GyroECLPPoolFactory` * [Gyro pools on Balancer App](https://balancer.fi/pools?poolTypes=GYRO\&protocolVersion=3) * Maths requires the following pool specific immutable parameters: ``` paramsAlpha paramsBeta paramsC paramsS paramsLambda tauAlphaX tauAlphaY tauBetaX tauBetaY u v w z dSq ``` * These are set at creation and are immutable. * Data can be fetched onchain using the following helpers (see [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/GyroECLPPool.sol#L246-L258) and [here](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-gyro/contracts/GyroECLPPool.sol#L260-L278)): ```solidity function getGyroECLPPoolDynamicData() external view returns (GyroECLPPoolDynamicData memory data); struct GyroECLPPoolDynamicData { uint256[] balancesLiveScaled18; uint256[] tokenRates; uint256 staticSwapFeePercentage; uint256 totalSupply; uint256 bptRate; bool isPoolInitialized; bool isPoolPaused; bool isPoolInRecoveryMode; } function getGyroECLPPoolImmutableData() external view returns (GyroECLPPoolImmutableData memory data); struct GyroECLPPoolImmutableData { IERC20[] tokens; uint256[] decimalScalingFactors; int256 paramsAlpha; int256 paramsBeta; int256 paramsC; int256 paramsS; int256 paramsLambda; int256 tauAlphaX; int256 tauAlphaY; int256 tauBetaX; int256 tauBetaY; int256 u; int256 v; int256 w; int256 z; int256 dSq; } ``` * [API](/integration-guides/aggregators/fetching-pools-and-data.md#using-balancers-api) Support: Pool will show as `GYROE` type and immutable params are available: ```graphql query MyQuery { aggregatorPools( where: { chainIn: ARBITRUM, protocolVersionIn: 3, poolTypeIn: GYROE } ) { address type alpha beta c s lambda tauAlphaX tauAlphaY tauBetaX tauBetaY u v w z dSq } } ``` ### AutoRange Pools AutoRange Pools are two-token fungible concentrated liquidity pools conceptually similar to 2-CLPs, but the parameters are not immutable. They can be changed not only by admins, but also by the pool itself, as it automatically adjusts virtual balances to shift the price range as necessary to keep the pool balanced (and earning fees for LPs). \::: warning V2 Changes V1 AutoRange Pools are deprecated in favour of V2. V2 has a small maths fix to handle an edge case when pool is out of range. They have independent factories. If using the API please use `version` to differentiate. \::: * See SC code implementation in this [dedicated repo](https://github.com/balancer/reclamm/blob/main/contracts/ReClammPool.sol) * [Typescript maths reference](https://github.com/balancer/balancer-maths/blob/main/typescript/src/reClammV2/reClammV2Pool.ts) * [Factory Deployment Addresses](https://docs.balancer.fi/developer-reference/contracts/deployment-addresses/mainnet.html#pool-factories) - See `ReClammPoolFactory` * Maths requires the following pool specific parameters: ``` dailyPriceShiftExponent; centerednessMargin; initialMinPrice; initialMaxPrice; initialTargetPrice; tokenAPriceIncludesRate; tokenBPriceIncludesRate; ``` * The "initial params" are set at creation and are immutable. They are only used to facilitate initializing the pool with correct token amounts to avoid arbitration losses. * The `dailyPriceShiftExponent` and `centerednessMargin` can be changed by admins after deployment. * The price shift exponent affects how quickly the pool is allowed to automatically shift the price range to keep the pool in balance. Faster = more responsive to volatility, but also more vulnerable to manipulation. * The centeredness margin determines how sensitive the pool is to swaps that move it toward a more unbalanced state. Higher values mean greater sensitivity: the pool will react quicker to becoming unbalanced (e.g., at 60/40 vs. 80/20). A zero margin is essentially equivalent to a 2-CLP Gyro pool constructed with the same price range. * Many common use cases involved wrapped tokens with rate providers. The rate flags allow the price to be specified using either the wrapped or underlying token prices * Admins can change the sensitivity and behavior of the pool after deployment by setting the margin or price shift exponent. While the price range cannot be set directly while the pool is in operation, it can be narrowed or widened (slowly over time, to prevent manipulation), by changing the ratio of the bounds. * Data can be fetched onchain using the following helpers (see [here](https://github.com/balancer/reclamm/blob/main/contracts/ReClammPool.sol#L709-L738) and [here](https://github.com/balancer/reclamm/blob/main/contracts/ReClammPool.sol#L740-L765)): ```solidity function getReClammPoolDynamicData() external view returns (ReClammPoolDynamicData memory data); struct ReClammPoolDynamicData { // Base Pool uint256[] balancesLiveScaled18; uint256[] tokenRates; uint256 staticSwapFeePercentage; uint256 totalSupply; // ReClamm uint256 lastTimestamp; uint256[] lastVirtualBalances; uint256 dailyPriceShiftExponent; uint256 dailyPriceShiftBase; uint256 centerednessMargin; uint256 currentPriceRatio; uint256 currentFourthRootPriceRatio; uint256 startFourthRootPriceRatio; uint256 endFourthRootPriceRatio; uint32 priceRatioUpdateStartTime; uint32 priceRatioUpdateEndTime; // Pool State bool isPoolInitialized; bool isPoolPaused; bool isPoolInRecoveryMode; } function getReClammPoolImmutableData() external view returns (ReClammPoolImmutableData memory data); struct ReClammPoolImmutableData { // Base Pool IERC20[] tokens; uint256[] decimalScalingFactors; bool tokenAPriceIncludesRate; bool tokenBPriceIncludesRate; uint256 minSwapFeePercentage; uint256 maxSwapFeePercentage; // Initialization uint256 initialMinPrice; uint256 initialMaxPrice; uint256 initialTargetPrice; uint256 initialDailyPriceShiftExponent; uint256 initialCenterednessMargin; // Operating Limits uint256 maxDailyPriceShiftExponent; uint256 maxDailyPriceRatioUpdateRate; uint256 minPriceRatioUpdateDuration; uint256 minPriceRatioDelta; uint256 balanceRatioAndPriceTolerance; } ``` ### QuantAMM BTFs BTFs by QuantAMM dynamically adjust pool weights to capitalize on price movements. For example, a BTF pool can automatically increase its WBTC allocation when the BTF strategy thinks the value will rise faster than ETH. This allows LPs to earn both trading fees and profits from underlying asset appreciation through continuous, responsive, fully on-chain TradFi-style strategies. * [QuantAMM Docs](https://quantamm.fi/documentation) * See SC code implementation [here](https://github.com/QuantAMMProtocol/QuantAMM-V1) * [Typescript maths reference](https://github.com/balancer/balancer-maths/tree/main/typescript/src/quantAmm) * [Python maths reference](https://github.com/balancer/balancer-maths/tree/main/python/src/pools/quantamm) * [BTF pools on Balancer App](https://balancer.fi/pools?poolTypes=QUANT_AMM_WEIGHTED) * [Deployment Addresses](https://mono-test-v3-git-feat-quantamm-support-balancer.vercel.app/pools/ethereum/v3/0xd4ed17bbf48af09b87fd7d8c60970f5da79d4852): ``` Mainnet: * UpdateWeightRunner: 0x21Ae9576a393413D6d91dFE2543dCb548Dbb8748 * QuantAMMWeightedPoolFactory: 0xD5c43063563f9448cE822789651662cA7DcD5773 Arbitrum: * UpdateWeightRunner: 0x8Ca4e2a74B84c1feb9ADe19A0Ce0bFcd57e3f6F7 * QuantAMMWeightedPoolFactory: 0x62B9eC6A5BBEBe4F5C5f46C8A8880df857004295 Base: * UpdateWeightRunner: 0x8Ca4e2a74B84c1feb9ADe19A0Ce0bFcd57e3f6F7 * QuantAMMWeightedPoolFactory: 0x62B9eC6A5BBEBe4F5C5f46C8A8880df857004295 ``` * Maths requires the following dynamic data: ``` firstFourWeightsAndMultipliers secondFourWeightsAndMultipliers lastUpdateTime lastInterpolationTimePossible ``` * Event tracking systems can use the `UpdateWeightRunner`, `WeightsUpdated` [event](https://github.com/QuantAMMProtocol/QuantAMM-V1/blob/main/pkg/pool-quantamm/contracts/UpdateWeightRunner.sol#L93). The UpdateWeight runner is a single contract that controls weight changes for all pools. * Data can also be fetched onchain using the following helpers (see [here](https://github.com/QuantAMMProtocol/QuantAMM-V1/blob/main/pkg/pool-quantamm/contracts/QuantAMMWeightedPool.sol#L594-L612) and [here](https://github.com/QuantAMMProtocol/QuantAMM-V1/blob/main/pkg/pool-quantamm/contracts/QuantAMMWeightedPool.sol#L614-L629)): ```solidity function getQsecondFourWeightsAndMultipliersuantAMMWeightedPoolDynamicData() external view returns (QuantAMMWeightedPoolDynamicData memory data); struct QuantAMMWeightedPoolDynamicData { uint256[] balancesLiveScaled18; uint256[] tokenRates; uint256 totalSupply; bool isPoolInitialized; bool isPoolPaused; bool isPoolInRecoveryMode; int256[] firstFourWeightsAndMultipliers; int256[] ; uint40 lastUpdateTime; uint40 lastInteropTime; } function getQuantAMMWeightedPoolImmutableData() external view returns (QuantAMMWeightedPoolImmutableData memory data); struct QuantAMMWeightedPoolImmutableData { IERC20[] tokens; uint oracleStalenessThreshold; uint256 poolRegistry; int256[][] ruleParameters; uint64[] lambda; uint64 epsilonMax; uint64 absoluteWeightGuardRail; uint64 updateInterval; uint256 maxTradeSizeRatio; } ``` * [API](/integration-guides/aggregators/fetching-pools-and-data.md#using-balancers-api) Support: Pool will show as `QUANT_AMM_WEIGHTED` type: ```graphql query MyQuery { aggregatorPools( where: { chainIn: MAINNET protocolVersionIn: 3 poolTypeIn: QUANT_AMM_WEIGHTED } ) { address type } } ``` * Max trade size: Each BTF pool has a `maxTradeSizeRatio` that is set on [pool creation](https://github.com/QuantAMMProtocol/QuantAMM-V1/blob/main/pkg/interfaces/contracts/pool-quantamm/IQuantAMMWeightedPool.sol#L105). This determines the max amount that can be traded. ## Swap Fees \::: info More Details For more detailed information on Swap Fees please see the [Swap Fee Concepts](/concepts/vault/swap-fee.md) section. \::: A swap fee is charged for each swap, as well as on the non-proportional amounts in add/remove liquidity operations. The swap fee is always charged on the amount in: * EXACT\_IN, on the given amount (see [Vault.sol](https://github.com/balancer/balancer-v3-monorepo/blob/72ccd408936b8786fd4a9e5bd9c7bd6bc08fd991/pkg/vault/contracts/Vault.sol#L370)): ```solidity // Round up to avoid losses during precision loss. locals.totalSwapFeeAmountScaled18 = poolSwapParams.amountGivenScaled18.mulUp(swapState.swapFeePercentage); poolSwapParams.amountGivenScaled18 -= locals.totalSwapFeeAmountScaled18; ``` * EXACT\_OUT, the calculated amount (see [Vault.sol](https://github.com/balancer/balancer-v3-monorepo/blob/72ccd408936b8786fd4a9e5bd9c7bd6bc08fd991/pkg/vault/contracts/Vault.sol#L412))): ```solidity // To ensure symmetry with EXACT_IN, the swap fee used by ExactOut is // `amountCalculated * fee% / (100% - fee%)`. Add it to the calculated amountIn. Round up to avoid losing // value due to precision loss. Note that if the `swapFeePercentage` were 100% here, this would revert with // division by zero. We protect against this by ensuring in PoolConfigLib and HooksConfigLib that all swap // fees (static, dynamic, pool creator, and aggregate) are less than 100%. locals.totalSwapFeeAmountScaled18 = amountCalculatedScaled18.mulDivUp( swapState.swapFeePercentage, swapState.swapFeePercentage.complement() ); ``` Swap fees come in two different forms for V3 pools: * [Static Swap Fee](/concepts/vault/swap-fee.md#setting-a-static-swap-fee): * Initially set as part of the pool registration. * Authorized addresses can then change the value by invoking the vault.setStaticSwapFeePercentage(address pool, uint256 swapFeePercentage) function. * If the staticSwapFeePercentage is changed, it will emit an event: `SwapFeePercentageChanged(pool, swapFeePercentage);` * Note - `setStaticSwapFeePercentage` can also be called as part of a regular [hook](../../concepts/core-concepts/hooks.md) * [Dynamic Swap Fee Using Hooks](/concepts/vault/swap-fee.md#dynamic-swap-fee): * Using Hooks a pool can be set up to use dynamic swap fees. * The Vault uses the [`onComputeDynamicSwapFeePercentage()`](/developer-reference/contracts/hooks-api.html#oncomputedynamicswapfeepercentage) hook to fetch the dynamic swap fee. This function can implement arbitrary logic. * Even when a pool is set to use dynamic swap fees, it still maintains a static swap fee. However, this static fee is not utilized. The pseudo logic to determine how swap fee is calculated looks like: ``` swapFeePercentage = Pool has DynamicSwapFee => call DynamicSwapFeeHook in the pool else => load static Swap fee percentage from Vault ``` ## Useful Resources * [Deployment Addresses](/developer-reference/contracts/deployment-addresses/mainnet.html) * [Router ABI](/developer-reference/contracts/abi/router.md) * [BatchRouter ABI](/developer-reference/contracts/abi/batch-router.md) * [BufferRouter ABI](/developer-reference/contracts/abi/buffer-router.md) * [CompositeLiquidityRouter ABI](/developer-reference/contracts/abi/composite-liquidity-router.md) * [Router API](/developer-reference/contracts/router-api.md) * [BatchRouter API](/developer-reference/contracts/batch-router-api.md) * [BufferRouter API](/developer-reference/contracts/buffer-router-api.md) * [CompositeLiquidityRouter API](/developer-reference/contracts/composite-liquidity-router-api.md) * [Pool Maths Reference](https://github.com/balancer/balancer-sor/blob/master/src/pools/weightedPool/weightedMath.ts) * [Vault API](/developer-reference/contracts/vault-api.md) * [Balancer API docs](/data-and-analytics/data-and-analytics/balancer-api/balancer-api.md) ## LBP Simulator ### What is the simulator? The LBP Simulator is an interactive, client-side tool for modeling a Liquidity Bootstrapping Pool (LBP). It lets you configure a launch/divestment/buyback setup and watch how price, weights, and demand dynamics evolve over time. The goal is to make LBP mechanics more intuitive and to help teams explore tradeoffs before going on-chain. ### What it can do * Configure core LBP parameters (token name/symbol, supply, percentage for sale, start and end weights, duration, fees, initial balances). * Model demand and sell pressure curves to see how different market behaviors affect the price path. * Run the simulation with play/pause controls and adjustable speed. * Visualize outcomes across multiple charts: * Price over time * Sales / swaps * Demand curve * Weight changes * Execute simulated actions with a wallet-style flow: * Instant swaps * Limit orders * TWAP (time‑weighted average price) orders ### How it works (high-level) * The simulator computes price and pool balances step‑by‑step using LBP math. * A client-side store (Zustand) holds configuration, state, and user actions. * Charts and stats are derived from the simulation state and update as inputs change. ### Intended use * Explore LBP configuration strategies. * Compare different demand/sell pressure assumptions. * Communicate launch mechanics with a clear, visual narrative. ### User guide
## LBP Simulator User Guide ### Overview The LBP Simulator lets you configure a Liquidity Bootstrapping Pool (LBP), model buy and sell pressure, and watch how prices, weights, and liquidity evolve over time. It also includes a simulated trading panel for swaps, limit orders, and TWAP. ### Getting Started 1. Open the simulator page. 2. Click `Configure your LBP` or use the keyboard shortcut kbd `⌘ + b` to open the configuration panel. 3. Set your parameters, then press `Play` to run the simulation. 4. Use the chart tabs and stats cards to read results. ### Core Configuration All core settings live in the `Configure your LBP` panel. **Timeline** * `Duration`: Total campaign length in days. This controls how long the LBP runs and how quickly weights shift. * `Play/Pause`: Starts or stops the simulation clock. * `Speed`: Changes how fast the simulation advances (`1x`, `5x`, `10x`). * `Buy & sell` / `Buy only`: Toggles whether sell pressure is included. `Buy only` disables selling pressure by setting it to zero. **Tokenomics** * `Total Supply`: Total token supply used to compute valuation metrics. * `% for Sale`: Share of total supply sold through the LBP. This drives the `Tokens for sale` amount. * `Initial Liquidity (Collateral token)`: Starting collateral balance in the pool (USDC/USDT/WETH). This anchors the starting price. **Weights** * `Start (Token / Collateral)`: Initial pool weights. Higher token weight means a higher starting price. * `End (Token / Collateral)`: Final pool weights at the end of the LBP. * `Collateral Token`: Selects the collateral asset (`USDC`, `USDT`, `WETH`). * `Swap Fee`: Fee charged on swaps (1%–5%). ### Buy Pressure Model Open `Model the buy pressure` to configure market demand assumptions. * `Preset`: `Bullish` (steadier/higher buying) or `Bearish` (lighter early, ramps later). * `Magnitude`: Sets the base scale of cumulative buy volume. * `Multiplier`: Scales the curve up or down (e.g. 0.5x, 2x). This model affects how much simulated buy volume is applied over time. To understand more about the curve modeling, read the article that discusses the study conducted to determine the purchase and sale curves modeled in the app. Modeling a demand pressure curve article ### Sell Pressure Model Open `Model the sell pressure` to configure sell behavior. This is only active when `Buy & sell` is selected. * `Loyal community` * `Total sold share of community tokens (%)`: Approximate fraction of community tokens sold over the whole campaign. * `Concentration at start & end (%)`: How much of the sell activity happens early and late vs. evenly distributed. * `Greedy community` * `Profit spread above cost basis (%)`: Sell trigger threshold above average entry price. * `Portion of holdings sold on trigger (%)`: Portion sold each time the trigger is hit. ### Trading Panel (Simulated) The right-side panel lets you simulate actions using a wallet-style flow. **Swap** * Buy or sell the token instantly at the current simulated price. * Use `Max` to spend your full balance. **Limit** * Set a target price and a maximum spend. * When the simulated price reaches the target, the order executes. **TWAP** * Split a total amount into multiple parts over a time period. * The simulator executes the parts across the chosen duration. All trades are simulated and do not use real funds. ### Results and Charts Use the chart tabs to explore outcomes. * `Price over time`: Spot price evolution during the LBP. When paused, dotted paths show potential price scenarios. * `Sales`: Displays simulated swap activity. * `Demand curve`: Shows buy and sell pressure over time (net pressure included). * `Weights`: Shows how token and collateral weights shift through the sale. ### Key Metrics (Stats Cards) The header and stat cards summarize current results. * `Tokens for sale`: Total tokens allocated to the LBP from `% for Sale`. * `Implied Market Cap`: Current price × tokens for sale. * `Starting price`: Initial spot price. * `Current price`: Current spot price. * `FDV`: Fully diluted valuation (current price × total supply). * `TVL`: Total value locked in the pool. * `Total Raised`: Net collateral accumulated in the pool. * `Total fees`: Approximate fees collected based on swap fee. * `Time remaining`: Remaining time in the simulation. ### Tips * Use a high start token weight and low end token weight to mimic a classic LBP price decay. * Increase initial collateral or reduce `% for Sale` to raise the starting price. * Compare `Bullish` vs `Bearish` buy pressure to see how sensitive pricing is to demand. ## Price Impact Price Impact refers to the change in the price of a token caused by a trade. It is particularly relevant when swapping tokens but is also indirectly connected to adding/removing liquidity. The [Balancer Frontend](https://balancer.fi/pools) calculates and displays Price Impact using different methods depending on the action being taken and some more detail for each is given below. ### Swaps Price impact for swaps is calculated using a simple market-based formula that compares the USD value of tokens going in versus tokens coming out. The main price impact calculation for swaps uses the [calcMarketPriceImpact](https://github.com/balancer/frontend-monorepo/blob/2e07683bcfcfbe60027d460eed7944341647470e/packages/lib/modules/price-impact/price-impact.utils.ts#L53) function. This implements a simple but effective calculation: 1. It compares the USD value of the input token versus the USD value of the output token 2. Uses the formula: `priceImpact = 1 - (usdOut / usdIn)` 3. Only considers negative differences (positive differences are treated as 0) 4. Returns the absolute value as a percentage Note: The calculation is specifically designed for swaps and uses current market prices rather than pool-specific mathematical models. ### Adds/Removes The [Balancer SDK](https://github.com/balancer/b-sdk/blob/main/src/entities/priceImpact/index.ts) is used to calcuate the PI for add/remove operations. The core of price impact calculation in the SDK is the "ABA method." This approach measures price impact by comparing initial and final state after a round-trip operation. (For more details checkout the [deepwiki](https://deepwiki.com/balancer/b-sdk/3.5-price-impact-calculation)) Note: Proporitonal add/remove liquidity operations have zero price impact by design. The SDK's PriceImpact class provides static methods for calculating price impact across different operation types: \| Method | Operation Type | Description | \|----------------------------------|----------------------------------|-----------------------------------------------------------------------------| \| `addLiquiditySingleToken` | Adding a single token | Calculates impact when adding a single token type to a pool | \| `addLiquidityUnbalanced` | Unbalanced liquidity addition | Calculates impact when adding multiple tokens in non-proportional amounts | \| `addLiquidityUnbalancedBoosted` | Boosted pool unbalanced addition | Handles unbalanced addition for pools with ERC4626 tokens | \| `addLiquidityNested` | Nested pool addition | Calculates composite impact when adding to pools containing other pools | \| `removeLiquidity` | Removing liquidity | Calculates impact when removing liquidity from a pool | \| `removeLiquidityNested` | Nested pool removal | Calculates impact when removing from nested pool structures | The psuedo code example showing how PI is calculated for an unbalanced boosted pool add action is shown below ([full code](https://github.com/balancer/frontend-monorepo/blob/07efb962d0f8da94ffb68a9c3a62052216560aa3/packages/lib/modules/pool/actions/add-liquidity/handlers/BoostedUnbalancedAddLiquidityV3.handler.ts#L25)): ```typescript import { PriceImpact, PriceImpactAmount, } from '@balancer/sdk' export class BoostedUnbalancedAddLiquidityV3Handler extends BaseUnbalancedAddLiquidityHandler { public async getPriceImpact(humanAmountsIn: HumanTokenAmountWithAddress[]): Promise { if (areEmptyAmounts(humanAmountsIn)) { // Avoid price impact calculation when there are no amounts in return 0 } const addLiquidityInput = this.constructSdkInput(humanAmountsIn) const priceImpactABA: PriceImpactAmount = await PriceImpact.addLiquidityUnbalancedBoosted( addLiquidityInput, this.helpers.boostedPoolState ) return priceImpactABA.decimal } protected constructSdkInput( humanAmountsIn: HumanTokenAmountWithAddress[], userAddress?: Address ): AddLiquidityUnbalancedInput { const amountsIn = this.helpers.toSdkInputAmounts(humanAmountsIn) return { chainId: this.helpers.chainId, rpcUrl: getRpcUrl(this.helpers.chainId), amountsIn, kind: AddLiquidityKind.Unbalanced, sender: getSender(userAddress), } } } ``` ## Remove Liquidity Guide Balancer v3 supports several different [types of remove liquidity operations](https://docs.balancer.fi/concepts/vault/add-remove-liquidity-types.html#remove-liquidity) ### Core Concepts The core concepts of removing liquidity are the same for any programming language or framework: * When removing liquidity the user sends [Balancer Pool Tokens](../../concepts/core-concepts/balancer-pool-tokens.md) (BPTs), and will receive pool tokens * Use a `permit` signature to approve the Router to spend BPT * Token amount inputs/outputs are always in the raw token scale, e.g. `1 USDC` should be sent as `1000000` because it has 6 decimals * If a pool's tokens include an ERC4626 with an initialized buffer, you have the option to receive the `asset()` of the ERC4626 when removing liquidity. * Transactions are always sent to a [Router](../../concepts/router/overview.md) * Use the standard `Router` to receive standard pool tokens * Use the `CompositeLiquidityRouter` to receive a pool's underlying tokens ### Example Scripts Run example scripts against a local fork of Ethereum mainnet using the [v3 pool operation examples repo](https://github.com/MattPereira/v3-pool-operation-examples/tree/main?tab=readme-ov-file#balancer-v3-pool-operation-examples) ##### TypeScript SDK * [removeLiquidityProportional.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/remove-liquidity/removeLiquidityProportional.ts) * [removeLiquidityProportionalFromERC4626Pool.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/remove-liquidity/removeLiquidityProportionalFromERC4626Pool.ts) * [removeLiquiditySingleTokenExactIn.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/remove-liquidity/removeLiquiditySingleTokenExactIn.ts) * [removeLiquiditySingleTokenExactOut.ts](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/hardhat/remove-liquidity/removeLiquiditySingleTokenExactOut.ts) ##### Solidity * [RemoveLiquidityProportional.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/remove-liquidity/RemoveLiquidityProportional.s.sol) * [RemoveLiquidityProportionalFromERC4626Pool.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/remove-liquidity/RemoveLiquidityProportionalFromERC4626Pool.s.sol) * [RemoveLiquiditySingleTokenExactIn.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/remove-liquidity/RemoveLiquiditySingleTokenExactIn.s.sol) * [RemoveLiquiditySingleTokenExactOut.s.sol](https://github.com/MattPereira/v3-pool-operation-examples/blob/main/scripts/foundry/remove-liquidity/RemoveLiquiditySingleTokenExactOut.s.sol) ### Beginner Tutorials

### Remove Liquidity with Typescript SDK This guide demonstrates how to remove liquidity from a pool. We will use the preferred function for removing liquidity, removeLiquidityProportional. Tokens are removed from the pool in proportional amounts, causing zero price impact and avoiding the swap fee charged when exiting non-proportional. Specifying an exactBptAmountIn ensures that the user will not be left with any dust. See the [Router API](/developer-reference/contracts/router-api.html) for other supported remove methods. *This guide is for removing liquidity from Balancer v3 with the [b-sdk](https://github.com/balancer/b-sdk). This sdk supports removing liquidity from Balancer v3, Balancer v2 as well as Cow-AMMs.* #### Install the Balancer SDK The [Balancer SDK](https://github.com/balancer/b-sdk) is a Typescript/Javascript library for interfacing with the Balancer protocol and can be installed with: \::: code-tabs#shell @tab pnpm ```bash pnpm add @balancer/sdk ``` @tab yarn ```bash yarn add @balancer/sdk ``` @tab npm ```bash npm install @balancer/sdk ``` \::: #### Example Script Run this example script on a local fork of Ethereum mainnet using our [v3 pool operation examples repo](https://github.com/MattPereira/v3-pool-operation-examples/tree/main?tab=readme-ov-file#balancer-v3-pool-operation-examples) The four main helper classes we use from the SDK are: * `BalancerApi` - to simplify retrieving pool data from the Pools API * `RemoveLiquidity` - to build removeLiquidity queries and transactions * `Slippage` - to simplify creating limits with user defined slippage * `PermitHelper` - to simplify creating a permit signature #### Fetch Pool Data In this example we use the BalancerApi `fetchPoolState` function to fetch the pool data required for the removeLiquidityProportional `poolState` parameter. ```typescript const balancerApi = new BalancerApi('https://api-v3.balancer.fi/', chainId); const poolState = await balancerApi.pools.fetchPoolState(pool); ``` To see the full query used to fetch pool state refer to the code [here](https://github.com/balancer/b-sdk/blob/41d2623743ab7fa466ed4d0f5f5c7e5aa16b7d91/src/data/providers/balancer-api/modules/pool-state/index.ts#L7). #### Query remove liquidity [Router queries](../../concepts/router/queries.md) allow for simulation of operations without execution. In this example, when the `query` function is called: ```typescript const queryOutput = await removeLiquidity.query( removeLiquidityInput, poolState ); // queryOutput.amountsOut ``` The Routers [queryRemoveLiquidityUnbalanced](../../developer-reference/contracts/router-api.md#queryremoveliquidityproportional) function is used to find the amount of pool tokens that would be received, `amountsOut`. #### Build the call with permit and slippage The `PermitHelper` abstracts away the complexity involved with creating a permit signature ```typescript const permit = await PermitHelper.signRemoveLiquidityApproval({ ...queryOutput, slippage, client: walletClient.extend(publicActions), owner: walletClient.account, }); ``` Then `buildCallWithPermit` uses the `amountsOut` and the user defined `slippage` to calculate the `minAmountsOut`: ```typescript const call = removeLiquidity.buildCallWithPermit( { ...queryOutput, slippage }, permit2 ); ``` In the full example above, we defined our slippage as `Slippage.fromPercentage('1')`, meaning that we if we do not receive at least 99% of our expected `amountsOut`, the transaction should revert. Internally, the SDK subtracts 1% from the query output, as shown in `Slippage.applyTo` below: ```typescript /** * Applies slippage to an amount in a given direction * * @param amount amount to apply slippage to * @param direction +1 adds the slippage to the amount, and -1 will remove the slippage from the amount * @returns */ public applyTo(amount: bigint, direction: 1 | -1 = 1): bigint { return MathSol.mulDownFixed( amount, BigInt(direction) * this.amount + WAD, ); } ``` #### Send the call The output of the `buildCallWithPermit` function provides all that is needed to submit the removeLiquidity transaction: * `to` - the address of the Router * `callData` - the encoded call data * `value` - the native asset value to be sent It also returns the `minAmountsOut` amounts which can be useful to display/validation purposes before the transaction is sent. ```typescript const hash = await walletClient.sendTransaction({ account: walletClient.account, data: call.callData, to: call.to, value: call.value, }); ``` ### Remove Liquidity with Solidity \::: info This page is a work in progress \::: The following code snippet shows how to remove liquidity from a smart contract. \::: warning Queries should not be used onchain to set minAmountOut due to possible manipulation via frontrunning. \::: ## Querying Swaps Onchain This guide explains how to query swap operations onchain using Balancer V3's querying mechanisms. You'll learn how to get accurate swap quotes without executing transactions, and how to handle scenarios where you need to query the same pool multiple times without state changes affecting subsequent queries. ### Introduction: quote vs quoteAndRevert When building applications that interact with Balancer pools, you often need to query swap outcomes before executing them. Balancer V3 provides two methods for querying swap operations: `quote` and `quoteAndRevert`. Understanding the difference between these methods is crucial, especially for scenarios where you need to query the same pool multiple times or compare different swap scenarios. #### quote The `quote` function performs a callback on `msg.sender` with arguments provided in `data`. It is used to query a set of operations on the Vault. However, `quote` **changes the Vault state** during execution, which means that subsequent queries on the same pool will see the modified state from previous queries. #### quoteAndRevert The `quoteAndRevert` function is a VaultExtension function that also performs a callback on `msg.sender` with arguments provided in `data`. Unlike `quote`, `quoteAndRevert`: * **Always reverts** - The call always reverts, returning the result in the revert reason * **Does not persist state changes** - Since it reverts, any state changes are rolled back * **Allows multiple queries on the same pool** - You can query the same pool multiple times in its initial state * **Only works off-chain** - Only off-chain `eth_call` operations are allowed; anything else will revert * **Non-payable** - The Vault does not allow ETH in these calls #### When to Use quoteAndRevert Since `quote` changes the Vault state, some query combinations are not possible. For example, if you wanted to quote `querySwapExactIn` for `POOL_A` but also query `querySwapExactOut` for `POOL_A` in its initial state, you would need to use `quoteAndRevert`. In this variant, the call always reverts and returns the result in the revert data (similar to the v2 mechanism). **Reference Documentation:** * [Vault API: quoteAndRevert](https://docs.balancer.fi/developer-reference/contracts/vault-api.html#quoteandrevert) * [Router Queries: quoteAndRevert](https://docs.balancer.fi/concepts/router/queries.html#quoteandrevert) ### Contract Flow and Execution #### Callback Mechanism When you call `quoteAndRevert` (or `quote`), the Vault performs a callback on `msg.sender` with the arguments provided in `data`. The Vault tries to call `querySwapHook` on your contract (which is the `msg.sender`). If this function doesn't exist, the call will revert. By implementing this function with your desired logic, you can execute the swap query within the hook. #### Execution Flow The following diagram illustrates the contract flow during `quoteAndRevert` execution: ```mermaid sequenceDiagram participant Client participant CustomRouter participant Vault participant Pool Client->>CustomRouter: querySwap() CustomRouter->>Vault: quoteAndRevert(encodedHookParams) Note over Vault: Vault decodes params
and prepares callback Vault->>CustomRouter: querySwapHook(params) Note over CustomRouter: Hook function executes CustomRouter->>Pool: vault.swap(swapParams) Pool-->>CustomRouter: amountOut CustomRouter-->>Vault: return amountOut Note over Vault: Vault wraps result
in Result error Vault-->>CustomRouter: revert Result(bytes) Note over CustomRouter: Catch revert and decode CustomRouter->>CustomRouter: RevertCodec.catchEncodedResult() CustomRouter-->>Client: amountOut (decoded) ``` #### Setup Requirements To use `quoteAndRevert`, you need: 1. **Custom Contract**: You must deploy a separate contract that implements the `querySwapHook` function. This is required because `vault.quote` (and `quoteAndRevert`) performs a callback on `msg.sender` with the encoded function call. 2. **Hook Implementation**: The `querySwapHook` function must: * Be marked as `external` (called by the Vault) * Accept the hook parameters (typically `IRouter.SwapSingleTokenHookParams`) * Perform the actual swap query by calling `vault.swap()` * Return the result (which will be encoded in the revert) 3. **Revert Decoding**: You need a mechanism to decode the revert data. The result is wrapped in a `Result(bytes)` error, which needs to be extracted and decoded. 4. **Off-chain Execution**: The call must be made via `eth_call` (static call). For testing with Foundry, you'll need to use `vm.prank(address(0), address(0))` to simulate the off-chain call environment. **Interface Reference:** * [IVaultExtension.sol](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/vault/IVaultExtension.sol#L440) ### Code Example The following complete example demonstrates how to use `quoteAndRevert` to query two consecutive swaps on the same pool: ```solidity // SPDX-License-Identifier: MIT pragma solidity ^0.8.22; import "forge-std/Script.sol"; import {console} from "forge-std/console.sol"; import {IRouter} from "@balancer-v3-monorepo/interfaces/vault/IRouter.sol"; import {IVault} from "@balancer-v3-monorepo/interfaces/vault/IVault.sol"; import {IVaultMain} from "@balancer-v3-monorepo/interfaces/vault/IVaultMain.sol"; import {SwapKind, VaultSwapParams} from "@balancer-v3-monorepo/interfaces/vault/VaultTypes.sol"; import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; import {IPermit2} from "@permit2/interfaces/IPermit2.sol"; // This has to be deployed as a separate contract because vault.quote performs a callback on msg.sender with the encoded function call contract CustomRouterQuoteAndRevert { IVault public constant vault = IVault(0xbA1333333333a1BA1108E8412f11850A5C319bA9); function querySwap() public returns (uint256) { console.log("querySwap"); address pool = 0x85B2b559bC2D21104C4DEFdd6EFcA8A20343361D; IERC20 tokenIn = IERC20(0xC71Ea051a5F82c67ADcF634c36FFE6334793D24C); IERC20 tokenOut = IERC20(0xD4fa2D31b7968E448877f69A96DE69f5de8cD23E); uint256 exactAmountIn = 100000000000000000000; bytes memory userData = new bytes(0); // First swap query try vault // calls back from vault to querySwapHook .quoteAndRevert( abi.encodeCall( CustomRouterQuoteAndRevert.querySwapHook, IRouter.SwapSingleTokenHookParams({ sender: msg.sender, kind: SwapKind.EXACT_IN, pool: pool, tokenIn: tokenIn, tokenOut: tokenOut, amountGiven: exactAmountIn, limit: 0, deadline: type(uint256).max, wethIsEth: false, userData: userData }) ) ) { revert("Unexpected success"); } catch (bytes memory result) { uint256 amountOut = abi.decode(RevertCodec.catchEncodedResult(result), (uint256)); console.log("amountOut 1 (CustomRouterQuoteAndRevert):", amountOut); } // Second call should not be affected by the first call try vault // calls back from vault to querySwapHook .quoteAndRevert( abi.encodeCall( CustomRouterQuoteAndRevert.querySwapHook, IRouter.SwapSingleTokenHookParams({ sender: msg.sender, kind: SwapKind.EXACT_IN, pool: pool, tokenIn: tokenIn, tokenOut: tokenOut, amountGiven: exactAmountIn, limit: 0, deadline: type(uint256).max, wethIsEth: false, userData: userData }) ) ) { revert("Unexpected success"); } catch (bytes memory result) { uint256 amountOut = abi.decode(RevertCodec.catchEncodedResult(result), (uint256)); console.log("amountOut 2 (CustomRouterQuoteAndRevert):", amountOut); return amountOut; } } // this function is called from the vault function querySwapHook(IRouter.SwapSingleTokenHookParams calldata params) external returns (uint256) { console.log("querySwapHook (CustomRouter)"); (uint256 amountCalculated, uint256 amountIn, uint256 amountOut) = vault.swap( VaultSwapParams({ kind: params.kind, pool: params.pool, tokenIn: params.tokenIn, tokenOut: params.tokenOut, amountGivenRaw: params.amountGiven, limitRaw: params.limit, userData: params.userData }) ); return amountOut; } } contract QuoteAndRevert is Script { IVault public constant vault = IVault(0xbA1333333333a1BA1108E8412f11850A5C319bA9); function run() public returns (uint256) { runQuote(); //runQuoteAndRevert(); return 0; } function runQuoteAndRevert() public returns (uint256) { vm.startBroadcast(); CustomRouterQuoteAndRevert customRouter = new CustomRouterQuoteAndRevert(); vm.stopBroadcast(); // Use vm.prank with address(0) for both msg.sender and tx.origin to simulate a static call // This is required because vault.quote() checks that tx.origin == address(0) vm.prank(address(0), address(0)); uint256 amountOut = customRouter.querySwap(); console.log("amountOut:", amountOut); return 0; } } /// @notice Support `quoteAndRevert`: a v2-style query which always reverts, and returns the result in the return data. library RevertCodec { /** * @notice On success of the primary operation in a `quoteAndRevert`, this error is thrown with the return data. * @param result The result of the query operation */ error Result(bytes result); /// @notice Handle the "reverted without a reason" case (i.e., no return data). error ErrorSelectorNotFound(); function catchEncodedResult(bytes memory resultRaw) internal pure returns (bytes memory) { bytes4 errorSelector = RevertCodec.parseSelector(resultRaw); if (errorSelector != Result.selector) { // Bubble up error message if the revert reason is not the expected one. RevertCodec.bubbleUpRevert(resultRaw); } uint256 resultRawLength = resultRaw.length; assembly ("memory-safe") { resultRaw := add(resultRaw, 0x04) // Slice the sighash mstore(resultRaw, sub(resultRawLength, 4)) // Set proper length } return abi.decode(resultRaw, (bytes)); } /// @dev Returns the first 4 bytes in an array, reverting if the length is < 4. function parseSelector(bytes memory callResult) internal pure returns (bytes4 errorSelector) { if (callResult.length < 4) { revert ErrorSelectorNotFound(); } assembly ("memory-safe") { errorSelector := mload(add(callResult, 0x20)) // Load the first 4 bytes from data (skip length offset) } } /// @dev Taken from Openzeppelin's Address. function bubbleUpRevert(bytes memory returnData) internal pure { // Look for revert reason and bubble it up if present. if (returnData.length > 0) { // The easiest way to bubble the revert reason is using memory via assembly. assembly ("memory-safe") { let return_data_size := mload(returnData) revert(add(32, returnData), return_data_size) } } else { revert ErrorSelectorNotFound(); } } } ``` ### Code Walkthrough #### querySwap() Function The `querySwap()` function demonstrates how to make consecutive queries on the same pool: 1. **Encoding Hook Parameters**: The function prepares swap parameters and encodes them using `abi.encodeCall()` to create the callback data: ```solidity abi.encodeCall( CustomRouterQuoteAndRevert.querySwapHook, IRouter.SwapSingleTokenHookParams({...}) ) ``` 2. **Calling quoteAndRevert**: The encoded parameters are passed to `vault.quoteAndRevert()`, which will: * Decode the parameters * Call back to `querySwapHook` on the contract * Wrap the result in a `Result(bytes)` error and revert 3. **Catching and Decoding**: The function uses a `try-catch` block to catch the revert: ```solidity catch (bytes memory result) { uint256 amountOut = abi.decode(RevertCodec.catchEncodedResult(result), (uint256)); } ``` 4. **Consecutive Queries**: The function makes two identical queries. Because `quoteAndRevert` reverts (rolling back state), the second query sees the pool in its original state, unaffected by the first query. #### querySwapHook() Function The `querySwapHook()` function is called by the Vault during `quoteAndRevert` execution: 1. **External Callback**: The function is marked as `external` because it's called by the Vault (not by your contract directly). 2. **Parameter Reception**: It receives `IRouter.SwapSingleTokenHookParams` containing all the swap details (pool, tokens, amounts, etc.). 3. **Swap Execution**: The function calls `vault.swap()` with the provided parameters to perform the actual swap query: ```solidity (uint256 amountCalculated, uint256 amountIn, uint256 amountOut) = vault.swap( VaultSwapParams({...}) ); ``` 4. **Return Value**: The function returns `amountOut`, which the Vault will encode and include in the revert data. #### RevertCodec Library The `RevertCodec` library handles decoding the revert data from `quoteAndRevert`: 1. **Result Error**: The library defines a `Result(bytes result)` error type. When `quoteAndRevert` succeeds, the Vault throws this error with the return data encoded. 2. **catchEncodedResult()**: This is the main decoding function: * **Parse Selector**: Extracts the first 4 bytes (error selector) to verify it's the `Result` error * **Validate**: If the selector doesn't match, it bubbles up the actual revert reason * **Slice Data**: Removes the 4-byte selector from the data * **Decode**: Decodes the remaining bytes to extract the actual result 3. **parseSelector()**: Extracts the error selector (first 4 bytes) from the revert data using assembly for efficiency. 4. **bubbleUpRevert()**: If the revert wasn't a `Result` error, this function re-throws the original revert reason, allowing proper error propagation. #### Revert Decoding Process The revert decoding process works as follows: 1. **Vault Reverts**: After executing the hook, the Vault wraps the return value in a `Result(bytes)` error and reverts. 2. **Catch Revert**: Your code catches the revert in a `try-catch` block, receiving the raw bytes. 3. **Extract Selector**: `parseSelector()` reads the first 4 bytes to identify the error type. 4. **Validate**: If it's not a `Result` error, the original revert is re-thrown. 5. **Decode**: If it is a `Result` error, the selector is removed and the remaining bytes are decoded to extract your return value. #### Testing with address(0) Workaround When testing with Foundry, you need to simulate the off-chain call environment: ```solidity // Use vm.prank with address(0) for both msg.sender and tx.origin to simulate a static call // This is required because vault.quote() checks that tx.origin == address(0) vm.prank(address(0), address(0)); uint256 amountOut = customRouter.querySwap(); ``` **Why this is needed:** * `quoteAndRevert` only works with off-chain `eth_call` operations * The Vault checks that `tx.origin == address(0)` to ensure it's an off-chain call * In Foundry tests, `vm.prank(address(0), address(0))` sets both `msg.sender` and `tx.origin` to `address(0)`, simulating the off-chain environment * Without this, the Vault will revert with: "Only off-chain eth\_call are allowed, anything else will revert." This workaround allows you to test `quoteAndRevert` functionality in a Foundry script environment while maintaining the same behavior as an actual off-chain `eth_call`. ## Swapping with Custom Paths with the Router This guide illustrates the process of executing swaps through the router once swap paths have been established. The examples provided encompass both single and multi-path swap types, focusing on exactIn swaps. For additional information on exactOut swaps, please refer to the [Router API](../../developer-reference/contracts/router-api.md) documentation. *To use the Balancer Smart Order Router to find efficient swap paths for a pair see [this guide](./swaps-with-sor-sdk.md).* *This guide is for Swapping on Balancer v3. The sdk supports swapping on v3 and Balancer v2.* ### Core Concepts The core concepts of executing Swaps are the same for any programming language or framework: * The sender must approve the Vault (not the Router) for each swap input token * Token amount inputs/outputs are always in the raw token scale, e.g. `1 USDC` should be sent as `1000000` because it has 6 decimals * Transactions are always sent to the [Router](../../developer-reference/contracts/router-api.md) * There are two different swap kinds: * ExactIn: Where the user provides an exact input token amount. * ExactOut: Where the user provides an exact output token amount. * There are two subsets of a swap: * Single Swap: A swap, tokenIn > tokenOut, using a single pool. This is the most gas efficient option for a swap of this kind. * Multi-path Swaps: Swaps involving multiple paths but all executed in the same transaction. Each path can have its own (or the same) tokenIn/tokenOut. The following sections provide specific implementation details for Javascript (with and without the SDK) and Solidity. ### Custom Paths With The SDK The SDK `Swap` object provides functionality to easily fetch updated swap quotes and create swap transactions with user defined slippage protection. ```typescript import { ChainId, Slippage, SwapKind, Swap, SwapBuildOutputExactIn, ExactInQueryOutput } from "@balancer/sdk"; import { Address } from "viem"; // User defined const swapInput = { chainId: ChainId.SEPOLIA, swapKind: SwapKind.GivenIn, paths: [ { pools: ["0x1e5b830439fce7aa6b430ca31a9d4dd775294378" as Address], tokens: [ { address: "0xb19382073c7a0addbb56ac6af1808fa49e377b75" as Address, decimals: 18, }, // tokenIn { address: "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773" as Address, decimals: 18, }, // tokenOut ], vaultVersion: 3 as const, inputAmountRaw: 1000000000000000000n, outputAmountRaw: 990000000000000000n, }, ], }; // Swap object provides useful helpers for re-querying, building call, etc const swap = new Swap(swapInput); console.log( `Input token: ${swap.inputAmount.token.address}, Amount: ${swap.inputAmount.amount}` ); console.log( `Output token: ${swap.outputAmount.token.address}, Amount: ${swap.outputAmount.amount}` ); // Get up to date swap result by querying onchain const updatedOutputAmount = await swap.query(RPC_URL) as ExactInQueryOutput; console.log(`Updated amount: ${updatedOutputAmount.expectedAmountOut}`); // Build call data using user defined slippage const callData = swap.buildCall({ slippage: Slippage.fromPercentage("0.1"), // 0.1%, deadline: 999999999999999999n, // Deadline for the swap, in this case infinite queryOutput: updatedOutputAmount, wethIsEth: false }) as SwapBuildOutputExactIn; console.log( `Min Amount Out: ${callData.minAmountOut.amount}\n\nTx Data:\nTo: ${callData.to}\nCallData: ${callData.callData}\nValue: ${callData.value}` ); ``` #### Install the Balancer SDK The [Balancer SDK](https://github.com/balancer/b-sdk) is a Typescript/Javascript library for interfacing with the Balancer protocol and can be installed with: \::: code-tabs#shell @tab pnpm ```bash pnpm add @balancer/sdk ``` @tab yarn ```bash yarn add @balancer/sdk ``` @tab npm ```bash npm install @balancer/sdk ``` \::: The two main helper classes we use from the SDK are: * `Swap` - to build swap queries and transactions * `Slippage` - to simplify creating limits with user defined slippage #### Providing Custom Paths To The Balancer SDK `swapInput` must be of the following type: ```typescript type SwapInput = { chainId: number; paths: Path[]; swapKind: SwapKind; }; ``` * `chainId` - the chain the swap is valid for * `swapKind` - either a `GivenIn` or `GivenOut` * `paths` - An array of paths that define a swap from a tokenIn>tokenOut where a path looks like: ```typescript type Path = { pools: Address[] | Hex[]; tokens: TokenApi[]; outputAmountRaw: bigint; inputAmountRaw: bigint; vaultVersion: 2 | 3; }; ``` * `pools` - an array of pools that will be swapped against, ordered sequentially for the path. * `tokens` - an array of tokens that will be swapped to/from, ordered sequentially for the path. `tokens[0]` is the initial `tokenIn` and `tokens[length-1]` is the final `tokenOut` for the path. * `inputAmountRaw`/`outputAmountRaw` - the final input/output amounts for the path. * `vaultVersion` - the version of the Balancer protocol. Note each path must use the same vaultVersion. Using the input given above as an illustrative example: ```typescript const swapInput = { chainId: ChainId.SEPOLIA, swapKind: SwapKind.GivenIn, paths: [ { pools: ["0x1e5b830439fce7aa6b430ca31a9d4dd775294378" as Address], tokens: [ { address: "0xb19382073c7a0addbb56ac6af1808fa49e377b75" as Address, decimals: 18, }, // tokenIn { address: "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773" as Address, decimals: 18, }, // tokenOut ], vaultVersion: 3 as const, inputAmountRaw: 1000000000000000000n, outputAmountRaw: 990000000000000000n, }, ], }; ``` We can infer: * The swap is of the GivenIn type and is valid for Balancer v3 on Sepolia * There is one path swapping: * token: `0xb19382073c7a0addbb56ac6af1808fa49e377b75` to `0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773` * using pool: `0x1e5b830439fce7aa6b430ca31a9d4dd775294378` * with an input amount of `1000000000000000000` (or 1 scaled to human format) * with an output amount of `990000000000000000` (or 9.9 scaled to human format) #### Queries and safely setting slippage limits [Router queries](../../concepts/router/queries.md) allow for simulation of operations without execution. In this example, when the `query` function is called: ``` const updatedOutputAmount = await swap.query(RPC_URL) as ExactInQueryOutput; ``` An onchain call is used to find an updated result for the swap paths, in this case the amount of token out that would be received, `updatedOutputAmount`, given the original `inputAmountRaw` as the input. In the next step `buildCall` uses the `updatedOutputAmount` and the user defined `slippage` to calculate the `minAmountOut`: ```typescript const callData = swap.buildCall({ slippage: Slippage.fromPercentage("1"), // 1%, deadline: 999999999999999999n, // Deadline for the swap, in this case infinite queryOutput: updatedOutputAmount, wethIsEth: false }) as SwapBuildOutputExactIn; ``` In the full example above, we defined our slippage as `Slippage.fromPercentage('1')`, meaning that we if we do not receive at least 99% of our expected `updatedOutputAmount`, the transaction should revert. Internally, the SDK subtracts 1% from the query output, as shown in `Slippage.applyTo` below: ```typescript /** * Applies slippage to an amount in a given direction * * @param amount amount to apply slippage to * @param direction +1 adds the slippage to the amount, and -1 will remove the slippage from the amount * @returns */ public applyTo(amount: bigint, direction: 1 | -1 = 1): bigint { return MathSol.mulDownFixed( amount, BigInt(direction) * this.amount + WAD, ); } ``` #### Constructing the call The output of the `buildCall` function provides all that is needed to submit the Swap transaction: * `to` - the address of the Router * `callData` - the encoded call data * `value` - the native asset value to be sent It also returns the `minAmountOut` amount which can be useful to display/validation purposes before the transaction is sent. ### Custom Paths Without The SDK The following section illustrates swap operations on the Router through examples implemented in Javascript and Solidity. #### Single Swap The following code examples demonstrate how to execute a single token swap specifying an exact input token amount. To achieve this, we use two Router functions: * [`swapSingleTokenExactIn`](../../developer-reference/contracts/router-api.md#swapsingletokenexactin) - Execute a swap specifying an exact input token amount. * [`querySwapSingleTokenExactIn`](../../developer-reference/contracts/router-api.md#queryswapsingletokenexactin) - The [router query](../../concepts/router/queries.md) used to simulate a swap. It returns the exact amount of token out that would be received. The Router interface for `swapSingleTokenExactIn` is: ```solidity /** * @notice Executes a swap operation specifying an exact input token amount. * @param pool Address of the liquidity pool * @param tokenIn Token to be swapped from * @param tokenOut Token to be swapped to * @param exactAmountIn Exact amounts of input tokens to send * @param minAmountOut Minimum amount of tokens to be received * @param deadline Deadline for the swap * @param userData Additional (optional) data required for the swap * @param wethIsEth If true, incoming ETH will be wrapped to WETH; otherwise the Vault will pull WETH tokens * @return amountOut Calculated amount of output tokens to be received in exchange for the given input tokens */ function swapSingleTokenExactIn( address pool, IERC20 tokenIn, IERC20 tokenOut, uint256 exactAmountIn, uint256 minAmountOut, uint256 deadline, bool wethIsEth, bytes calldata userData ) external payable returns (uint256 amountOut); ``` * `exactAmountIn` defines the exact amount of tokenIn to send. * `minAmountOut` defines the minimum amount of tokenOut to receive. If the amount is less than this (e.g. because of slippage) the transaction will revert * If `wethIsEth` is set to `true`, the Router will deposit the `exactAmountIn` of `ETH` into the `WETH` contract. So, the transaction must be sent with the appropriate `value` amount * `deadline` the UNIX timestamp at which the swap must be completed by - if the transaction is confirmed after this time then the transaction will fail. * `userData` allows additional parameters to be provided for custom pool types. In most cases it is not required and a value of `0x` can be provided. ##### Javascript Without SDK **Resources**: * [Router ABI](../../developer-reference/contracts/abi/router.md) * [Router deployment addresses](../../reference/contracts) \::: code-tabs#shell @tab Viem ```typescript import { createPublicClient, createWalletClient, http } from "viem"; import { sepolia } from "viem/chains"; // Query operation const client = createPublicClient({ transport: http(RPC_URL), chain: sepolia, }); const { result: amountOut } = await client.simulateContract({ address: routerAddress, abi: routerAbi, functionName: "querySwapSingleTokenExactIn", args: [ "0x1e5b830439fce7aa6b430ca31a9d4dd775294378", // pool address "0xb19382073c7a0addbb56ac6af1808fa49e377b75", // tokenIn "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773", // tokenOut 1000000000000000000n, // exactAmountIn "0x", // userData ], }); // Sending transaction const walletClient = createWalletClient({ chain: sepolia, transport: http(RPC_URL), }); const hash = await walletClient.writeContract({ address: routerAddress, abi: routerAbi, functionName: "swapSingleTokenExactIn", args: [ "0x1e5b830439fce7aa6b430ca31a9d4dd775294378", // pool address "0xb19382073c7a0addbb56ac6af1808fa49e377b75", // tokenIn "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773", // tokenOut 1000000000000000000n, // exactAmountIn 900000000000000000n, // minAmountOut 999999999999999999n, // Deadline, in this case infinite false, // wethIsEth for Eth wrapping "0x", // userData ], account: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", }); ``` @tab Ethers ```typescript // Query operation const provider = new JsonRpcProvider(RPC_URL); const router = new Contract(routerAddress, routerAbi, provider); const amountsOut = await router.querySwapSingleTokenExactIn.staticCall( "0x1e5b830439fce7aa6b430ca31a9d4dd775294378", // pool address 100000000000000000n, // token amounts in raw form "0x" // userData, set to 0x in most scenarios ); // Sending transaction const tx = await router.swapSingleTokenExactIn( "0x1e5b830439fce7aa6b430ca31a9d4dd775294378", // pool address "0xb19382073c7a0addbb56ac6af1808fa49e377b75", // tokenIn "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773", // tokenOut 1000000000000000000n, // exactAmountIn 900000000000000000n, // minAmountOut 999999999999999999n, // Deadline, in this case infinite false, // wethIsEth for Eth wrapping "0x" // userData ); ``` \::: ##### Solidity \::: warning Queries should not be used onchain to set minAmountOut due to possible manipulation via frontrunning. \::: ```solidity pragma solidity ^0.8.4; // TODO - Assume there will be interface type package? Needs updated when released. import "@balancer-labs/...../IRouter.sol"; contract SingleSwap { IRouter public router; constructor(IRouter _router) { router = _router; } function singleSwap( address pool, IERC20 tokenIn, IERC20 tokenOut, uint256 exactAmountIn, uint256 minAmountOut, uint256 deadline, bool wethIsEth, bytes calldata userData ) external { router.swapSingleTokenExactIn( pool, tokenIn, tokenOut, exactAmountIn, minAmountOut, deadline, wethIsEth, userData ); } } ``` #### Multi Path Swap :::warning Multi-path Swaps use the Balancer [BatchRouter](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks/20241205-v3-batch-router) ::: The following code examples demonstrate how to execute a multi path swap specifying exact input token amounts. To achieve this, we use two Router functions: * [`swapExactIn`](../../developer-reference/contracts/router-api.md#swapexactin) - Execute a swap involving multiple paths, specifying exact input token amounts. * [`querySwapExactIn`](../../developer-reference/contracts/router-api.md#queryswapexactin) - The [router query](../../concepts/router/queries.md) used to simulate a swap. It returns the exact amount of token out for each swap path. The Router interface for `swapExactIn` is: ```solidity /** * @notice Executes a swap operation involving multiple paths (steps), specifying exact input token amounts. * @param paths Swap paths from token in to token out, specifying exact amounts in. * @param deadline Deadline for the swap * @param wethIsEth If true, incoming ETH will be wrapped to WETH; otherwise the Vault will pull WETH tokens * @param userData Additional (optional) data required for the swap * @return pathAmountsOut Calculated amounts of output tokens corresponding to the last step of each given path * @return tokensOut Calculated output token addresses * @return amountsOut Calculated amounts of output tokens, ordered by output token address */ function swapExactIn( SwapPathExactAmountIn[] memory paths, uint256 deadline, bool wethIsEth, bytes calldata userData ) external payable returns (uint256[] memory pathAmountsOut, address[] memory tokensOut, uint256[] memory amountsOut); ``` * `deadline` the UNIX timestamp at which the swap must be completed by - if the transaction is confirmed after this time then the transaction will fail. * If `wethIsEth` is set to `true`, the Router will deposit the `exactAmountIn` of `ETH` into the `WETH` contract. So, the transaction must be sent with the appropriate `value` amount * `userData` allows additional parameters to be provided for custom pool types. In most cases it is not required and a value of `0x` can be provided. * `paths` an array of swap paths, in this case `SwapPathExactAmountIn`, that have a number of steps, `SwapPathStep`, to swap a given tokenIn to tokenOut: ```solidity struct SwapPathStep { address pool; IERC20 tokenOut; // If true, the "pool" is an ERC4626 Buffer. Used to wrap/unwrap tokens if pool doesn't have enough liquidity. bool isBuffer; } struct SwapPathExactAmountIn { IERC20 tokenIn; // for each step: // if tokenIn == pool use removeLiquidity SINGLE_TOKEN_EXACT_IN // if tokenOut == pool use addLiquidity UNBALANCED SwapPathStep[] steps; uint256 exactAmountIn; uint256 minAmountOut; } ``` * each `path` defines a `minAmountOut`. If the amount of `tokenOut` is less than this (e.g. because of slippage) the transaction will revert * pool add/remove operations can be included in the path by using a pool address as tokenIn/Out * tokenIn == pool: router will remove liquidity from pool to a single token, `tokenOut` * tokenOut == pool: router will add liquidity using `tokenIn` * isBuffer: if true, this means the "pool" address is actually an ERC4626 wrapped token, and we want to use the associated buffer ##### Javascript **Resources**: * [Batch Router ABI](../../developer-reference/contracts/abi/batch-router.md) * [Batch Router deployment addresses](../../reference/contracts) \::: code-tabs#shell @tab Viem ```typescript // query operation const client = createPublicClient({ transport: http(RPC_URL), chain: sepolia, }); /* Two paths to swap 0xf043 > 0xb193: * 0xf043[0xb816]0x7b79[0x6ad4]0xb193 * 0xf043[0x1e5b]0xb193 */ const paths = [ { tokenIn: "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773" as Address, exactAmountIn: 1000000000000000000n, minAmountOut: 0n, steps: [ { pool: "0xb816c48b18925881ce8b64717725c7c9842429e4" as Address, tokenOut: "0x7b79995e5f793a07bc00c21412e50ecae098e7f9" as Address, isBuffer: false, }, { pool: "0x6ad4e679c5bd9a14c50a81bd5f928a2a5ba7ec80" as Address, tokenOut: "0xb19382073c7a0addbb56ac6af1808fa49e377b75" as Address, isBuffer: false, }, ], }, { tokenIn: "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773" as Address, exactAmountIn: 1000000000000000000n, minAmountOut: 0n, steps: [ { pool: "0x1e5b830439fce7aa6b430ca31a9d4dd775294378" as Address, tokenOut: "0xb19382073c7a0addbb56ac6af1808fa49e377b75" as Address, isBuffer: false, }, ], }, ]; const { result: tokensOut, result: amountsOut, result: pathAmountsOut } = await client.simulateContract({ address: batchRouterAddress, abi: batchRouterAbi, functionName: "querySwapExactIn", args: [ paths, "0x", // userData ], }); // Sending transaction const hash = await walletClient.writeContract({ address: batchRouterAddress, abi: batchRouterAbi, functionName: "swapExactIn", args: [ paths, 999999999999999999n, // Deadline, in this case infinite false, // wethIsEth for Eth wrapping "0x", // userData ], account: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", }); ``` @tab Ethers ```typescript // Query operation /* Two paths to swap 0xf043 > 0xb193: * 0xf043[0xb816]0x7b79[0x6ad4]0xb193 * 0xf043[0x1e5b]0xb193 */ const paths = [ { tokenIn: "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773", exactAmountIn: 1000000000000000000n, minAmountOut: 0n, steps: [ { pool: "0xb816c48b18925881ce8b64717725c7c9842429e4", tokenOut: "0x7b79995e5f793a07bc00c21412e50ecae098e7f9", isBuffer: false, }, { pool: "0x6ad4e679c5bd9a14c50a81bd5f928a2a5ba7ec80", tokenOut: "0xb19382073c7a0addbb56ac6af1808fa49e377b75", isBuffer: false, }, ], }, { tokenIn: "0xf04378a3ff97b3f979a46f91f9b2d5a1d2394773", exactAmountIn: 1000000000000000000n, minAmountOut: 0n, steps: [ { pool: "0x1e5b830439fce7aa6b430ca31a9d4dd775294378", tokenOut: "0xb19382073c7a0addbb56ac6af1808fa49e377b75", isBuffer: false, }, ], }, ]; const provider = new JsonRpcProvider(RPC_URL); const router = new Contract(batchRouterAddress, routerAbi, provider); const result = await router.querySwapExactIn.staticCall(paths, "0x"); console.log(result.tokensOut); console.log(result.amountsOut); console.log(result.pathAmountsOut); // Sending transaction const tx = await router.swapExactIn( paths, 999999999999999999n, // Deadline, in this case infinite false, // wethIsEth for Eth wrapping "0x" // userData ); ``` \::: ##### Solidity \::: warning Queries should not be used onchain to set minAmountOut due to possible manipulation via frontrunning. \::: ```solidity pragma solidity ^0.8.4; // TODO - Assume there will be interface type package? Needs updated when released. import "@balancer-labs/...../IRouter.sol"; contract MultiPathSwap { IRouter public router; constructor(IRouter _router) { router = _router; } function multiPathSwap( SwapPathExactAmountIn[] memory paths, uint256 deadline, bool wethIsEth, bytes calldata userData ) external { router.swapExactIn( paths, deadline, wethIsEth, userData ); } } ``` ## Swapping with the Balancer Smart Order Router and SDK This guide showcases the capabilities of the Balancer Smart Order Router (SOR) accessible through the Balancer API, focusing on its ability to identify optimal swap paths for a given token pair. Subsequently, we explore the process of utilizing the SDK to seamlessly create and execute swap transactions. *This guide uses the Balancer API SOR which will find the best result using v2 and v3 liquidity. The SDK supports both.* ```typescript import { BalancerApi, ChainId, Slippage, SwapKind, Token, TokenAmount, Swap, SwapBuildOutputExactIn, SwapBuildCallInput, ExactInQueryOutput } from "@balancer/sdk"; // User defined const chainId = ChainId.MAINNET; const swapKind = SwapKind.GivenIn; const tokenIn = new Token( chainId, "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", 18, "WETH" ); const tokenOut = new Token( chainId, "0xba100000625a3754423978a60c9317c58a424e3D", 18, "BAL" ); const wethIsEth = false; // If true, incoming ETH will be wrapped to WETH, otherwise the Vault will pull WETH tokens const deadline = 999999999999999999n; // Deadline for the swap, in this case infinite const slippage = Slippage.fromPercentage("0.1"); // 0.1% const swapAmount = TokenAmount.fromHumanAmount(tokenIn, "1.2345678910"); // API is used to fetch best swap paths from available liquidity across v2 and v3 const balancerApi = new BalancerApi( "https://api-v3.balancer.fi/", chainId ); const sorPaths = await balancerApi.sorSwapPaths.fetchSorSwapPaths({ chainId, tokenIn: tokenIn.address, tokenOut: tokenOut.address, swapKind, swapAmount, }); // Swap object provides useful helpers for re-querying, building call, etc const swap = new Swap({ chainId, paths: sorPaths, swapKind, }); console.log( `Input token: ${swap.inputAmount.token.address}, Amount: ${swap.inputAmount.amount}` ); console.log( `Output token: ${swap.outputAmount.token.address}, Amount: ${swap.outputAmount.amount}` ); // Get up to date swap result by querying onchain const updated = await swap.query(RPC_URL) as ExactInQueryOutput; console.log(`Updated amount: ${updated.expectedAmountOut.amount}`); let buildInput: SwapBuildCallInput; // In v2 the sender/recipient can be set, in v3 it is always the msg.sender if (swap.protocolVersion === 2) { buildInput = { slippage, deadline, queryOutput: updated, wethIsEth, sender: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", recipient: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", }; } else { buildInput = { slippage, deadline, queryOutput: updated, wethIsEth, }; } const callData = swap.buildCall(buildInput) as SwapBuildOutputExactIn; console.log( `Min Amount Out: ${callData.minAmountOut.amount}\n\nTx Data:\nTo: ${callData.to}\nCallData: ${callData.callData}\nValue: ${callData.value}` ); ``` #### Install the Balancer SDK The [Balancer SDK](https://github.com/balancer/b-sdk) is a Typescript/Javascript library for interfacing with the Balancer protocol and can be installed with: \::: code-tabs#shell @tab pnpm ```bash pnpm add @balancer/sdk ``` @tab yarn ```bash yarn add @balancer/sdk ``` @tab npm ```bash npm install @balancer/sdk ``` \::: The three main helper classes we use from the SDK are: * `BalancerApi` - to query the SOR for optimized swap path * `Swap` - to build swap queries and transactions * `Slippage` - to simplify creating limits with user defined slippage #### Fetching Optimized Swap Paths In this example we use the BalancerApi `fetchSorSwapPaths` function to fetch the optimized swap paths for a token pair and swap amount. ```typescript const balancerApi = new BalancerApi( 'https://api-v3.balancer.fi/', chainId, ); const sorPaths = await balancerApi.sorSwapPaths.fetchSorSwapPaths({ chainId, tokenIn: tokenIn.address, tokenOut: tokenOut.address, swapKind, swapAmount, }); ``` To see the full query used to fetch pool state refer to the code [here](https://github.com/balancer/b-sdk/blob/main/src/data/providers/balancer-api/modules/sorSwapPaths/index.ts#L19). \:::tip Liquidity Source By default the API will return the swap that gives the best result from either v2 or v3 liquidity. The version can be forced by setting the optional `fetchSorSwapPaths`, `useProtocolVersion` input parameter. \::: #### Queries and safely setting slippage limits [Router queries](../../concepts/router/queries.md) allow for simulation of operations without execution. In this example, when the `query` function is called: ```typescript const updated = await swap.query(RPC_URL) as ExactInQueryOutput; ``` An onchain call is used to find an updated result for the swap paths, `expectedAmountOut`. In the next step `buildCall` uses the `amount` and the user defined `slippage` to calculate the `minAmountOut`: ```typescript const callData = swap.buildCall(buildInput) as SwapBuildOutputExactIn; ``` In the full example above, we defined our slippage as `Slippage.fromPercentage('1')`, meaning that we if we do not receive at least 99% of our expected `amount`, the transaction should revert. Internally, the SDK subtracts 1% from the query output, as shown in `Slippage.applyTo` below: ```typescript /** * Applies slippage to an amount in a given direction * * @param amount amount to apply slippage to * @param direction +1 adds the slippage to the amount, and -1 will remove the slippage from the amount * @returns */ public applyTo(amount: bigint, direction: 1 | -1 = 1): bigint { return MathSol.mulDownFixed( amount, BigInt(direction) * this.amount + WAD, ); } ``` \::: tip v2 vs v3 differences In Balancer v2 the swap functions required the user to define the `sender` and `recipient` as part of the [FundManagement](https://docs-v2.balancer.fi/reference/swaps/batch-swaps.html#fundmanagement-struct) parameter. In v3 this is no longer an option and the msg.sender is always the sender/recipient. `swap.protocolVersion` is used to correctly construct the parameters for the `buildCall` function: ```typescript let buildInput: SwapBuildCallInput; // In v2 the sender/recipient can be set, in v3 it is always the msg.sender if (swap.protocolVersion === 2) { buildInput = { slippage, deadline, queryOutput: updated, wethIsEth, sender: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", recipient: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045", }; } else { buildInput = { slippage, deadline, queryOutput: updated, wethIsEth, }; } ``` \::: #### Constructing the call The output of the `buildCall` function provides all that is needed to submit the swap transaction: * `to` - the address the transaction should be sent to * `callData` - the encoded call data * `value` - the native asset value to be sent It also returns the `minAmountOut` amount which can be useful to display/validation purposes before the transaction is sent. ## Onboarding Yield-bearing Assets This guide outlines the process of onboarding yield-bearing assets to Balancer v2. To fully leverage Balancer's technology, you'll need to complete several key steps including token setup, rate provider implementation, pool creation, and optional gauge setup for BAL rewards. ### Step-by-Step Onboarding Process #### 1. Token Setup First, ensure your token is properly whitelisted on Balancer. See our [token whitelisting guide](./token-whitelisting.md) for detailed instructions. #### 2. Rate Provider Implementation \::: tip Documentation For detailed information, see our [rate provider onboarding FAQ](../onboarding-overview/rate-providers.md) \::: For yield-bearing asset stable pairs using a stable pool, you must provide a vetted rate provider during pool creation: 1. Review existing rate providers in our [registry](https://github.com/balancer/code-review/tree/main/rate-providers) 2. If needed, submit your rate provider for review [here](https://github.com/balancer/code-review/issues) \::: info Review Timeline Rate provider reviews typically take 1-2 weeks for Balancer Labs to complete. Monitor the [issue board](https://github.com/balancer/code-review/issues) for review status. \::: #### 3. Pool Creation and Initialization Use the [community pool creator tool](https://balancer.defilytica.tools/pool-creator-v2) to create your pool: 1. Select ComposableStable Pool type 2. Set `Yield Protocol Fee Exempt` to `false` 3. Add your token and approved rate provider 4. Add additional tokens (up to 5) with their rate providers 5. Create the pool 6. Perform an init join to seed initial liquidity #### 4. APR Data Integration To ensure accurate yield metrics display: 1. Provide an API endpoint for your yield-bearing token * Format: `api-yourtoken` * Include APR in return values 2. Register your endpoint in the [yield token registry](https://github.com/balancer/yield-tokens) 3. This ensures correct APR display across Balancer frontend deployments #### 5. BAL Rewards Setup (Optional) If you want to receive BAL rewards, you'll need to set up a gauge and apply through governance. ##### Gauge Creation \::: info Network-Specific Instructions Find detailed gauge creation endpoints for different networks in our [instructions overview](https://forum.balancer.fi/t/instructions-overview/2674) \::: ##### Governance Application Timeline 1. Submit proposal following the [instruction set](https://forum.balancer.fi/t/instructions-overview/2674/2) 2. Contributor review by Thursday 3. Voting period: Thursday 8PM CET to Monday 8PM CET 4. On-chain gauge controller transaction by Tuesday evening CET 5. Gauge appears on [veBAL voting page](https://app.balancer.fi/#/ethereum/vebal) \::: tip Reward Distribution * Ethereum mainnet: BAL rewards begin after voting round * L2 networks (Arbitrum, Polygon POS): One week delay before reward streaming \::: ### Additional Resources * [Gauge Onboarding Guide](../onboarding-overview/gauge-onboarding.md) * [Rate Provider Documentation](../onboarding-overview/rate-providers.md) * [Core Pool Framework](../onboarding-overview/core-pools.md) * [Protocol Fees Documentation](../../concepts/governance/protocol-fees.md) ## Setting Up a Partner Points Program Tag on the Balancer UI This guide will walk you through the process of setting up a tag for your partner points program in the Balancer protocol. By following these steps, you'll be able to tag sets of pools in the Balancer API and frontend, allowing users to earn points for providing liquidity to specific pools. :::info All information provided herein is referencing how to make changes to the [metadata repository](https://github.com/balancer/metadata/) to register a points program on the Balancer Zen UI. ::: ### Overview To set up a partner points program tag, you'll need to: 1. Add a new tag to the `index.json` file 2. (Optional) Add a tag icon ### Step 1: Add a New Tag First, you'll need to update the `index.json` file located at `metadata/pools/tag/index.json`. Add a new object to the JSON array with the following structure: ```json { "id": "points_your_protocol_name", "name": "Points (Your Protocol Name)", "description": "Description of your points program", "value": "4", // Optional: Use if you have a points multiplier (e.g., "4" for 4x points) "url": "https://your-protocol-website.com", "icon": "points_your_protocol_name.svg", // Optional "pools": [ "0x90e6cb5249f5e1572afbf8a96d8a1ca6acffd739000000000000000000000055c", "0x7761b6e0daa04e70637d81f1da7d186c205c2ade00000000000000000000065d", "0x73a7fe27fe9545d53924e529acf11f3073841b9e000000000000000000000133" ] } ``` #### Key Points: * The `id` should start with "points\_" followed by your protocol name (e.g., "points\_kelp"). * Provide a clear, concise description of your points program. * If your program has a points multiplier, include it in the `value` field (e.g., "4" for 4x points). * The `pools` array should contain the IDs of all pools that are eligible for your points program. * If you're adding an icon (Step 2), include the `icon` property. ### Step 2: (Optional) Add a Tag Icon If you want to display an icon for your tag in the Balancer frontend: 1. Add your icon file (preferably in SVG, PNG, or JPG format) to the `/icons` directory. 2. Name the file using your tag ID (e.g., `points_your_protocol_name.svg`). 3. Ensure you've included the `icon` property in your tag object in `index.json`. ### Examples Here are some examples of existing partner points program tags: #### Kelp DAO ```json { "id": "points_kelp", "name": "Points (Kelp)", "description": "LPs earn Miles on the TVL of the pool. The Miles boost increases rewards based on the total pool capital, not just rsETH. Your daily Kelp Miles value is calculated by multiplying the effective rsETH balance by 10,000 times the boost value. Your Miles are then distributed based on your share of the liquidity pool.", "url": "https://kelpdao.xyz", "icon": "points_kelp.jpg", "pools": [ "0x90e6cb5249f5e1572afbf8a96d8a1ca6acffd73900000000000000000000055c", "0x7761b6e0daa04e70637d81f1da7d186c205c2ade00000000000000000000065d", "0x73a7fe27fe9545d53924e529acf11f3073841b9e000000000000000000000133" ] } ``` #### YieldFi (with multiplier) ```json { "id": "points_yieldfi", "name": "Points (YieldFi)", "description": "LPs in this pool earn 2x YieldCrumbs on the TVL provided to the pool (yUSD + aUSDC)", "value": "2x", "url": "https://yield.fi", "icon": "points_yieldfi.jpg", "pools": [ "0x7abe8caa137cdb2490a9fa9f8be70cfbb0ff8652", "0x424d19d482a891b2c5cb881d651fbc32b349cb3c", "0x21f132ade35684b230af974b80b5bfd2678ebd80", "0xb6a9a815d98cb98fd9f2353ec59de07b63f5b485" ] } ``` #### Sonic Points Program (with higher multiplier) ```json { "id": "points_sonic_12x", "name": "Sonic Points Program", "description": "Earn 12x Sonic Activity points for supplying USDC.e, scUSD or wstkscUSD. Receive a share of the 200M S airdrop!", "value": "12", "url": "https://blog.soniclabs.com/sonic-points-simplified-how-to-qualify-for-200-million-s-airdrop/", "icon": "sonic.svg", "pools": [ "0x43026d483f42fb35efe03c20b251142d022783f2", "0xcd4d2b142235d5650ffa6a38787ed0b7d7a51c0c000000000000000000000037", "0x25ca5451cd5a50ab1d324b5e64f32c0799661891000200000000000000000018" // Additional pool IDs... ] } ``` ### Alternative Format: Using Tokens Instead of Pools For some tags, you may want to specify tokens instead of pools. In this case, your tag object would look like: ```json { "id": "points_rings", "name": "Rings Points Program", "description": "Earn Rings points and receive part of their Sonic Gems allocation.", "value": "1.5", "url": "https://app.rings.money/#/points", "icon": "rings.svg", "pools": [], "tokens": { "146": [ "0xd3dce716f3ef535c5ff8d041c1a41c3bd89b97ae", "0x3bce5cb273f0f148010bbea2470e7b5df84c7812", "0x9fb76f7ce5fceaa2c42887ff441d46095e494206" // Additional token IDs... ] } } ``` ### Best Practices 1. **Clear Descriptions**: Provide a clear and concise description of your points program, including any special mechanics or multipliers. 2. **Unique Identifiers**: Ensure your tag ID is unique and descriptive, always starting with "points\_" for points programs. 3. **Up-to-date Pool Lists**: Regularly update your pool list to reflect any changes in eligible pools. 4. **High-Quality Icons**: If providing an icon, ensure it's high-quality either in in .svg or .png format 5. **Appropriate Values**: If your program has a multiplier, clearly indicate it in the "value" field. By following these steps and best practices, you'll successfully set up your partner points program tag in the Balancer protocol, allowing users to easily identify and participate in your program. ## Token Whitelisting This guide explains the process of whitelisting tokens on Balancer and important security considerations. ### Security Advisory \::: danger Important Security Note Due to a known vulnerability in Balancer V2's Vault (discovered March 2023), new tokens require careful verification before being added to pools. While this vulnerability doesn't affect existing tokens or funds in the V2 Vault, it's important to follow the proper whitelisting process for new tokens. The vulnerability only potentially affects token addresses that: * Are not currently live on-chain * Would eventually be deposited in the Balancer V2 Vault For full details, see the [vulnerability disclosure](https://forum.balancer.fi/t/balancer-v2-token-frontrun-vulnerability-disclosure/6309). \::: ### Whitelisting Process To whitelist your token on Balancer, you need to submit a Pull Request to the [Balancer tokenlists repository](https://github.com/balancer/tokenlists). This process ensures your token is properly verified and can be traded on our platform. #### Requirements 1. Token contract must be verified on Etherscan (or equivalent explorer for other networks) 2. Token must be already deployed and live on-chain 3. Token must have no transfer restrictions or rebasing mechanics that could interfere with pool operations #### Steps 1. Prepare Token Images * Provide PNG files of your token logo * Name the file using your token contract address: `0xTOKENADDRESS.png` 2. Update Tokenlist Files * Add your token to `tokenlists/balancer/tokens` * Update the corresponding network typescript file * Example: `tokenlists/balancer/tokens/arbitrum/0x...` 3. Submit Pull Request * Create a PR with your changes * Include relevant token information and documentation * Wait for review from the Balancer team ### Post-Whitelisting After your token is whitelisted: * It will appear in the Balancer interface * Users can trade it through the frontend * You can create pools including the token \::: warning Note For new projects looking to integrate with Balancer, we recommend using Balancer V3 which is not affected by the V2 vulnerability. \::: ### Additional Resources 1. [Balancer Tokenlists Repository](https://github.com/balancer/tokenlists) 2. [Token Requirements Documentation](../../concepts/vault/token-types.md) 3. [V2 Vulnerability Disclosure](https://forum.balancer.fi/t/balancer-v2-token-frontrun-vulnerability-disclosure/6309) ## Onboarding to Balancer v2 Balancer v2 has been a core pillar of DeFi since 2021. By leveraging innovative pool types, Balancer v2 has attracted liquidity in the liquid staking token (LST) and liquid restaking token (LRT) sector. Balancer Technology provides decentralized infrastructure for DAOs, enabling efficient scaling of Yield Bearing assets, creating advanced Governance positions, and developing customized pool types. The Balancer ecosystem facilitates the streamlined scaling of liquidity for DAOs through core pool incentive flywheels and its network of liquidity enhancing protocols. \::: warning Security Advisory Before integrating new tokens with Balancer V2, please review our [token whitelisting](./token-whitelisting.md) documentation and security considerations. For new integrations, we recommend using Balancer V3. \::: ### Onboarding Steps Onboarding to Balancer v2's tech stack involves various steps depending on your specific needs: 1. Choosing and launching your pool 2. Providing initial liquidity 3. Onboarding to Balancer's gauge system 4. Setting up voting incentive markets #### Choosing and Launching A Pool Balancer v2 offers a variety of pool types to suit different needs: \| Pool Type | Use-Cases | Examples | \|-----------|-----------|-----------| \| Composable Stable Pool | Provision of highly correlated asset liquidity | [wstETH:WETH pool](https://app.balancer.fi/#/ethereum/pool/0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2) on mainnet | \| Weighted Pools incl. 80/20 | Creation of pools with any weight distribution with up to 8 tokens | [BAL:WETH 80:20 pool](https://app.balancer.fi/#/ethereum/pool/0x93d199263632a4ef4bb438f1feb99e57b4b5f0bd0000000000000000000005c2) | \| Gyroscope E-CLPs | Specialized pools with [customized liquidity curves](https://docs.gyro.finance/gyroscope-protocol/readme) | [USDC:GYD Stable Pool](https://app.balancer.fi/#/ethereum/pool/0xc2aa60465bffa1a88f5ba471a59ca0435c3ec5c100020000000000000000062c) on mainnet | \| Managed pools | Specialized pools with dynamic pool weights | [Example index fund](https://app.kassandra.finance/pool/1370xc22bb237a5b8b7260190cb9e4998a9901a68af6f000100000000000000000d8d) on Avalanche | #### Providing Initial Liquidity Depending on your chosen pool type, you can bootstrap liquidity through: * [Pool creation UI](https://app.balancer.fi/#/ethereum/pool/create) for weighted pools * [Community pool creator tool](https://pool-creator.web.app/) for Composable Stable pools * [Gyroscope platform](https://app.gyro.finance/) for E-CLP liquidity pools #### Gauge System Integration If your project intends to receive BAL rewards, consult our [Gauge Onboarding](../onboarding-overview/gauge-onboarding.md) documentation. The gauge system enables: 1. BAL rewards through [veBAL](https://app.balancer.fi/#/ethereum/vebal) holder votes 2. [vlAURA](https://app.aura.finance/#/1/lock) votes from AURA finance 3. Direct incentives on Balancer Gauges 4. Direct incentives on AURA Finance Gauges 5. Voting incentive markets \::: tip Core Pool Status Interested in receiving core pool status? Read our [core pools documentation](../onboarding-overview/core-pools.md) \::: ### Additional Resources * [Yield-bearing Token Onboarding](./onboard-yb-token.md) * [Rate Provider Onboarding](../onboarding-overview/rate-providers.md) * [Token Whitelisting](./token-whitelisting.md) * [Incentives Management Documentation](../onboarding-overview/incentive-management.md) * [Voting Markets](../onboarding-overview/voting-markets.md) ## Pool Creation This guide will help you understand pool configuration options and how to use the [v3 pool creation UI](https://pool-creator.balancer.fi/v3) :::tip If you encounter any issues or need additional help, please reach out to us on [discord](https://discord.balancer.fi/) or create an issue on [github](https://github.com/balancer/pool-creator/issues/new/choose) ::: ### Configuration Options The process of creating a pool begins with choosing the configuration #### Network Selection * You must select a supported network before choosing a pool type ![Switch Network Button](/images/pool-creation/switch-network.png) * After advancing with the "Next" button, the only way to switch the network is to "Reset Progress" ![Reset Progress](/images/pool-creation/reset-progress.png) #### Pool Type * Select either a "Weighted" or "Stable" pool type. See more detailed information [here](/concepts/explore-available-balancer-pools/) #### Pool Tokens * You must choose at least two tokens * You must have sufficient wallet balance relative to the input amount in order to advance * If the token is yield-bearing, you may need to use a [rate provider](/partner-onboarding/onboarding-overview/rate-providers.html) * If our API contains an approved rate provider review for the token you selected, it will be automatically populated ![Rate Provider](/images/pool-creation/rate-provider.png) * For weighted pools, you can lock weights so that only unlocked weights are automatically recalculated ![Weight Lock](/images/pool-creation/weight-lock.png) * For weighted pools, it is important that you enter amounts that are proportional to weight percentage values ![Proportional Weights](/images/pool-creation/proportional-weight.png) #### Pool Parameters * See detailed information about swap fee percentages [here](/concepts/vault/swap-fee.html) * Stable pools require an amplification parameter setting. See detailed information [here](/concepts/explore-available-balancer-pools/stable-pool/stable-math.html) * See more information about pool management [here](/concepts/core-concepts/pool-role-accounts.html) * When using a pool hook, you have the option to disable unbalanced liquidity operations and/or allow donations * If the pool hooks contract sets the `enableHookAdjustedAmounts` flag to `true`, the pool must also set `disableUnbalancedLiquidity` to `true` * It is important to understand the permissions related to a given hooks `onRegister` function. For example, the hook could require that the pool set `enableDonations` to `true` ![Pool Hooks Config](/images/pool-creation/pool-hooks-config.png) #### Pool Information * Pool name and symbol are automatically populated, but you have the option to modify both ### Creation Process * Click the "Preview Pool" button to open the Pool Creation modal * After step 1, you cannot close the modal to go back and change configuration options unless you "Reset Progress" ![Reset Creation Progress](/images/pool-creation/reset-creation-progress.png) * After completing the pool creation process, you will have the option to view your pool on Balancer or create another pool ![Pool Creation Success](/images/pool-creation/creation-success.png) :::tip - Your pool may take a few minutes to show on [balancer.fi](https://balancer.fi/pools) - If you used a rate provider that has not been reviewed, your pool will not show on [balancer.fi](https://balancer.fi/pools) ::: ## Onboarding to Balancer v3 Balancer v3 introduces a simplified and more efficient AMM infrastructure, optimized for scalability and developer experience. The v3 architecture brings native support for yield-bearing tokens, 100% boosted pools, and a flexible hooks system for custom pool extensions. \::: tip Key Improvements * Simplified pool development with core features managed by vault * Native support for yield-bearing tokens * Gas-efficient 100% boosted pools * Flexible hooks system for custom extensions * No token whitelisting required \::: ### Onboarding Steps 1. Choosing and launching your pool 2. Providing initial liquidity 3. Implementing hooks (optional) 4. Setting up incentives through the gauge and our incentive management systems #### Pool Types and Use Cases \| Pool Type | Key Features | Best For | \|-----------|-------------|-----------| \| Boosted Pools | 100% yield-bearing exposure, gas-efficient swaps | Passive LPs seeking additional yield | \| Weighted Pools | Customizable weight distributions | Token pairs and index-style products | \| Composable Stable Pools | Highly correlated assets, native rate scaling | Stablecoin and LST/LRT pairs | \| Custom Pools | Fully customizable AMM logic | Specialized trading strategies | \::: info Consult our [pool type](../../concepts/explore-available-balancer-pools) section for more in-depth information on supported pools on Balancer v3. \::: #### Pool Creation and Liquidity 1. Use the [Pool Creation UI](https://pool-creator.balancer.fi/v3) to: * Select pool type * Configure parameters * Set initial liquidity * Deploy pool \::: info Boosted Pools Boosted Pools in v3 use an efficient buffer system that: * Maintains 100% exposure to yield-bearing assets * Provides gas-efficient swaps * Automatically manages underlying token conversions \::: Check out our pool creation [tutorials and docs](./pool-creation.md) for more details. #### Implementing Hooks (Optional) Balancer v3 remains a platform for AMM experimentation and innovation, allowing custom pools to iterate on existing or define entirely new swap invariants. With the v3 vault handling much of the responsibility that was previously delegated to the pool contract, internally developed pools are significantly less complex. By shifting core design patterns out of the pool and into the vault, we believe v3 strikes a better balance and produces a 10x improvement in pool developer experience (DX). In addition to custom pools, we’ve introduced a hooks framework that allows developers to easily extend existing pool types at various key points throughout the pool’s lifecycle. While already present in v2, the v3 vault formalizes the definition of hooks and transient accounting enables secure vault reentrancy, unlocking an infinite design space. To showcase the potential of hooks, we’ve put together some simple examples: * [veBalDiscountHook](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/contracts/VeBALFeeDiscountHookExample.sol) * [LotteryHook](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/contracts/LotteryHookExample.sol) * [ExitFeeHook](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/contracts/ExitFeeHookExample.sol) * [FeeTakingHook](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/pool-hooks/contracts/FeeTakingHookExample.sol) \::: tip Developer Resources Check our [example hooks](https://github.com/balancer/balancer-v3-monorepo) for implementation guidance and our [hooks docs](../../concepts/core-concepts/hooks.md). \::: #### Gauge System Integration The gauge system remains consistent with v2: 1. BAL rewards through [veBAL](https://app.balancer.fi/#/ethereum/vebal) votes 2. Integration with [vlAURA](https://app.aura.finance/#/1/lock) 3. Direct incentive placement options See our [gauge onboarding documentation](../onboarding-overview/gauge-onboarding.md) for detailed setup instructions. \::: tip Core Pool Status Review our [core pools documentation](../onboarding-overview/core-pools.md) for information about enhanced benefits and requirements. \::: ### Developer Support * Grants program offering up to 150k BAL for v3 implementations * Hooks bounty program for innovative extensions * Direct support through [Balancer Discord](https://discord.balancer.fi) ### Additional Resources * [Technical Documentation](../../concepts/vault/README.md) * [Core Pool Framework](../onboarding-overview/core-pools.md) * [Gauge System Guide](../onboarding-overview/gauge-onboarding.md) * [Incentives Management](../onboarding-overview/incentive-management.md) * [Pool Creation Guide](./pool-creation.md) * [Example Hooks Repository](https://github.com/balancer/balancer-v3-monorepo) ## Core Pools Core pools are a fundamental concept in Balancer's tokenomics model, designed to align token emissions with pool performance and fee generation. This document outlines the requirements and benefits of core pools across both Balancer v2 and v3. ### What is a Core Pool? A core pool is a liquidity pool that meets specific criteria established by governance and participates in enhanced fee distribution and incentive mechanisms. Core pools receive additional benefits through voting incentives and participate in the protocol's sustainable incentive flywheel. ### Requirements for Core Pool Status #### Composition Requirements 1. Token composition must meet one of these criteria: * Minimum 50% yield-bearing or boosted tokens for weighted/composable stable pools * 80/20 weighted pools using Balancer as primary liquidity hub (requires application) 2. Maintain minimum $100k TVL #### Technical Requirements * No yield fee exemption allowed * Fee settings must be delegated to Balancer governance * If default protocol fee settings are unavailable, an alternative fee setting must be established \::: warning Important Pools without proper protocol fee settings (e.g., CoWAMM v1 pools) cannot achieve core pool status unless an alternative fee collection mechanism is implemented. \::: #### Token Requirements * All pool tokens must have verified smart contracts In addition, the following token features are unsupported by the Vault. This list is not meant to be exhaustive, but covers many common types of tokens that will not work with the Vault architecture. (See https\://github.com/d-xo/weird-erc20 for examples of features that are problematic for many protocols.) * Rebasing tokens (e.g., aDAI). The Vault keeps track of token balances in its internal accounting; any token whose balance changes asynchronously (i.e., outside a swap or liquidity operation), would get out-of-sync with this internal accounting. This category would also include "airdrop" tokens, whose balances can change unexpectedly. * Double entrypoint tokens (e.g., old Synthetix tokens, now fixed). These could likewise bypass internal accounting by registering the token under one address, then accessing it through another. This is especially troublesome in v3, with the introduction of ERC4626 buffers and transient accounting. * Fee on transfer tokens (e.g., PAXG). The Vault issues credits and debits according to given and calculated token amounts, and settlement assumes that the send/receive transfer functions transfer\ exactly the given number of tokens. If this is not the case, and the token itself imposes a "tax" on transfers, transactions will not settle. Unlike with the other types, which are fundamentally incompatible, it would be possible to design a Router to handle this - but we didn't try it. In any case, it's not supported in the current Routers. * Tokens with more than 18 decimals (e.g., YAM-V2). The Vault handles token scaling: i.e., handling I/O for amounts in native token decimals, but doing calculations with full 18-decimal precision. This requires reading and storing the decimals for each token. Since virtually all tokens are 18 or fewer decimals, and we have limited storage space, 18 was a reasonable maximum. Unlike the other types, this is enforceable by the Vault. Attempting to register such tokens will revert with `InvalidTokenDecimals`. Of course, we must also be able to read the token decimals, so the Vault only supports tokens that implement `IERC20Metadata.decimals`, and return a value less than or equal to 18. * Token decimals are checked and stored only once, on registration. Valid tokens store their decimals as immutable variables or constants. Malicious tokens that don't respect this basic property would not work anywhere in DeFi. These types of tokens are technically supported but discouraged, as they don't tend to play well with AMMs generally. * Very low-decimal tokens (e.g., GUSD). The Vault has been extensively tested with 6-decimal tokens (e.g., USDC), but going much below that may lead to unanticipated effects due to precision loss, especially with smaller trade values. * Revert on zero value approval/transfer. The Vault has been tested against these, but peripheral contracts, such as hooks, might not have been designed with this in mind. * Other types from "weird-erc20," such as upgradeable, pausable, or tokens with blocklists. We have seen cases where a token upgrade fails, "bricking" the token - and many operations on pools containing that token. Any sort of "permissioned" token that can make transfers fail can cause operations on pools containing them to revert. Even Recovery Mode cannot help then, as it does a proportional withdrawal of all tokens. If one of them is bricked, the whole operation will revert. Since v3 does not have "internal balances" like v2, there is no recourse. ### Core Pool Benefits Core pools receive several advantages: 1. Enhanced fee distribution: * 70% of collected fees distributed as voting incentives * 12.5% to veBAL holders * 17.5% to DAO 2. Participation in protocol's incentive flywheel 3. Increased visibility in the Balancer ecosystem \::: tip Fee Processing Core pool fees are processed every two weeks to align with: * veBAL vote cooldown period (10 days) * Bi-weekly voting schedule on yield aggregators \::: ### Maintaining Core Pool Status * Status is evaluated bi-weekly before fee sweeps * Automated checks are performed by the MAXYZ service provider * Pools must continuously meet all requirements * New pool types require explicit governance approval ### How to Apply for Core Pool Status If your pool meets the above requirements: 1. For standard pools (50% yield-bearing): * Ensure all requirements are met * Pool will be automatically evaluated during bi-weekly checks 2. For 80/20 weighted pools: * Submit a governance proposal * Include documentation of Balancer as primary liquidity hub * Follow the template from [ALCX/ETH proposal](https://forum.balancer.fi/t/bip-290-designate-alcx-eth-80-20-as-a-core-pool-with-10-emissions-cap/4753) ### Additional Resources * [V2 Core Pools Analytics Dashboard](https://balancer.defilytica.com/#/corePools) * [V3 Core Pools Analytics Dashboard](https://balancer.defilytica.tools/core-pools) * [Protocol Fee Dashboard](https://dune.com/balancer/protocol-fees) * [Automatic Core Pool List](https://github.com/balancer/bal_addresses/blob/main/outputs/core_pools.json) * [Fee Model Documentation](/concepts/protocol-fee-model) ## Gauge Onboarding A gauge is a staking contract that enables token rewards to flow to liquidity providers of a pool. Gauges are required for **both** BAL emissions and secondary reward token incentives. Pools do not automatically have gauges—they must be created first. ### Which Path Do You Need? \| Goal | Steps Required | \|------|----------------| \| **Secondary rewards only** | Complete [Step 1](#step-1-create-the-gauge) (gauge creation), then follow the [Incentive Management Guide](./incentive-management.md) | \| **BAL emissions** | Complete all steps in this guide (Steps 1-6), including governance approval | \| **Both BAL + secondary rewards** | Complete all steps in this guide, then add secondary rewards via the [Incentive Management Guide](./incentive-management.md) | :::tip If you only need to distribute your own reward tokens (not BAL), you can skip the governance process. Simply create the gauge and proceed directly to the [Incentive Management Guide](./incentive-management.md) to configure secondary rewards. ::: ### Prerequisites Before starting the gauge onboarding process, ensure: 1. Your pool is deployed and indexed (visible on the [Balancer App](https://balancer.fi)) 2. Rate providers are vetted (if applicable) - see [Rate Provider Registry](https://github.com/balancer/code-review/tree/main/rate-providers) 3. You understand [gauge caps](#gauge-caps) and have determined the appropriate cap for your pool ### Step 1: Create the Gauge Use the [Gauge Creator Tool](https://balancer.defilytica.tools/gauge-creator) to deploy your gauge contracts. #### Ethereum Mainnet Pools 1. Select "Ethereum" on the gauge creator 2. Search and select your pool from the pool list ![Select Pool](/images/incentive-management/gauge_creation_1.png) 3. If a gauge has already been created for your pool, the UI will display a warning - you can skip this step ![Gauge Already Exists](/images/incentive-management/gauge_creation_4.png) 4. Choose an appropriate [voting cap](#gauge-caps) (2% to uncapped) 5. Execute the transaction by clicking "Create Mainnet Gauge" 6. **Note down the root gauge address** - it will appear in the event logs and in the UI after successful transaction ![Creation Event](/images/incentive-management/gauge_creation_5.png) For mainnet, only the root gauge is needed. #### L2/Sidechain Pools For pools on Arbitrum, Polygon, Gnosis, and other L2 networks: 1. **Create the Child Chain Gauge first:** * Select the target network on the gauge creator * Search and select your pool from the pool list * The tool will indicate if a child chain gauge already exists for this pool * Execute the child chain gauge creation transaction 2. **Create the Root Gauge on Ethereum Mainnet:** * Switch to Ethereum on the gauge creator * Create a root gauge pointing to your child chain gauge * Choose an appropriate [voting cap](#gauge-caps) * **Note down the root gauge address** - this is needed for the governance proposal :::tip The child chain gauge handles staking on the L2, while the root gauge on Ethereum is what veBAL voters interact with. Only the root gauge address is needed for your proposal. ::: ### Step 2: Create the Enable Gauge Payload Before submitting your governance proposal, create the technical payload that will be executed upon approval. 1. Go to the [Enable Gauge Payload Builder](https://balancer.defilytica.tools/payload-builder/enable-gauge) 2. Enter the following: * **Root Gauge**: The root gauge contract address from Ethereum Mainnet * **Network**: The chain where your pool is deployed 3. Click "Generate Payload" and validate it by simulating the transaction 4. **Copy the Technical Specification** - you'll need this for your forum proposal 5. **Create the PR**: Log in with your GitHub account and submit the PR directly from the tool :::info The payload builder generates the exact technical specification text that should be included in your forum proposal, ensuring consistency and accuracy. ::: ### Step 3: Write Your Governance Proposal Create a proposal on the [Balancer Forum](https://forum.balancer.fi) under the BAL gauges [category](https://forum.balancer.fi/c/vebal/13). #### Proposal Template Copy the template below and fill in the placeholders (marked with `[PLACEHOLDER]`). The technical specification should be copied from the [Enable Gauge Payload Builder](https://balancer.defilytica.tools/payload-builder/enable-gauge). ##### For Mainnet Pools ```md # [Proposal] Enable Gauge for [POOL_SYMBOL] on Ethereum **Summary:** This is a proposal to enable the gauge `[ROOT_GAUGE_ADDRESS]` on Ethereum, for the pool `[POOL_ADDRESS]` ([POOL_SYMBOL]). **References/Useful links:** - Website: [URL] - Documentation: [URL] - Github Page: [URL] - Communities: [URL] - Other useful links: [URL] **Protocol Description:** [Describe the proposed asset(s), the corresponding protocol(s), and historic prices of the token. Price must come from the source of highest liquidity.] **Motivation:** [Explain why this pool needs incentivization.] **Specifications:** **Governance:** [Provide current information on the protocol's governance structure. Provide links to any admin and/or multisig addresses, and describe the powers afforded to these addresses. If there are plans to change the governance system in the future, please explain.] **Oracles:** [Does the protocol rely on external oracles? If so, provide details about the oracles and their implementation in the protocol.] **Audits:** [Provide links to audit reports and any relevant details about security practices.] **Centralization vectors:** [Is there any component of the protocol that has centralization vectors? E.g. if only 1 dev manages the project, that is a centralized vector. If price oracles need to be updated by a bot, that is a centralized vector. If liquidations are done by the protocol, that is also a centralization vector.] **Market History:** [Has the asset observed severe volatility? In the case of stablecoins, has it depegged? In the case of an unpegged asset, have there been extreme price change events in the past? Provide specific information about the Balancer pool: how long has it been active, TVL, historical volume? **You must provide a direct link to the pool AND a link to your pool's gauge.**] **Value:** [Is this pool intended to be the primary source of liquidity for the token(s)? If this is not the case, explain the expected value add to Balancer (can this pool generate consistent fees?)] **Technical Specification:** [PASTE_TECHNICAL_SPEC_FROM_PAYLOAD_BUILDER] ``` ##### For L2/Sidechain Pools ```md # [Proposal] Enable Gauge for [POOL_SYMBOL] on [NETWORK] **Summary:** This is a proposal to enable the root gauge `[ROOT_GAUGE_ADDRESS]` on Ethereum, which redirects rewards to the child gauge `[CHILD_GAUGE_ADDRESS]` on [NETWORK], for the pool `[POOL_ADDRESS]` ([POOL_SYMBOL]). **References/Useful links:** - Website: [URL] - Documentation: [URL] - Github Page: [URL] - Communities: [URL] - Other useful links: [URL] **Protocol Description:** [Describe the proposed asset(s), the corresponding protocol(s), and historic prices of the token. Price must come from the source of highest liquidity.] **Motivation:** [Explain why this pool needs incentivization.] **Specifications:** **Governance:** [Provide current information on the protocol's governance structure. Provide links to any admin and/or multisig addresses, and describe the powers afforded to these addresses. If there are plans to change the governance system in the future, please explain.] **Oracles:** [Does the protocol rely on external oracles? If so, provide details about the oracles and their implementation in the protocol.] **Audits:** [Provide links to audit reports and any relevant details about security practices.] **Centralization vectors:** [Is there any component of the protocol that has centralization vectors? E.g. if only 1 dev manages the project, that is a centralized vector. If price oracles need to be updated by a bot, that is a centralized vector. If liquidations are done by the protocol, that is also a centralization vector.] **Market History:** [Has the asset observed severe volatility? In the case of stablecoins, has it depegged? In the case of an unpegged asset, have there been extreme price change events in the past? Provide specific information about the Balancer pool: how long has it been active, TVL, historical volume? **You must provide a direct link to the pool AND a link to your pool's gauge.**] **Value:** [Is this pool intended to be the primary source of liquidity for the token(s)? If this is not the case, explain the expected value add to Balancer (can this pool generate consistent fees?)] **Technical Specification:** [PASTE_TECHNICAL_SPEC_FROM_PAYLOAD_BUILDER] ``` :::tip The technical specification is automatically generated by the [Enable Gauge Payload Builder](https://balancer.defilytica.tools/payload-builder/enable-gauge). Simply copy and paste it into the Technical Specification section of your proposal. ::: ### Step 4: Community Discussion After posting your proposal: * Allow time for community discussion and feedback * **Recommended discussion period**: 1 week (common practice) * Address any questions or concerns raised by community members * See [Governance Guidelines](https://forum.balancer.fi/t/governance-guidelines) for detailed timelines ### Step 5: Request Snapshot Vote When discussion is complete: 1. Request a vote by replying to your forum thread 2. **Deadline**: Proposals meeting all requirements by **Thursday 8PM CET** are included in the next snapshot round #### Voting and Execution Schedule * Snapshot votes begin every **Friday 8PM CET** * Voting period concludes the following Tuesday 8PM CET * Gauges are added to the gauge controller by Wednesday of vote conclusion #### Emissions Timeline * **Mainnet gauges**: Emissions can begin after successful vote once the payload is executed * **L2 gauges**: Emissions begin **one week after** a successful vote * Example: If voting ends June 8, emissions start June 15 :::warning Gauges receiving less than 0.1% of total votes may not receive emissions every week due to gas cost considerations. ::: ### Step 6: Post-Approval & Maintenance #### After Approval Once your proposal passes: * The MAXYZ service provider executes the payload from your PR * Your gauge begins receiving BAL emissions based on veBAL votes #### Gauge Monitoring & Kill Conditions Gauges are evaluated on a quarterly basis per [BIP-795](https://forum.balancer.fi/t/bip-795-kill-stale-gauges-q1-2025/6410). A gauge may be flagged for removal if it meets **both** conditions: * Has not received veBAL votes for **>60 days** * Median TVL has been under **$100k USD** for **>60 days** Check the [Gauge Kill List](https://balancer.defilytica.tools/gauge-kill-list) to see if your gauge is flagged. :::info Gauges can also be killed at any time due to externalities. The rules above are meant to keep the system lean and efficient. ::: #### Security Notes * Balancer's **Emergency subDAO** may disable pools or gauges in case of malicious activity * **veBAL holders** can vote to disable gauges at any time ### Gauge Caps Gauge caps limit the maximum percentage of BAL emissions a pool can receive, ensuring efficient incentive distribution across the ecosystem. Use the [Gauge Cap Calculator](https://docs.google.com/spreadsheets/d/1zKb8x1RqJ5Ap7gXPvYn38U0TwjqAQCzBB62N2d7feto/edit?gid=1999793696#gid=1999793696) to determine the appropriate cap for your pool. #### How Caps Are Calculated Caps are determined through a two-phase analysis: **Phase 1: Weight and Market Cap Factors** *Market Cap Factor:* ``` token mcap factor = mcap (in USD millions) / % weighting mcapFactor = sqrt(min(token mcap factor)) ``` *Overall Factor:* ``` overall factor = mcapFactor^weightFactor ``` **Phase 2: Revenue Factor** If a pool remains below the threshold after Phase 1, a revenue factor is applied: * Average percentage of total revenue contributed during the two most recent protocol fee distribution periods * Bounded between 1 and 100 * Multiplied by the overall factor #### Cap Tiers Pools below the threshold after both phases undergo mandatory migration to a gauge with one of these caps: * **2%** cap * **5%** cap * **10%** cap * **Uncapped** (for qualifying pools) ### Checklist ### Resources * [Gauge Creator Tool](https://balancer.defilytica.tools/gauge-creator) * [Enable Gauge Payload Builder](https://balancer.defilytica.tools/payload-builder/enable-gauge) * [Gauge Cap Calculator](https://docs.google.com/spreadsheets/d/1zKb8x1RqJ5Ap7gXPvYn38U0TwjqAQCzBB62N2d7feto/edit?gid=1999793696#gid=1999793696) * [Balancer Forum - veBAL Category](https://forum.balancer.fi/c/vebal/13) * [Gauge Kill List](https://balancer.defilytica.tools/gauge-kill-list) * [BIP-795: Stale Gauge Removal](https://forum.balancer.fi/t/bip-795-kill-stale-gauges-q1-2025/6410) * [Rate Provider Registry](https://github.com/balancer/code-review/tree/main/rate-providers) ## Introduction The Balancer ecosystem is utilizing a modified version of Curve's vyper gauge infrastructure. Here, we outline how you can utilize our staking gauge system. We cover both how you can apply for a veBAL gauge to receive BAL rewards and how secondary reward programs can be set up. ### BAL Incentives through veBAL Gauges BAL is emitted to staking gauges that have been added to our veBAL system. For a pool to be eligible for BAL rewards, it needs to be voted in by governance. :::info For a gauge to be active in Balancer's veBAL voting list, it needs to be added to / enabled via the Gauge Controller. Therefore, a governance proposal has to be put forward to enable a gauge to receive BAL rewards from veBAL voters. Consult the [Gauge Onboarding Guide](./gauge-onboarding.md) for the complete step-by-step process. ::: ### Secondary Reward Token Incentives The MAXYZ service provider has built a sophisticated infrastructure to create and manage secondary reward campaigns for Balancer staking gauges. To make full use of this system, tooling is provided to facilitate the setup. :::info To facilitate the management and configuration of secondary reward programs, the [DAO Operations UI](https://balancer.defilytica.tools/) serves as an entry-point to configure, view and modify secondary reward programs and other related DAO payloads. ::: For secondary reward distributions on Balancer, following limitations apply (given Balancer's staking gauges are based on Curve's Vyper implementation): 1. A staking gauge can have up to 6 reward tokens. It is recommended to use less than 3 to avoid issues if a gauge will receive BAL (and subsequently AURA) rewards. 2. A gauge distributes rewards in a 1 week schedule after receiving funds. Meaning if you deposit 100 Token A on Monday 00:00 UTC, then those 100 tokens will be distributed over 7 days at a rate of 14.285 tokens / day assuming there is BPT staked in the gauge. 3. Each reward token has its own 1 week distribution schedule based on the time of deposit :::warning Directly depositing reward tokens to the gauge contract will result in loss of funds! If you want to manage deposits yourself, make sure the depositor is whitelisted as a [distributor](#whitelisting-reward-tokens-on-a-target-gauge) and that you call `deposit_reward_token` ::: ### Guide: Create a Secondary Reward Token Program on Balancer These sections will provide a step-by-step guide on how to enable, program and distribute secondary rewards based on the rewards injector infrastructure. :::info The MAXYZ service provider is the primary POC for incentive management and is happy to assist you along the way of setting up your incentive plans. You can also manage injectors yourself if you please to do so. ::: #### Step 1: Token Whitelisting Prerequisite for the reward token to be properly picked up by the infrastructure is that it is whitelisted in the tokenlist. Whitelist the reward token by doing a pull-request [here](https://github.com/balancer/tokenlists). Make sure you are providing a checksummed entry for the relevant network. #### Step 2: Gauge Creation Before setting up secondary rewards, you need a gauge for your pool. Follow the [Gauge Onboarding Guide](./gauge-onboarding.md) to create your gauge. This guide covers: * Creating gauges for Ethereum Mainnet pools * Creating child chain and root gauges for L2 networks * Understanding gauge caps Once your gauge is created (governance approval is only required if you want BAL emissions), you can proceed with configuring secondary rewards. #### Step 3: Rewards Injector Creation :::info The MAXYZ service provider is available to help setup and deploy rewards injectors. For more information on the Injector v2 infrastructure, consult the repository [documentation](https://github.com/balancer/ChildGaugeInjectorV2). ::: Depending on your use-case you want to create a rewards injector for your reward token. In that case, you need to follow a series of configuration steps outlined below. A rewards injector has the purpose of streamlining the distribution of rewards to gauges on Balancer. It takes care of correct token deposits and timely execution based on Chainlink automation. Furthermore, the rewards injector infrastructure is fully customizable and manageable through the [operations UI](https://balancer.defilytica.tools/rewards-injector) overall streamlining the process. Follow these steps if you want to utilize this infrastructure: 1. Create a new rewards injector from the factory using the Injector Creator [interface](https://balancer.defilytica.tools/injector-creator-v2). Depending on your needs, choose different initial configuration parameters :::tip The canonical factory for injectors v2 can be accessed via `0x6142582f8946bf192a4f80ed643a5856d18a7060` on all networks Balancer is currently deployed to. ::: 2. If your new injector has been set up correctly, it will show up in the Injector v2 viewer [drop-down list](https://balancer.defilytica.tools/rewards-injector?version=v2) 3. For the injector to properly work, set up Chainlink automation as outlined in the [injector documentation](https://github.com/balancer/ChildGaugeInjectorV2?tab=readme-ov-file#setting-up-a-chainlink-automation-balancer-maxi-specific-notes). If this is not configured, the injector will not automatically trigger reward distributions to gauges. #### Step 4: Gauge Configuration ##### Whitelisting Reward Tokens on a Target Gauge :::tip Deploying secondary incentives on Balancer is not fully permissionless. For a token to be added as reward token, an authorized multi-sig needs to whitelist that token. The MAXYZ service provider controls this infrastructure and will facilitate whitelisting. ::: A gauge can only receive secondary token rewards from a registered `distributor`. On the gauge contract you can read the current configuration via the `reward_data` field by passing the reward token address as input argument. If your reward token is not registered, follow these steps: 1. Go to the [Add Reward Token to Gauge](https://balancer.defilytica.tools/payload-builder/add-reward-to-gauge) payload builder on the operations UI 2. For the input arguments, do the following: * Target gauge: the gauge you want to whitelist * Reward token: your desired reward token * Distributor address: your injector or alternative reward distributor 3. Click "Add Reward" 4. Generate payload and review / simulate via tenderly 5. Do a pull request via the operations UI. The payload will be reviewed and loaded within 12h of receiving the request 6. Once the payload has been executed by the managed multi-sig, you should see the reward token configuration by using the `reward_data` method #### Step 5: Rewards Injector Configuration :::info Be careful when setting up rewards schedules. If Chainlink automation and a program without a start timestamp are setup, this will mean that incentives will directly be distributed if they are present in the injector ::: Comprehensive infrastructure and tooling has been built to make this process as easy as possible. Before configuring an injector make sure the following criteria are met: * Gauge created (see [Gauge Onboarding Guide](./gauge-onboarding.md)) * Reward token and injector as distributor correctly set up * Reward token is whitelisted on the Balancer [tokenlist](https://github.com/balancer/tokenlists) * Chainlink Automation: Injector Upkeep is correctly configured and there is enough LINK to fund the upkeep (more details on this topic [here](https://github.com/balancer/ChildGaugeInjectorV2?tab=readme-ov-file#setting-up-a-chainlink-automation-balancer-maxi-specific-notes)) Now you can create your own schedule with the [injector configuration tool](https://balancer.defilytica.tools/payload-builder/injector-configurator?version=v2) ![Injector Configurator](/images/incentive-management/injector_config_1.png) 1. Click on "Add Recipients" 2. Choose the parameter set for your incentive program: * Recipients: Enter your target gauge(s) you set up in the previous steps * Define the amount per one week period you want to emit * Define for how many periods (weeks) the program will run * If you want to define a specific start date, fill out a UNIX time stamp 3. Generate the payload 4. Review if the Tenderly simulation passes correctly 5. If the MAXYZ service provider is set as manager, do a pull request via the operations UI. If you have set your own multi-sig or other EOA as manager, execute the payload via your safe. #### Step 6: Funding of the Rewards Injector Funding is straightforward: you can simply deposit funds into the injector contract. Rest assured that the configured `owner` can sweep any amounts left in the injector at any time. :::warning If you have set up a new injector, or if you have modified an incentive program, we advise to only fund it, when you are certain that the configuration will result in the desired outcome. Once an injector has an active program without any start date in the future, it will immediately release funds / start the program after receiving them. ::: ### Secondary Reward Setup Checklist Given the many steps involved in setting up a secondary rewards program, we made this checklist for you to go through based on the above step-by-step guide: ### Direct Incentives on Aura Finance Aura Finance is a yield aggregator protocol built on top of Balancer. It allows to configure and stream rewards to their staking contracts (gauges) through their UI. Consult [their docs](https://docs.aura.finance/developers/how-to-___/add-extra-incentives-to-aura-pools) on how to set up direct incentives. Note that incentives placed on the AURA UI will only be streamed to AURA staking gauges and not Balancer gauges! ## Partner Onboarding :::info From product details to step-by-step integration guides, this documentation provides partners with everything they need to onboard into the Balancer Ecosystem. ::: ### Overview #### Balancer Technology With a vastly simplified developer experience, 100% boosted pools, LST base pairings, and customizable pool hooks, Balancer v3 provides partners with a streamlined hub to optimize yield-bearing liquidity, build robust governance tokenomics, and streamline AMM innovation. \::: tip Want to learn more about Balancer? Click [here](../../concepts/core-concepts/introduction.md) \::: #### Unique AMM Designs and Products Balancer Technology provides innovative AMM designs such as 1. **[Boosted Pools](./products/boostedpools.md)**: Leverage token utilization for optimal yield. 2. **[Yield-Bearing liquidity pools](products/lstandlrt.md)**: Optimized LP tech for liquid-staking tokens (LST) and yield-bearing stablecoins. 3. **[ve8020 / Governance Tokenomics](products/ve8020.md)**: Enhanced liquidity dynamics and aligned incentive structures for governance positions. 4. **[Stable Pools](products/stablecoinliquidity.md)**: Advanced technology for highly correlated stable assets, such as Gyro-stable pools. ### High-Level Overview of Onboarding Process Onboarding into Balancer varies depending on the AMM technology you want to use. Some components are simple and straightforward to use, while others may require expert assistance from our core contributors. Learn more about our onboarding journey, use the Balancer Tech Product Wizard to find the best solution for your needs, or check out our product case studies below. :::info When adding new tokens to Balancer pools, keep in mind the [Vault compatibility requirements](../../partner-onboarding/onboarding-overview/core-pools.md#token-requirements). ::: #### Onboarding Journey Onboarding your asset onto Balancer involves three main steps: * **Choose a Pool Type**: Select the pool type that best fits your use case. * **Customize Pool Logic**: Utilize Balancer v3's [hooks](../../concepts/core-concepts/hooks.md) for tailored functionality. * **Engage in Governance**: [Apply for a gauge](./../onboarding-overview/gauge-onboarding.md) to receive BAL rewards for your pool. \::: tip Balancer contributors are happy to help during the onboarding process. Feel free to reach out to us on our [Discord](https://discord.balancer.fi)! \::: #### Find the Right Product for Your Needs Use our Product Wizard to discover the best solutions for your business. Explore use cases and read success stories. ### Balancer Deployments Looking for deployment-specific information? Choose a specific deployment type below to explore various options and next steps. ## Rate Providers Rate providers are contracts that provide an exchange rate between two assets. For example, the rate of stETH to wstETH. If a pool is created with tokens that use unreviewed rate providers, it will not show on [balancer.fi](https://balancer.fi/pools) \::: tip * Technical information on rate providers can be found [here](../../concepts/core-concepts/rate-providers.md) * To submit your own rate provider contract for review, create an issue [here](https://github.com/balancer/code-review/issues/new?assignees=mkflow27\&labels=request\&projects=\&template=review-request.yml) \::: ### When is a rate provider needed? The primary use-case of a rate provider is the deployment of liquidity in a composable stable pool consisting of correlated or non-correlated assets containing a yield-bearing asset. If 50% or more of the tokens are yield-bearing, the pool is eligible to be flagged as a [core pool](/partner-onboarding/balancer-v2/core-pools.html) to receive a share of fees as voting incentives ### What are the requirements for a rate provider contract? * Must implement the [`IRateProvider`](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/solidity-utils/helpers/IRateProvider.sol) interface, which simply requires a `getRate()` function that returns an 18 decimal fixed point number that is the exchange rate of the token to some other underlying token. * If the rate provider or any underlying portion of the rate can be upgraded, changed, manipulated in any way it will not be owned by or executed by an EOA. It must be a multisig with a minimum set up of 3/5 signers. * Timelock is encouraged but not required. * Should not be susceptible to donation attacks. * Should be monotonic (up only) in nature. \::: warning Warning It is essential for the rate provider to behave in the expected way at all times. Scenarios where for example an upgrade makes the `getRate` call revert, swaps through the pool will revert. If the rate provider returns unexpected values the pool can get drained. \::: ### What are the steps needed to set up a rate provider with Balancer? 1. Build a rate provider based on the above mentioned requirements and consult the Balancer RP registry 2. Make an [issue](https://github.com/balancer/code-review/issues/new?assignees=mkflow27\&labels=request\&projects=\&template=review-request.yml) against the RP Registry to start the code review process 3. Wait for the review to finish (takes approx. 1-2 weeks depending on the backlog) 4. Implement any needed changes that are required to pass the review 5. Upon merge the rate provider is vetted and a composable stable pool can be set up with the newly implemented rate provider set. ### Resources * [IRateProvider interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/solidity-utils/helpers/IRateProvider.sol) * [Request a rate provider review](https://github.com/balancer/code-review/issues/new?assignees=mkflow27\&labels=request\&projects=\&template=review-request.yml) * [Registry of completed rate provider reviews](https://github.com/balancer/code-review/tree/main/rate-providers) ## Voting Incentive Marketplaces The Balancer community voted in favor of centralizing voting incentives to StakeDAOs Votemarket with [BIP-903](https://forum.balancer.fi/t/bip-903-transition-core-pool-incentive-program-to-stake-dao-s-votemarket-v2/6928) Below is a list of current and former voting incentive marketplaces: * [StakeDAOs Votemarket](https://votemarket.stakedao.org/balancer) - default voting marketplace, aggregating core pool incentives. * [Paladin Quests](https://quest.paladin.vote/#/bal) - provides marketplaces for both veBAL and vlAURA voting. * [HiddenHand](https://hiddenhand.finance/balancer) - officially sunset in December 2025 Balancer DAO does not endorse any voting market and encourages protocol participants to explore available options independently. ### Voting Markets and the Core Pool Framework A fraction of core pool revenue is recycled back into voting markets as voting incentives to align BAL token emissions with pool performance as part of our [fee model](../../concepts/protocol-fee-model). The [core pool framework](core-pools.md) allows participants to benefit from Balancer's success and attract more liquidity through attractive emissions. The configuration of the emission ratio is decided by Balancer governance and is subject to change. The Balancer DAO currently places voting incentives in USDC on StakeDAOs Votemarket. Incentives are distributed across Mainnet, Arbitrum and Base depending on the source gauge and StakeDAOs incentive configurations. ### Voting Market Emission Efficiency and Core Pools A topic that comes up frequently when interacting with voting markets is the analysis of emission efficiency. The emission efficiency is a ratio of the $ value of placed voting incentives vs emissions received for votes placed. Ideally, for an entity placing voting incentives, emission efficiency should stay above 1, so that more token rewards are emitted to a gauge than invested in $ value terms. Note that the voting marketplace landscape is a dynamic system and depends on many factors like overall incentive allocation, token prices and market dynamics. The core pool framework dictates placing of voting incentives independent of emission efficiency as per current [voted in governance](https://forum.balancer.fi/t/bip-19-incentivize-core-pools-l2-usage/3329). Therefore, it is crucial for parties interested in placing voting incentives to understand these implications and act accordingly. #### Estimating voting efficiency The community has provisioned several tools to evaluate voting incentive efficiency. One such tool can be consulted for the [AURA marketplace](https://aura.defilytica.com/#/incentiveSimulator) and the [Balancer marketplace.](https://defilytica.tools/#/balancer/incentiveSimulator) Note that these tools do not guarantee correct outcomes as the voting incentive market is a dynamic system that reacts to token prices, incentive volume and voter participation. ## Batch Swap ## Pools ## Smart Order Router ## Overview We have implemented oracles for both Weighted and Stable Balancer pools, as well as Gyro E-CLPs. These are in the `oracles` package: `WeightedLPOracle`, `StableLPOracle`, `EclpLPOracle`, and associated factories. ![Inheritance Diagram (for Weighted and Stable)](/images/BPTOracles.png) ### Oracle Factories Oracles are created from the corresponding pool factories using the create function: `function create(IBasePool pool, bool shouldUseBlockTimeForOldestFeedUpdate, bool shouldRevertIfVaultUnlocked, AggregatorV3Interface[] memory feeds) external returns (ILPOracleBase oracle);` If `shouldUseBlockTimeForOldestFeedUpdate` is set, `latestRoundData` returns the current time for `updatedAt`, instead of calculating the minimum update time over all the feeds (i.e., using the update time of the "oldest" / most stale feed). If `shouldRevertIfVaultUnlocked` is set, operations requiring calculation of the TVL will revert if the Vault is unlocked (i.e., in the middle of an operation). This guarantees that the BPT balance is "real," and not transient, which would make the result manipulable in cases where no other protective mechanisms are available (such as using wrapped BPT, or imposing limits in the lending protocol). `AggregatorV3Interface` is a Chainlink price feed. Note that this assumes the feed array is parallel to the pool tokens; i.e., the feed at each position is the correct one for the corresponding pool token. There is no way for the code to check this, so responsibility falls on the caller to ensure that this is the case. See the [Usage with Rate Providers](./bpt-oracles.md#usage-with-rate-providers) section for further considerations. Note that there are no restrictions in the factories that limit oracle creation, **so any mistakes can be remedied by simply calling create again with correct values**. This first computes an "OracleID" as the hash of the pool and price feed addresses. Since the tokens must be ordered, we know the price feed addresses will also be in the same order, so a given combination of the pool and price feeds is guaranteed unique. It then calls an internal `_create` (implemented by derived contracts) to deploy the oracle with the given configuration, and registers it with the factory. The base factory contract [interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/oracles/ILPOracleFactoryBase.sol) defines functions to find an oracle given pool and feed addresses, and check whether a given oracle was deployed by the official factory: Note that all basic parameters must be specified. Distinct oracles can be deployed with all combinations of flag settings. ``` function getOracle( IBasePool pool, bool shouldUseBlockTimeForOldestFeedUpdate, bool shouldRevertIfVaultUnlocked, AggregatorV3Interface[] memory feeds ) external view returns (ILPOracleBase oracle); function isOracleFromFactory(ILPOracleBase oracle) external view returns (bool success); ``` ### Oracle contracts The common factory contract [interface](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/interfaces/contracts/oracles/ILPOracleBase.sol) defines: `function calculateTVL() external view returns (uint256 tvl);` `function calculateTVLGivenPrices(int256[] memory prices) external view returns (uint256 tvl);` This function computes the total value of the pool, solving the pool and price constraints simultaneously as described in the [main article](./bpt-oracles.md#derivation). THe function without price arguments uses current prices. Just as the price feeds implement the Chainlink interface `AggregatorV3Interface` to fetch individual token prices, the common factory [contract](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/oracles/contracts/LPOracleBase.sol) also implements this interface, and calling `latestRoundData` invokes the virtual `calculateTVL` function to get the total value, then divides by the total supply to get the BPT price. ## Numerical example Consider a Stable Pool containing USDC and USDT, where USDT has slightly de-pegged (say, to 0.98). p₁ = $1.00 (USDC), p₂ = $0.98 (USDT)
A = 100, D = 1000 (invariant) Step 1: Calculate the parameters (amplification coefficient, and "helper" constants derived from the Amplification parameter) a = A × n^(2n) = 100 × 2^4 = 1600
b = a - n^n = 1600 - 2^2 = 1596
c = b/a = 1596/1600 = 0.9975 Step 2: Calculate the r values (scaled prices) r₁ = p₁/a = 1.00/1600 = 0.000625
r₂ = p₂/a = 0.98/1600 = 0.0006125 Step 3: Find the root, using Newton's method: Choose the starting point: k₀ = (1 + 1/(1+b)) × ρ k₀ = (1 + 1/(1+1596)) × 1633 = (1 + 1/1597) × 1633 ≈ 1634.02 Newton's method:
kₙ₊₁ = kₙ − G(kₙ) / G′(kₙ); where G(k) = T(k)³ × P(k) - α
G'(k) = T²(k) × P(k) × \[(3T'(k) × P(k) + T(k) × P'(k))/P(k)] And: T'(k) = -r₁/(kr₁-1)² - r₂/(kr₂-1)²
P'(k) = P(k) × \[r₁/(kr₁-1) + r₂/(kr₂-1)] First iteration (k₀ = 1634.02) Calculate T(1634.02): T = 1/(1634.02×0.000625-1) + 1/(1634.02×0.0006125-1) - 1
T = 1/(1.0213-1) + 1/(1.0008-1) - 1
T = 1/0.0213 + 1/0.0008 - 1 = 46.95 + 1250 - 1 ≈ 1295.95 Calculate P(1634.02): P = (1634.02×0.000625-1) × (1634.02×0.0006125-1)
P = 0.0213 × 0.0008 ≈ 0.000017 Calculate G(1634.02): the "error" G = (1295.95)³ × 0.000017 - 1588.03
G = 2.17×10⁹ × 0.000017 - 1588.03 ≈ 36,890 - 1588 ≈ 35,302 Calculate derivatives and find k₁: T' = -0.000625/(0.0213)² - 0.0006125/(0.0008)² ≈ -1380 - 956 ≈ -2336
G' ≈ ... (complex calculation) ≈ 50.8 k₁ = 1634.02 - 35,302/50.8 ≈ 1634.02 - 694.9 ≈ 939.1 Continue iterations: \| Step | k̃ₙ | G(k̃ₙ) | k̃ₙ₊₁ | \| ---- | ------- | ------ | ------- | \| 0 | 1634.02 | 35,302 | 939.1 | \| 1 | 939.1. | -285.4 | 1425.7 | \| 2 | 1425.7 | 892.1 | 1580.3 | \| 3 | 1580.3 | 124.7 | 1635.8 | \| 4 | 1635.8 | 8.2 | 1640.1 | \| 5 | 1640.1 | 0.1 | 1641.0 | \| 6 | 1641.0 | \~0 | 1641.0 | Graphical representation of the T curve: ![Find the root](/images/BPTOracleIllustration.png) Step 4: Calculate T (using our found k̃ = 1641) Recall that T = 1/(k̃r₁ - 1) + 1/(k̃r₂ - 1) - 1 T = 1/(1641 × 0.000625 - 1) + 1/(1641 × 0.0006125 - 1) - 1
T = 1/(1.025625 - 1) + 1/(1.0051125 - 1) - 1
T = 1/0.025625 + 1/0.0051125 - 1
T = 39.02 + 195.60 - 1 = 233.62 Step 5: Calculate effective balances Recall that x₁ = (cD)/(k̃r₁ - 1) × T⁻¹ x₁ = (0.9975 × 1000)/(1641 × 0.000625 - 1) × (1/233.62)
x₁ = 997.5/0.025625 × (1/233.62) = 166.69 x₂ = (cD)/(k̃r₂ - 1) × T⁻¹
x₂ = (0.9975 × 1000)/(1641 × 0.0006125 - 1) × (1/233.62)
x₂ = 997.5/0.0051125 × (1/233.62) = 833.31 Step 6: Calculate total pool value Recall that Pool Value = p₁ × x₁ + p₂ × x₂ Pool Value = $1.00 × 166.69 + $0.98 × 833.31
Pool Value = $166.69 + $816.64 = $983.33 Step 7: Calculate BPT price Recall that BPT Price = Pool Value / Total BPT Supply (If total BPT supply = 1000 tokens, since D = 1000, and starting prices were nominal at $1) BPT Price = $983.33 / 1000 = $0.983 per BPT ## Price Oracles for BPT as Collateral ### Introduction Liquidity provider (LP) tokens represent proportional ownership in a pool of assets. In the Balancer ecosystem, these are known as BPT (Balancer Pool Tokens). When users add liquidity to a Balancer pool by depositing tokens, they receive corresponding Balancer Pool Tokens in return. These tokens represent their proportional share of the liquidity pool. With the rising popularity of lending protocol partners like [AAVE](https://aave.com/), there's growing interest in using these tokens as collateral, allowing users to borrow against their staked liquidity. However, LP tokens pose unique challenges for oracle design and risk management. Their value depends on both the underlying assets and the pool composition and parameters, making them potentially vulnerable to manipulation. Balancer's radical flexibility is a major benefit for builders - yet presents unique challenges for oracle building. Balancer supports not only arbitrary pool types, but also yield-bearing tokens with rate providers, and offers hooks that can alter swap fees, adjust swap results, or otherwise affect pool balances during operations. Flash loans - either explicit using Balancer V2 or other platforms, or implicit in Balancer's batch operations - are especially dangerous, and could be used to temporarily distort pool balances and pricing, inflating the apparent BPT value. The risk is not theoretical! Naive oracle implementations have been exploited many times in recent years: [Alpha Homora](https://blog.alphaventuredao.io/alpha-homora-v2-post-mortem/) lost over $38 million when an attacker manipulated LP token pricing to over-collateralize a loan, while [Harvest Finance](https://medium.com/harvest-finance/harvest-flashloan-economic-attack-post-mortem-3cf900d65217) saw $24 million drained through a similar manipulation of stablecoin Curve pools. These examples highlight the critical need for manipulation-resistant oracles when integrating LP tokens into lending systems. One defense against implicit flash loans is the [WrappedBalancerPoolToken](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/vault/contracts/WrappedBalancerPoolToken.sol), and its associated factory. This factory deploys wrapped versions of arbitrary BPT, which are freely interchangeable with "raw" BPT - but only when the Vault is locked (i.e., when there is no ongoing operation). This ensures that the BPT balance of the caller is "real," and not flash loaned: at least not from Balancer V3. (It could still be an externally flash loaned from another source.) It's one layer of "Swiss cheese" security, but not necessarily sufficient by itself. Note that at least for the case of oracles used in lending protocols, this concern is adequately addressed using either deposit limits in the native protocols, or alternatively setting the `shouldRevertIfVaultUnlocked` flag on the oracle itself, which reverts if anything requiring TVL calculation happens during a transaction (i.e., when the Vault is unlocked). While "balance verification" is important for some operations (e.g., nested pools), what we really want is a reliable way to derive a reliable, non-manipulable price for a Balancer Pool Token: on-chain, and available during Vault operations. This enables many advanced use cases, including our target of BPT as collateral. To do this, we require externally derived, true market prices, as anything internal and calculated purely on-chain could be front-run and manipulated. The current interface uses Chainlink oracles for this, but is easily adaptable to other protocols. There is unlikely to be a Chainlink oracle for the BPT itself (or we could just use that), so our pricing algorithm must operate using price feeds for the constituent tokens. It must incorporate the total pool value and supply - yet *not* the actual token balances, which are manipulable. ### Overview Instead of using the actual balances to derive the overall price, Balancer BPT oracles compute *theoretical* balances which satisfy two constraints: * The "pool constraint": the theoretical balance invariant must equal the real balance invariant, so that the value of the pool is equivalent and can legitimately be scaled by the supply. * The "price constraint": the token prices derived from the theoretical balances must be proportional to the oracle prices, so that the internal token prices correctly reflect the current state of the market. See [this page](./bpt-oracles-contracts.md) for references to the BPT Oracle contracts. ### Usage with Rate Providers In practical usage with different kinds of pools, there are some subtleties. For boosted pools, or other pools where tokens have rate providers, it's very important to choose a compatible price feed. There are several cases: 1. Regular tokens, no rate providers. This is the simplest case (e.g., BAL/WETH or WETH/USDC). We just need price feeds corresponding to the USD prices of ETH, BAL, and USDC. Of course, the price feeds could also have some other reference (e.g., EUR), as long as they are all consistent. 2. Boosted pools with yield-bearing tokens (e.g., wstETH/USDC, or waWETH/waUSDC). Here we would typically have rate providers that converted the wrapped tokens to the underlying value tokens: wstETH -> ETH, waWETH -> ETH, waUSDC -> USDC. Since calculations use "live balances," which incorporate the rates, balances are always expressed in underlying tokens. Therefore, the price feeds should also correspond to the underlying tokens: ETH and USDC, in this case, same as in the "regular token" case above. 3. Pools with double-wrapped tokens, such as waWstETH. What to use here depends on the details of the rate provider. Essentially, the price feed needs to match the target of the rate provider. If the rate provider itself converts waWstETH -> WETH, then the price feed should just be the regular underlying WETH/ETH price (since WETH is pegged 1-to-1 to ETH by definition). However, if the rate provider converts waWstETH -> wstETH, you would need a price feed for wstETH: not ETH. 4. Pools with RWA / commodity tokens, like XAUt. Here the rate provider is already effectively a price feed, converting XAUt -> USD directly. If you combined this rate provider with an XAUt price feed, it would do the conversion twice. So in this case, you would need a "constant price" price feed, that always returned 1. ### Mathematical Derivation #### Stable Pools We begin with a system of n+1 equations: n "gradient" equations (relating the internal prices to the oracle prices), plus the invariant equation. So, that's n+1 equations with n unknowns: the theoretical token balances we are trying to find. We can linearize the gradient equations by introducing k̃, a sort of scaling factor that converts internal to external prices. Since by definition the prices are proportional, we can define k̃ as this constant of proportionality. We can then solve this system of linear gradient equations, and express the theoretical balances xⱼ in terms of k̃. Then the real magic happens - we can substitute those xⱼ expressions into the invariant equation, and reduce the system to a single equation in k̃. On chain, this equation can be solved using Newton's method. Once we have k̃, we can compute the theoretical balances, sum the product of each token balance and oracle price to get the total pool value, then divide by the total supply to derive the final BPT price. With x̃ representing the vector of theoretical token balances, the pool constraint can be expressed mathematically as: ``` F(x̃) = D; or F(x̃, D) = 0 ``` This means that the theoretical balances must reproduce the real invariant. The price constraint can be expressed mathematically as: ``` ∇f(x̃) = k̃ · ρ ``` This means that the internal prices must match (i.e., be proportional to) the oracle prices. ρ (rho) is a "critical boundary" constraint on k̃. We can derive a minimum valid value of k̃ from the oracle prices and degree of price curvature: at this value or below, the "T curve" (described below) goes to infinity and the equation is no longer soluble. To calculate ρ: 1. Compute the amplification coefficient: a = A × n^(2n); A is the amplification parameter of the Stable Pool 2. Compute the scaled prices: rᵢ = pᵢ / a for each token; these are the market prices "normalized" by the pool state 3. Since k̃ must be greater than 1/rₙ for each token, the final lower bound is given by: ρ = 1 / min(r₁, r₂, ..., rₙ) Given these constraints, the key is to find k̃. To understand how, we start with the invariant equation for the pool: ``` f(x₁, x₂, ..., xₙ) = D ``` This is some function which, operating on the real balances, produces a single invariant value D, representing the total value of the pool. The details vary, but the Balancer Vault requires this to be defined for every pool type. So far, we have implemented oracles for both Weighted and Stable Balancer pools. (AutoRange Pools should also work, as they are fundamentally Weighted pools, just incorporating virtual balances. See the AutoRange Pools section below.) The partial derivative ∂f/∂xⱼ represents how much the invariant changes per unit of token j, and the ratio of these partial derivatives represents the internal spot price of one token in terms of another. For instance, ∂f/∂x₂ / ∂f/∂x₁ would be the spot price of token 2 in terms of token 1. The n gradient equations look like: ``` ∂F/∂x₁ = k̃ · p₁
∂F/∂x₂ = k̃ · p₂
...
∂F/∂xₙ = k̃ · p₂ ``` We now apply the second constraint, substituting the x expressions into the pool constraint F(x̃, D) = 0. Since all the x expressions are functions of k̃, we now have a single equation in terms of k̃: one equation, one unknown. After a lot of algebra, the single equation can be written as: ``` T(k̃)^(n+1) · P(k̃) = α ``` where: ``` T(k̃) = Σ(1/(k̃rᵢ - 1)) - 1 ``` from the gradient equations, where the r values are the scaled prices described above; ``` P(k̃) = ∏(k̃rᵢ - 1) ``` also from the gradient equations; and ``` α = a·c^(n+1) ``` a constant derived from the pool parameters, where: ``` a = A·n^(2n);
b = a - n^n; and
c = b/a ``` Unfortunately this "T equation" is non-linear in `k̃`, so it must be solved numerically. On-chain, we use Newton's method to find the root (= the value of k̃ that satisfies both constraints). In the mathematical paper referenced below, we prove that given the specified starting point, it will converge to the correct solution. There may be many roots, especially with higher numbers of tokens. The correct one is the smallest non-negative root, closest to the origin. In summary: * Oracle prices tell us what the "fair" relative prices should be * The gradient condition ensures that internal AMM prices correspond to these fair prices * The invariant condition ensures that the theoretical balances are valid for the pool, and correctly represent the value * `k̃` is the common scaling parameter: the "knob" we adjust to make both conditions true simultaneously * Once we find the right k̃, we can calculate the fair balances `x̃ⱼ`, and price the LP token See [this page](./bpt-oracles-example.md) for a numerical example. #### Weighted Pools Weighted and Stable Pools use the same general algorithm. While the complex StableSwap invariant requires Newton's method to find the scaling parameter k̃, then calculate effective balances and total value, the power-law invariant of the Weighted Pool allows us to solve the gradient and invariant conditions simultaneously, giving us: ``` TVL = k × Π((Pᵢ/Wᵢ)^Wᵢ) ``` in one step. In particular, "mapping" the weighted pool solution onto the equivalent terms used above: * The theoretical balances `x̃ᵢ = (TVL × Wᵢ)/Pᵢ`, where TVL is the total pool value (referred to as `Bᵢ` in the WeightedLPOracle code docs). * The Weighted invariant `D = Π(Bᵢ^Wᵢ)`, computed directly using theoretical balances and weights (referred to as k in the WeightedLPOracle code docs). * The scaling parameter `k̃` is in the Weighted case simply equal to the TVL (`C` in the code docs). So, positing a normalization constant `C` such that `C = (Pᵢ × Bᵢ / Wᵢ)` for every token, the gradient "price constraint" is: ``` Bᵢ = (C × Wᵢ)/Pᵢ ``` This set of balances align the pool's spot price with external prices provided by the feeds. We can then substitute this directly into the invariant (no complex polynomials here): ``` D = Π((C × Wᵢ/Pᵢ)^Wᵢ) = C × Π((Wᵢ/Pᵢ)^Wᵢ) ``` And then solve for C directly: ``` C = k × Π((Pᵢ/Wᵢ)^Wᵢ) = TVL = Total pool value ``` The price is then simply TVL / totalSupply. #### AutoRange Pools The same principle can be applied to an AutoRange Pool, which is in practice a 50/50 weighted pool with virtual balances that concentrate the liquidity in a specified price range. The AutoRange Pool invariant is ``` D = (Ra + Va)(Rb + Vb) ``` where `Ri` represents real balances and `Vi` represents virtual balances. On the other hand, the spot price of the pool is: ``` P = Tb / Ta = (Rb + Vb) / (Ra + Va) ``` So we can express the invariant in terms of price: ``` D = P (Ra + Va)^2 = (Rb + Vb)^2 / P ``` Isolating total balances: ``` sqrt(D / P) = Ta sqrt(D * P) = Tb ``` So ``` Ra = Ta - Va = sqrt(D / P) - Va Rb = Tb - Vb = sqrt(D * P) - Vb ``` With the given price feeds `Pᵢ`, we can find the theoretical real balances `x̃ᵢ = [Ra', Rb']`, and compute the TVL: ``` TVL = Σ (x̃ᵢ × Pᵢ) = Ra' × Pa + Rb' × Pb ``` ##### Practical constraints The calculated `Ra'` and `Rb'` in the step above can be negative, or above the maximum real balance. Therefore it needs to be capped on both ends: ``` Ra' = max(0, Ra'); Ra' = min(Ra', Ra_max) ``` To calculate maximum real balances, we can evaluate the invariant at the edges. In particular, `Ra` is `Ra_max` when `Rb = 0`, and vice versa: ``` D = (Ra_max + Va)(Vb) D = (Va)(Rb_max + Vb) ``` Isolating max real balances: ``` Ra_max = D / Vb - Va Rb_max = D / Va - Vb ``` #### E-CLP Pools (Elliptic Concentrated Liquidity Pools) E-CLP pools use a concentrated liquidity mechanism based on an elliptical curve constraint, allowing liquidity to be concentrated within a specified price range \[α, β]. Unlike Weighted Pools which use simple power-law invariants or Stable Pools which use polynomial invariants, E-CLPs leverage geometric transformations to map between price space and balance space. The pool's invariant constraint defines an ellipse in the (x, y) balance space, rotated and stretched according to the pool parameters. The key parameters are: * **α (alpha)**: The lower bound of the concentrated price range * **β (beta)**: The upper bound of the concentrated price range * **τ(α) and τ(β)**: Derived points on the ellipse corresponding to the price bounds * **A**: A transformation matrix that maps the unit circle to the pool's ellipse Given oracle prices P₁ and P₂ for the two tokens, the relative price is r = P₁/P₂. The TVL calculation depends on where this relative price falls within the concentrated range: **Case 1: r < α (Below the lower price bound)** When the market price is below the pool's concentrated range, the pool holds only token 1. The effective balance width in price space is: ``` b_P = (A⁻¹·τ(β))_x - (A⁻¹·τ(α))_x ``` And the total value locked is: ``` TVL = b_P × P₁ × invariant ``` **Case 2: r > β (Above the upper price bound)** When the market price is above the pool's concentrated range, the pool holds only token 2. The effective balance width is: ``` b_P = (A⁻¹·τ(α))_y - (A⁻¹·τ(β))_y ``` And the total value locked is: ``` TVL = b_P × P₂ × invariant ``` **Case 3: α ≤ r ≤ β (Within the concentrated range)** When the market price is within the pool's range, both tokens are present. We compute the point τ(r) on the ellipse corresponding to the current price ratio, then calculate the effective balance vector: ``` v_x = (A⁻¹·τ(β))_x - (A⁻¹·τ(r))_x v_y = (A⁻¹·τ(α))_y - (A⁻¹·τ(r))_y ``` The total value is then the scalar product of the price vector and the balance vector: ``` TVL = (P₁ × v_x + P₂ × v_y) × invariant ``` In summary: * The `τ` function maps price ratios to points on the ellipse curve, establishing the "fair" position on the curve given market prices * The `A⁻¹` transformation converts points from the ellipse to effective balance space * The three cases handle the geometric reality that outside the `[α, β]` range, the pool is single-sided * Unlike Weighted Pools where TVL can be computed directly, or Stable Pools where Newton's method solves for a scaling parameter, E-CLP uses geometric projections to determine the effective balances * The invariant serves as a scaling factor, similar to its role in other pool types The final BPT price is simply TVL / totalSupply. This approach is resistant to price manipulation within the pool because it relies entirely on external oracle prices to determine the theoretical position on the ellipse, then uses only the pool's invariant (total supply) to scale the result. The geometric constraints of the ellipse ensure consistency between internal and external pricing. ### References See the [contracts](./bpt-oracles-contracts.md) page for practical examples of using the oracles. For a more detailed mathematical description of how this is applied to Stable Pools, see the [Pricing Stable Pool BPTs](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/standalone-utils/docs/StableOracle.md) document in the V3 monorepo. True math nerds can review the [Pricing StableSwap pools](https://github.com/balancer/balancer-v3-monorepo/blob/main/pkg/standalone-utils/docs/pricing_stableSwap_pools.pdf) paper by Sergio A. Yuhjtman, our resident mathematician. This paper goes much deeper into the mathematics, providing a rigorous theoretical backing for the computation methods described in the docs. It reviews the mathematical properties of the StableSwap function, especially the upper/lower price bounds. It analyzes Newton's method, proving that the function `G(k)` is convex (which ensures convergence), and justifies the choice of starting point `k₀`. Newton's method is iterative, converging on the final value of `k̃` by refining subsequent guesses, so it is important for performance to start in the right place. The paper proves the existence, uniqueness, and guaranteed monotonic convergence on `k̃` from `k₀`. Finally, it analyzes edge cases, and proves that the boundary condition `k̃rᵢ - 1 > 0` always holds under all supported conditions (i.e., balances, invariant, and amplification parameter are all > 0, and there are at least two tokens). ### Gyroscope Pool Types There are two kinds of Gyroscope Pools on Balancer v3: 2-CLP and E-CLP, both of which are 2-token pools. * [Gyro 2-CLP](./gyro-2clp.md): Quadratic concentrated liquidity pools concentrate liquidity within a price range. A given 2-CLP is parameterized by the price range \[α,β], and the two assets in the pool. * [Gyro E-CLP](./gyro-eclp.md): Elliptic CLPs are likewise efficient liquidity concentrators, so named because their price curve is an ellipse, technically formed by transforming a “constant circle” with stretch (lambda), rotation (phi), and displacement (alpha, beta) parameters. Like 2-CLPS, these parameters are fixed on deployment. Every pool type is associated with its own Factory contract, facilitating the creation of new pools. Experienced developers can find the factory deployment addresses through [this resource](../../../developer-reference/contracts/deployment-addresses/mainnet.html). For further assistance, individuals are encouraged to contact our developers via [Discord](https://discord.balancer.fi/). ## Gyroscope 2-CLP Pools ### Overview These pools use the following invariant: (x + a)(y + b) = $L^2$. Given quantities of real reserves (x,y) in the pool and the pool’s price range \[α,β], these "offsets" a and b can be calculated as $a = \frac{L}{\sqrt{\beta}}$, $b = \frac{L}{\sqrt{\alpha}}$. They describe the amount of "virtual reserves" the pool adds to real reserves to generate the curve defining the price range. In the code, the sum of the real balance and offset is known as the "virtual balance" of each token. Note that native Gyro pools also have a 3-CLP, 3-token version of the pool, which uses a cubic invariant - (x + a)(y + a)(z + a) = $L^3$ - that essentially amplifies the capital efficiency benefits, vs. a corresponding pair of 2-CLPs. \::: info Gyro 2-CLPs are always two-token pools. * The minimum swap fee percentage is 0.0001% * The maximum swap fee is not constrained (i.e., 100%) * The invariant growth ratios are unconstrained * The key parameters are Alpha (α), the lower bound of the 2-CLP price curve, and Beta (β), the upper limit of the price interval. \::: Note that the swap fee and invariant limits are defined in `Gyro2CLPPool` through implementing the `ISwapFeePercentageBounds` and `IUnbalancedLiquidityInvariantRatioBounds` interfaces, which are included in `IBasePool`. See [here](../../../../integration-guides/aggregators/pool-maths-and-details.html) for a more detailed reference. ![2-CLP price curve illustration](/images/2-clp-v2.gif) Source: [Gyroscope Docs](https://docs.gyro.finance/gyroscope-protocol/concentrated-liquidity-pools/2-clps) ### Advantages #### Higher Fees for LPs Concentrated liquidity allows the LPs' entire contributions to be used within a relatively narrow expected price range, as opposed to distributing it along the entire price curve, where most of it would be idle. Since the price range is fixed and common to all LPs, there is no individual variation (as there would be in NFT-based CL AMMs like Uniswap, where each LP's position is tracked individually), but these pools are more capital efficient than, for instance, regular Weighted Pools. #### Better execution for traders Another effect of concentration is effectively "deeper" liquidity for traders, which allows larger trades with lower slippage and better overall execution prices. ### Impermanent Loss [Impermanent Loss](../weighted-pool/impermanent-loss.md) is the difference in value between holding a set of assets and providing liquidity for those same assets. This can occur with these pools, and the liquidity concentration somewhat increases this risk, especially with narrow price range settings. ## Gyroscope E-CLP Pools ### Overview Elliptic CLPs, or E-CLPs, allow trading along the curve of an ellipse. Similar to other CLPs, E-CLPs are designed to concentrate liquidity within price bounds. However, E-CLP liquidity is much more flexible. Just as 2-CLP pools concentrate liquidity over a range, vs. spreading it uniformly over the entire (infinite) price range, E-CLP pools can focus liquidity asymmetrically over the already restricted range defined by the alpha and beta parameters. \::: info Gyro E-CLPs are always two-token pools. * The minimum swap fee percentage is 0.000001% (note - lower than the Stable Pool minimum by an order of magnitude) * The maximum swap fee is not constrained (i.e., 100%) * The invariant cannot decrease below 60% or increase beyond 500% on liquidity operations (same limits as the standard Stable Pool) * The key parameters are Alpha (α), the lower bound of the E-CLP price curve, and Beta (β), the upper limit of the price interval. * The lambda parameter (>= 1) determines the "stretch" of the curve. It behaves like the reciprocal of the eccentricity. 1 would be a perfect circle = price-bounded StableSwap. Higher values increasingly concentrate the liquidity around the center (e.g., $1), with less and less in other regions of the bounded price curve. * The phi parameter measures the "rotation" of the curve. Other parameters (documented in the code), are combinations or functions of these fundamentals. For instance, c = cos(-phi). \::: Note that the swap fee and invariant limits are defined in `GyroECLPPool` through implementing the `ISwapFeePercentageBounds` and `IUnbalancedLiquidityInvariantRatioBounds` interfaces, which are included in `IBasePool`. See [here](../../../../integration-guides/aggregators/pool-maths-and-details.html) for a more detailed reference. ![E-CLP price curve illustration](/images/E-CLP-v1.gif) Using rate providers, which (depending on their sensitivity and tracking speed) can track the price much more closely, preserving capital by reducing or eliminating arbitrage opportunities caused by lagging prices. ![Rate providers](/images/Rate-providers-v8.gif) Source: [Gyroscope Docs](https://docs.gyro.finance/gyroscope-protocol/concentrated-liquidity-pools/e-clps) ### Advantages #### Higher Fees for LPs Concentrated liquidity allows the LPs' entire contributions to be used within a relatively narrow expected price range, as opposed to distributing it along the entire price curve, where most of it would be idle. Since the price range is fixed and common to all LPs, there is no individual variation (as there would be in NFT-based CL AMMs like Uniswap, where each LP's position is tracked individually), but these pools are more capital efficient than, for instance, regular Weighted Pools. #### Better execution for traders Another effect of concentration is effectively "deeper" liquidity for traders, which allows larger trades with lower slippage and better overall execution prices. ### Impermanent Loss [Impermanent Loss](../weighted-pool/impermanent-loss.md) is the difference in value between holding a set of assets and providing liquidity for those same assets. This can occur with these pools, and the liquidity concentration somewhat increases this risk, especially with narrow price range settings. ## Fixed Price LBP A **Fixed Price LBP** is a Liquidity Bootstrapping Pool that keeps a **constant exchange rate** between the project token and the reserve token for the whole sale. Unlike standard LBPs, there is no scheduled weights changes. ### When to use it? Use a Fixed Price LBP when you want a **constant-rate token sale**: one project token always costs the same amount of reserve token (1 TOKEN = 10 USDC). Use a **weight-shifting LBP** when you want price discovery (check [Token Launches](./token-launches.md)). ### How it works? * **Constant price:** The pool uses a fixed rate (e.g. `projectTokenRate`). Swaps are simple: reserve in → project out at that rate (or the reverse, depending on configuration). * **Invariant:** The pool uses a constant-sum style invariant: `projectBalance * projectTokenRate + reserveBalance`, i.e. total value in terms of the reserve token. No weighted math or time-dependent weights. * **Buy-only (one-way):** Fixed Price LBPs are configured so that only **buying** the project token with the reserve token is allowed. Selling the project token back into the pool is disabled. * **Seedless:** Because the sale is one-way, the pool is initialized with **project tokens only**. No reserve tokens are required up front; buyers supply the reserve token when they swap. ### Summary \| Aspect | Weight-shifting LBP | Fixed Price LBP | \| :----------------------------- | :----------------------------------------------- | :---------------------------- | \| **Price** | Changes over time (weight schedule) | Constant (fixed rate) | \| **Use case** | Price discovery (launches, divestment, buybacks) | Constant-rate sales | \| **Initial liquidity** | Project + reserve (or seedless for some configs) | Project token only (seedless) | \| **Selling project token back** | Configurable | Disabled (buy-only) | ## Liquidity Bootstrapping Pools (LBPs) ### Overview Balancer offers two LBP variants: * **Weight Shifting LBPs**: Pools that change token weighting over time (e.g. 90/10 → 10/80 project/reserve token). They use Weighted Math with time-dependent weights for price discovery. * **[Fixed Price LBP](./fixed-price-lbp.md)**: Pools that keep a constant exchange rate for the whole sale (no weight schedule). Used for constant-rate token sales; buy-only and seedless by default. The pool owner is the only address that can add liquidity to the pool, which must be done prior to the start of the sale. Furthermore, the proceeds can only be removed after the end time. ### Use Cases (weight-shifting LBPs) The following use cases rely on **weight-shifting** LBPs (scheduled weights changes). For constant-rate sales, see [Fixed Price LBP](./fixed-price-lbp.md). #### 1. [Token Launches](./token-launches.md) **Objective:** Fair initial token distribution. The standard LBP configuration supports price discovery for new tokens via a continuous Dutch-auction-style mechanism. The pool starts with a high project-token weight (e.g., 90% project token / 10% reserve) and gradually shifts over time to a lower project-token weight (e.g., 20% / 80%). This scheduled reweighting creates controlled downward price pressure, helping distribute tokens more broadly while reducing incentives for bot sniping and whale concentration. Buyers can enter when the price reaches a level they consider fair, rather than rushing into a priority-fee bidding race at launch. #### 2. [Treasury Diversification](./treasury-diversification.md) **Objective:** Position entry and exit with minimal market impact. For DAOs and treasuries managing significant capital, LBPs can be used to enter or exit positions over time via a scheduled weight shift. This allows treasuries to: * **Divest:** Sell large positions with reduced market impact. * **Invest:** Accumulate positions in target tokens with reduced market impact. #### 3. [Token Buybacks](./token-buybacks.md) **Objective:** Efficient protocol token accumulation. An LBP configured for accumulation inverts the weight logic to create upward price pressure. By starting with high reserve token weight (90% WETH / 10% project token) and shifting toward the project token (10% / 90%), the pool functions as an automated, rising limit order. ### Pool Settings LBPs are highly configurable. Here are the key parameters and settings, as defined in the pool implementation: * **Tokens**: LBPs are always two-token pools: the project token (being launched) and the reserve token (e.g., a stablecoin or WETH). * **Weights**: The pool owner specifies the starting and ending weights for both tokens. These weights change linearly over time. * **Period**: The pool owner sets the `startTime` and `endTime` (timestamps) for the LBP. Swaps are only enabled between these times. * **Swaps**: Optionally, the pool can block selling the project token back into the pool (`blockProjectTokenSwapsIn`). This is typically enabled for token launches to prevent manipulation, but disabled for token buybacks where sellers must deposit the project token into the pool. * **Trusted Router**: All pool interactions must go through a trusted router to ensure correct sender reporting and security. **Technical Parameters (from the implementation):** * `projectToken` / `reserveToken`: ERC20 addresses for the tokens. * `projectTokenStartWeight` / `reserveTokenStartWeight`: Initial weights (scaled). * `projectTokenEndWeight` / `reserveTokenEndWeight`: Final weights (scaled). * `startTime` / `endTime`: UNIX timestamps for the sale window. * `blockProjectTokenSwapsIn`: Boolean to restrict project token sales. * `poolCreator`: The account accruing [pool creator fees](../core-concepts/pool-creator-fee.md) * Only two tokens are allowed per pool. ## Token Buybacks The same LBP mechanism can be used to support **accumulation** - buying the project token. ### Mental Model For DAOs and treasuries, buying back a large amount of a token can be challenging - **market buys** can create abrupt price impact. An LBP buyback configuration approaches this differently: it uses a scheduled weight shift to create a rising bid, and lets arbitrage keep the pool price aligned with external markets. ### Configuration #### 1. Token Setup * **Reserve token:** The asset the Treasury wishes to spend (e.g., USDC, WETH). * **Project token:** The asset the Treasury wishes to acquire. #### 2. Weight Schedule The weight curve should start with the reserve token dominant. \| Parameter | Example value | Reason | \| :---------------- | :------------------------------------ | :-------------------------------- | \| **Start weights** | 90% reserve token / 10% project token | Keeps the initial bid low. | \| **End weights** | 30% reserve token / 70% project token | Allows the bid to rise over time. | #### 3. Swap Permissions Ensure that `blockProjectTokenSwapsIn` is set to `false`. The mechanism relies on external actors swapping the project token *in* to the pool to extract the reserve token. ## Token Launches The Liquidity Bootstrapping Pool (LBP) is the standard primitive for initial token distribution. The LBP utilizes time-dependent weights to facilitate fair price discovery and capital efficiency. \::: tip New on Balancer v3: Seedless LBPs Token launch LBPs can be configured as **seedless**, meaning they require **no initial liquidity in the reserve token**. \::: #### Mental Model You can think of the starting price of your LBP as the ceiling you would want to set for the token sale. This may seem counterintuitive, but since LBPs work differently than other token sales, your starting price should be set much higher than what you believe is the fair price. This does not mean you are trying to sell the token above what it is worth. Setting a high starting price allows the changing pool weights of your LBP to make their full impact, lowering the price progressively until market equilibrium is reached. Unlike older token sale models, such as bonding curves, users are disincentivized to buy early and instead benefit from waiting for the price to decrease until it reaches a level they believe is fair. ### Advantages #### Capital Efficiency LBPs can be configured as **seedless**, meaning they require **no initial liquidity in the reserve token**. #### Fair Market Price LBPs often start with intentionally high prices. This strongly disincentivizes whales and bots from snatching up much of the pool liquidity at the get-go. When LBPs are used for early-stage tokens, this can help increase how widespread the token distribution is. #### Immediate Liquidity Once the LBP concludes, immediate access to the funds raised is available. The new token holders can immediately trade their token, providing instant liquidity without lengthy lock-up periods. #### Sell Pressure During a weight shift, the token price of one token experiences sell pressure while the other experiences buy pressure. When this is mixed with modest swap volume, the price approaches the generally agreed-upon market price. ### Configuration Token launches using LBPs rely on a scheduled **weight shift**. #### Duration Token launches are typically run over **days** (not weeks or months). A common choice is **48 to 72 hours** (2 to 3 days), which gives participants time to enter without concentrating all activity into a short window. #### Weights To create the necessary "price ceiling," the **project token** should start with a dominant weight and decrease over time. In practice, avoid weight schedules that go all the way to the extremes (close to 100/0 or 0/100), as pricing becomes highly non-linear near the ends of the range. ## Treasury Diversification LBPs can help treasuries and DAOs enter or exit large positions with reduced market impact. \::: tip Balancer V3: Boosted Pools In Balancer V3, one side of an LBP can be a boosted token (e.g., a yield-bearing stablecoin), so idle balances don’t have to sit unproductive during a long-running operation. \::: #### Mental Model LBPs can be used by treasuries to enter (invest) or exit (divest) positions over time, using a scheduled weight shift rather than a single large swap. Arbitrage keeps the LBP internal price aligned with external markets as the sale progresses. ### Strategic Divestment (Selling) Divestment involves selling a large quantity of an asset (e.g., a grant received in an illiquid token, or diversifying treasury holdings) into a more liquid asset like USDC or ETH. Unlike a **Token Launch** (which typically runs over days), a **Strategic Divestment** prioritizes stability and value retention over longer horizons. ### Strategic Investment (Buying) While token buybacks focus on retiring supply, a **Strategic Investment** allows treasuries to acquire large stakes in *other* protocols (e.g., a DAO rotating stablecoins into another governance token or ETH) without triggering a parabolic price response. These operations are often run over weeks or months, rather than days. ### Case Study: The Gitcoin Akita Divestment The viability of using LBPs for institutional-scale divestment was demonstrated by the **Gitcoin DAO**. **Challenge:** In 2021, Vitalik Buterin donated approximately 49 trillion AKITA tokens (valued at \~$5 million) to Gitcoin. The on-chain market depth was insufficient to absorb this liquidity; a direct sale would have resulted in roughly 99% slippage and a collapse of the token's community value. **Solution:** Instead of an atomic sell-off, the DAO deployed a LBP with the following parameters: * **Duration:** 1 Year (slow decay). * **Weight Shift:** 90% AKITA / 10% WETH $\to$ 10% AKITA / 90% WETH. **Results:** Over the 12-month period, the mechanism successfully converted the illiquid asset into WETH. \| Metric | Result | \| :--------------------- | :------------ | \| **Total Funds Raised** | $4.48 Million | \| **Trading Volume** | $22.6 Million | ## AutoRange Pool Math ### Intro The AutoRange Pool is based on a "constant product," essentially equivalent to standard weighted math (with a clever redefinition of the token balances). The idea is to concentrate liquidity by adding price bounds to the constant product curve using virtual balances: $L = (R\_a + V\_a)(R\_b + V\_b)$, where $R\_a$ and $R\_b$ are the real balances of the token, and $V\_a$ and $V\_b$ are the virtual balances of the token. Currently, in fungible concentrated liquidity pools with fixed price intervals, LPs may need to actively migrate liquidity between CL pools to avoid losing fee revenue to pools that have gone out of range. The AutoRange Pool automatically readjusts the price interval, so that a retail LP can confidently deposit assets and rely on the pool to manage them efficiently and profitably. ### Concepts #### Virtual Balances Virtual Balances can be understood as offsets to the real token balances, so that the balances of the pool are never lower than the virtual balance. (`balance = real balance + virtual balance`) Therefore, even if the real balance of a token goes to zero, the virtual balance will keep the token balance (and therefore price) above zero. In practice, it restricts the token price between two bounds, defined by the virtual balances of each token. The image below illustrates a constant product price curve with hard price limits, "soft" margins (described later - basically the threshold where the pool will begin self-adjusting), and the "target" price, usually close to the middle of the range. ![AutoRange price curve illustration](/images/reclamm-initial-state.png) #### Initial Virtual Balances During pool creation, the pool creator will define the `minimum price` ($P\_{a\_{min}}$) and the `maximum price` ($P\_{a\_{max}}$). Given these three parameters, we can calculate the initial virtual balances. The invariant of an AutoRange Pool is calculated as follows: $L = (R\_a + V\_a)(R\_b + V\_b)$ Where $R\_a$ and $R\_b$ are the real balances, $V\_a$ and $V\_b$ are the virtual balances, and $L$ is the invariant. The current price of the pool is given by: $P\_a = \frac{R\_b + V\_b}{R\_a + V\_a}$ Note that we have (arbitrarily) defined it as B/A (i.e., prices represent the value of token A denominated in token B. In other words, how many B tokens equal the value of one A token.) Given the price formula, we know that the minimum price happens when the denominator takes on its maximum value: $R\_a = R\_{a\_{max}}$. In this state, $R\_b = 0$. (At the edges, one of the balances will be maximum, and the other balance will be 0). Before the initialization of the pool the "maximum" balance is not known - so, to calculate the initial balances of the pool, we set the maximum balance of token A to an arbitrary value, and then we can scale accordingly when the initialization amounts are known: $P\_{a\_{min}} = \frac{V\_b}{R\_{a\_{max}} + V\_a}, P\_{a\_{max}} = \frac{R\_{b\_{max}} + V\_b}{V\_a}$ Since the invariant is constant, we can deduce that the invariant formulas in these two edge points are, respectively: $L = (R\_{a\_{max}} + V\_a)(V\_b)$ $L = (V\_a)(R\_{b\_{max}} + V\_b)$ One last concept important to calculate the initial virtual balances is the price ratio, defined in the next section. Since $Q\_0^2 = \frac{P\_{a\_{max}}}{P\_{a\_{min}}}$, substituting the max and min price formulas, we have: $Q\_0^2 = \frac{(R\_{a\_{max}} + V\_a)(R\_{b\_{max}} + V\_b)}{V\_aV\_b}$ Using the two invariant formulas for the edges, we have $Q\_0^2 = \frac{\frac{L}{V\_b}\frac{L}{Va}}{V\_aV\_b} = \frac{L^2}{V\_a^2V\_b^2}$ Now we have a way to calculate the invariant based on the virtual balances. Since $Q\_0^2 = \frac{P\_{a\_{max}}}{P\_{a\_{min}}}$, $L = V\_aV\_b\sqrt{\frac{P\_{a\_{max}}}{P\_{a\_{min}}}}$ Now we need a way to use these prices to calculate $V\_a$ and $V\_b$. Since the virtual balances are proportional to the real balances, we will randomly choose a $R\_{a\_{max}} = R\_{a\_{max}}'$, which will allow us to calculate $V\_a'$ and $V\_b'$. When the pool is initialized we can find $V\_a$ and $V\_b$ by multiplying the $V\_a'$ and $V\_b'$ by the rate $\frac{R\_{a\_{max}}}{R\_{a\_{max}}'}$, where $R\_{a\_{max}}$ is calculated based on the initialization balances. So, using the invariant $L = (R\_{a\_{max}}' + V\_a')(V\_b')$, we have that $(R\_{a\_{max}}' + V\_a')(V\_b') = V\_a'V\_b'\sqrt{\frac{P\_{a\_{max}}}{P\_{a\_{min}}}}$ $V\_b'$ cancels out, so we can isolate $V\_a'$: $V\_a' = \frac{R\_{a\_{max}}'}{\sqrt{\frac{P\_{a\_{max}}}{P\_{a\_{min}}}} - 1}$ Using the $P\_{a\_{min}}$ formula, we have: $V\_b' = (R\_{a\_{max}}' + V\_a')(P\_{a\_{min}})$ #### Initial Real Balances Now we need to find the balances $R\_a'$ and $R\_b'$ corresponding to the desired `target price` ($P\_{a\_{target}}$), which was also given by the user when the pool was created. These values will be used to inform the initializer of the pool of the correct token proportion required to initialize the pool at the given target price. To calculate it, let's use $P\_{a\_{target}} = \frac{R\_b' + V\_b'}{R\_a' + V\_a'}$ , $L = (R\_a' + V\_a')(R\_b' + V\_b')$ and $Q\_0 = \frac{L}{V\_a'V\_b'}$. Using the invariant formula in $P\_{a\_{target}}$, we have that $P\_{a\_{target}} = \frac{(R\_b' + V\_b')^2}{L}$. Since $L = Q\_0V\_a'V\_b'$, $P\_{a\_{target}} = \frac{(R\_b' + V\_b')^2}{Q\_0V\_a'V\_b'}$, which leads to $R\_b' = \sqrt{P\_{a\_{target}}Q\_0V\_a'V\_b'} - V\_b'$ Then, isolating $R\_a'$ in the $P\_{a\_{target}}$ formula, we have $R\_a' = \frac{R\_b' + V\_b' - V\_a'P\_{target}}{P\_{target}}$ #### Balance Ratio As noted above, the Balance Ratio is the proportion of token B in relation to token A that must be used to initialize the pool, so that the target price is respected. $balanceRatio = \frac{R\_b'}{R\_a'}$ #### Initialization Finally, when initializing, we need to scale $V\_a'$ and $V\_b'$ to the initial balances of the pool. The scale is $\frac{R\_a}{R\_a'}$ (which is the same rate as $\frac{R\_{a\_{max}}}{R\_{a\_{max}}'}$). So $V\_a = \frac{R\_aV\_a'}{R\_a'}, V\_b = \frac{R\_aV\_b'}{R\_a'}$ ### Price Ratio ($Q\_0^2$) The Price Ratio is the ratio between the high and low price bounds of the range. The current price of token A is defined as $P\_A = \frac{R\_b + V\_b}{R\_a + V\_a}$. The highest price of A ($P\_{max\_A}$) is reached when the real balance of A is 0. So, the price is defined as: ![AutoRange price equation](/images/reclamm-price-equation.png) The Price Ratio is calculated as $Q\_0^2 = \frac{P\_{max\_A}}{P\_{min\_A}}$. For example, let's say the pool has virtual balances of 1000 of each token. Also, let's assume that when the real balance of token A is 0, token B is 1000 ($R\_{max\_{B}}$), and vice versa. In the example, $P\_{max\_{A}} = \frac{1000 + 1000}{1000} = 2$ and $P\_{min\_{A}} = \frac{1000}{1000 + 1000} = 0.5$, so $Q\_0^2 = \frac{2}{0.5} = 4$. #### Price Ratio Update The Price Ratio can be updated by a pool manager. This update is gradual, analogous to updating the amplification factor of a Stable Pool. The pool manager defines a start and end time and a target $Q\_{0\_{target}}$, and then $Q\_{0\_{new}}$ is calculated as an interpolation between the initial and final values, $Q\_{0\_{initial}}$ and $Q\_{0\_{target}}$. $\sqrt{Q\_{0\_{new}}} = \sqrt{Q\_{0\_{initial}}}(\frac{\sqrt{Q\_{0\_{target}}}}{\sqrt{Q\_{0\_{initial}}}})^{\frac{blockTimestamp - startQ0Time}{endQ0Time - startQ0Time}}$ ### Pool Centeredness As noted above, the main idea of this pool is to manage the price interval such that the market price stays within it as much as possible. The pool won't use an external oracle, so we need a mechanism to determine whether the market price is within the current price interval: `Pool Centeredness`. `Pool Centeredness` is a number (a percentage) that describes how far the pool balances are from the center of the current interval. The number goes from 0 to 1, where 0 means that the balances are at the edge and 1 means that the balances are exactly in the middle of the current price range, which is the case when the pool is initialized. The number is calculated as follows: 1. First, if $R\_a$ or $R\_b$ are 0, we know that pool centeredness is 0. 2. If not, we calculate $\frac{R\_a}{R\_b}$ and compare with $\frac{V\_a}{V\_b}$. Since the virtual balances were calculated based on the real balances, these rates are the same near the center of the interval. 1. If $\frac{R\_a}{R\_b} < \frac{V\_a}{V\_b}$, $centeredness = \frac{\frac{R\_a}{R\_b}}{\frac{V\_a}{V\_b}}=\frac{R\_a V\_b}{R\_b V\_a}$ 2. Otherwise, $centeredness = \frac{\frac{R\_b}{R\_a}}{\frac{V\_b}{V\_a}}=\frac{R\_b V\_a}{R\_a V\_b}$ ### Pool Centeredness Margin We could wait for the centeredness to be zero before moving the price interval, but that means we would wait for one of the real balances to be zero, which in practice means no exposure to one of the assets of the pool, and lost trade opportunities. The balance doesn't have to literally go to zero for the pool to get "stuck"; there could also be a dust balance worth less than the gas required to arb it. To avoid this situation, we introduce the concept of Centeredness Margin (see the image above used to illustrate the concept of virtual balances). Margin is a number (also a percentage) from 0 to 1, very similar to pool centeredness, and helps calculate whether the pool is `IN RANGE` (Pool Centeredness > Margin) or `OUT OF RANGE` (Pool Centeredness ≤ Margin). If the pool is `OUT OF RANGE` , the price interval will be recalculated (which, in practice, means that we will recalculate the virtual balances). ### Daily Price Shift Exponent The `Daily Price Shift Exponent` is a percentage that defines the speed at which the virtual balances will change per day. A value of 100% (i.e, FP 1) means that the min and max prices will double (or halve) every day, until the pool price is within the range defined by the margin. We use the following formula to calculate the current virtual balance of a token: $V\_{next} = V\_{current}(1 - \tau)^{n + 1}$ where $\tau$ is a time constant, defined as $\tau = \frac{PriceShiftDailyRate}x$. If we want $V\_{next} = 2V\_{current}$ in the following day, `DailyPriceShiftExponent = 100%`, so we can easily find $x$ as 124649.35015039. Therefore, $\tau = \frac{PriceShiftDailyRate}{124649.35015039}$. Using $\tau$ and the seconds passed since the last swap ($n$) we can calculate the target virtual balances: 1. If the current price is closer to $P\_{min\_A}$ ($\frac{R\_a}{R\_b} >= \frac{V\_a}{V\_b}$): $V\_{b\_{next}} = V\_{b\_{current}}(1 - \tau)^{n + 1}$ $V\_{a\_{next}} = R\_a \frac{V\_{b\_{next}} + R\_b}{V\_{b\_{next}}(Q\_0-1) - R\_b}$ 2. In the current price is closer to $P\_{max\_A}$ ($\frac{R\_a}{R\_b} < \frac{V\_a}{V\_b}$): $V\_{a\_{next}} = V\_{a\_{current}}(1 - \tau)^{n + 1}$ $V\_{b\_{next}} = R\_b \frac{V\_{a\_{next}} + R\_a}{V\_{a\_{next}}(Q\_0-1) - R\_a}$ In the code, we store $1 - \tau$ for convenient, and refer to it as the `dailyPriceShiftBase`, as it is the base of the exponential function used to update the virtual balances. If there are no swaps that bring the pool back into range, we need an additional guardrail to prevent the range from shifting “past” the center point. We do this by imposing a minimum value on the “overvalued” token (i.e., the one with the lower balance: `o` = `b` in case 1 above, and `a` in case 2), given by the virtual balance when centeredness = 1: ``` $V_{o_{min}} = \frac{R_{o}}{\sqrt Q_0-1)}$ ``` ### Price Interval Update Given the new price ratio ($Q\_{0\_{new}}$), we can calculate the new virtual balances. There are several ways to do this, and we decided to update it keeping the pool centeredness constant. That's because, if pool centeredness is not constant, an update in the price ratio can take the pool from an `IN RANGE`state to an `OUT OF RANGE` state without a user action, so it can introduce inconsistencies when moving the price interval to follow the market price. To keep the pool centeredness constant, we need to calculate the new virtual balances based on the following (we are using the centeredness if $\frac{R\_a}{R\_b} < \frac{V\_a}{V\_b}$, but a similar derivation applies to the other case): $centeredness = \frac{R\_aV\_b}{R\_bV\_a}$ $Q\_0 = \frac{P\_{a\_{max}}}{P\_{a\_{min}}} = \frac{L}{V\_aV\_b} = \frac{(R\_a + V\_a)(R\_b + V\_b)}{V\_aV\_b}$ Since we have $R\_a$, $R\_b$, $Q\_{0\_{new}}$ and $centeredness$, we can calculate $V\_a$ and $V\_b$. 1. Isolate $V\_a$ in the $Q\_0$ formula: $V\_a = \frac{R\_a(R\_b + V\_b)}{V\_bQ\_{0\_{new}} - R\_b - V\_b}$ 2. Isolate $V\_b$ in the centeredness formula: $V\_b = \frac{R\_bV\_acenteredness}{R\_a}$ (if $\frac{R\_a}{R\_b} > \frac{V\_a}{V\_b}$, `centeredness` goes in the denominator) 3. Replace $V\_a$ formula in the $V\_b$ formula, and the result will be: $V\_b^2 (Q\_{0\_{new}} - 1) - V\_b (R\_b(1 + centeredness)) - R\_b^2centeredness = 0$ 4. Resolve the formula above with Bhaskara to find the value of $V\_b$, then replace the value of $V\_b$ in the $V\_a$ formula. ### Calculation of the Virtual Balances #### When initializing the pool During pool creation, pass minimum and maximum prices of A ($P\_{a\_{min}}$ and $P\_{a\_{max}}$), target price $P\_{target}$. The contract also needs to assume an arbitrary $R\_{a\_{max}}'$, which we suggest should be `1000 * FixedPoint.ONE`. This allows the pool, during creation, to calculate the virtual balances as: $V\_{a}' = \frac{R\_{a\_{max}}'}{Q\_0 - 1}$ $V\_b' = P\_{a\_{min}}(R\_{a\_{max}}' + V\_a')$ Note, in the equation above, that $Q\_0 = \sqrt\frac{P\_{a\_{max}}}{P\_{a\_{min}}}$. Also, note that $V\_a'$ and $V\_b'$ are scaled according to $R\_{a\_{max}}'$. During the pool initialization, these parameters will be scaled according to the real balances. Calculate the theoretical balances $R\_a'$ and $R\_b'$ for the default $R\_{a\_{max}}'$. $R\_b' = \sqrt{P\_{target}V\_b'V\_a'Q\_0} - V\_b'$ $R\_a' = \frac{R\_b' + V\_b' - V\_a'P\_{target}}{P\_{target}}$ Calculate the pool centeredness using the parameters above, and check if the pool centeredness is above the margin. If not, reverts. #### Balance Ratio Getter Between pool creation and initialization, the user must be able to calculate the correct token proportions (i.e., the Balance Ratio) needed to ensure the target price is respected. This balance ratio is given by: $\frac{R\_b'}{R\_a'}$. #### Pool initialization To initialize the pool, we receive the real balances $R\_a$ and $R\_b$, and need to validate that $\frac{R\_b}{R\_a}==\frac{R\_b'}{R\_a'}$, with some margin of error (0.01%). If this is false, revert. Otherwise, calculate the scale of $V\_a$ and $V\_b$, which is $scale = \frac{R\_a}{R\_a'}$. Therefore, $V\_a = scaleV\_a'$ and $V\_b = scaleV\_b'$. Finally, ensure the price is close enough to the target price, and the centeredness is above margin. #### On Swap When executing onSwap, three steps must be performed: 1. Calculate $Q\_{0\_{current}}$. If `blockTimestamp > endQ0Time`, return $Q\_{0\_{target}}$. Else, calculate $Q\_{0\_{current}}$ based on the formula: $\sqrt{Q\_{0\_{current}}} = \sqrt{Q\_{0\_{initial}}}(\frac{\sqrt{Q\_{0\_{target}}}}{\sqrt{Q\_{0\_{initial}}}})^{\frac{blockTimestamp - startQ0Time}{endQ0Time - startQ0Time}}$ 2. Update the virtual balances as follows, keeping the pool centeredness constant. This won't move the pool `OUT OF RANGE` if there's no swap, which makes off-chain calculations more reliable and optimizes the price interval calculation when the pool is `OUT OF RANGE`. a. Calculate the `centerednessFactor` ($C\_f$) using the method described in the [Pool Centeredness](#pool-centeredness) section above. b. Calculate $V\_b$. It's a Bhaskara formula (notice that there's no minus sign, to avoid issues with unsigned math): \- $a = Q\_{0\_{current}} - 1$ \- $b = R\_b(1 + C\_f)$ \- $c = R\_b^2C\_f$ \- $V\_{b\_{new}} = \frac{b + \sqrt{b^2 + 4ac}}{2a}$ c. Calculate $V\_a=\frac{R\_aV\_{b\_{new}}}{R\_b \* C\_f}$. Notice that $C = \frac{R\_aV\_{b\_{old}}}{R\_bV\_{a\_{old}}}$, so the $V\_{a\_{new}}$ equation can be simplified to $V\_{a\_{new}} = \frac{V\_{a\_{old}}V\_{b\_{new}}}{V\_{b\_{old}}}$. This simplification is useful, since it allows $V\_{a\_{new}}$ to be calculated with $R\_a = 0$ or $R\_b = 0$. 3. Check whether the pool is `OUT OF RANGE` If so, update the virtual balances using the formulas from the [Daily Price Shift Exponent](#daily-price-shift-exponent) section above. If the virtual balances were updated due to Q0 updating, use the new virtual balances. **IMPORTANT:** Notice that Virtual Balances are not recalculated on every swap. This is because rounding issues in the calculation of virtual balances may lead to inconsistencies with the pool invariant and return more tokens to the user, potentially draining the pool. Since we do not update the virtual balances on swaps, fee collection causes the invariant to grow slowly as the virtual balances remain constant. This slowly increases the price ratio, which has the effect of de-concentrating liquidity. Note that under these conditions, the price ratio will slowly diverge from what was set by the admin. If this is undesirable, the admin can always reset it to the original value, and it will slowly reconcentrate liquidity to the original range. #### On Add/Remove Liquidity When adding or removing liquidity (which can only be done proportionally), we need to increase the virtual balances in the same proportion that we increased the real balances. That will ensure that the token prices don't change. In the `onBefore[Add|Remove]Liquidity` hooks, calculate the proportion as follows: $proportion = \frac{bpt\[Out|In]}{totalSupply}$ Use `bptOut` when adding liquidity, and `bptIn` when removing it. Finally, scale virtual balances A and B by this proportion ($V\_{new} = V\_{old} \* (1 ± proportion)$) ## Calculation of Swap Result In the equations below, we will replace `Token A` and `Token B` by `Token In` and `Token Out` . So, the invariant can be described as $$ L = (R\_i + V\_i)(R\_o + V\_o) $$ When a swap occurs, the invariant $L$ is constant, so $$ L= (R\_i + V\_i + amountIn)(R\_o + V\_o - amountOut) $$ Isolating amountIn and amountOut in the equation above is a prerequisite for calculating the swap result. Notice that all swaps are calculated after the virtual balances are updated (when the price ratio is changing or the pool is out of range). ### Exact In $$ (R\_i + V\_i)(R\_o + V\_o) = (R\_i + V\_i + amountIn)(R\_o + V\_o - amountOut) $$ So, isolating `amountOut` we have: $$ amountOut = (R\_o + V\_o) - \frac{(R\_i + V\_i)(R\_o + V\_o)}{R\_i + V\_i + amountIn} $$ We can use the same denominator on the right: $$ amountOut = \frac{(R\_o + V\_o)(R\_i + V\_i + amountIn) - (R\_i + V\_i)(R\_o + V\_o)}{R\_i + V\_i + amountIn} $$ Now, if we expand the multiplications, we have: $$ amountOut = \frac{R\_oR\_i + R\_oV\_i + R\_oamountIn + V\_oR\_i + V\_oV\_i + V\_oamountIn - R\_oR\_i - R\_oV\_i - V\_oR\_i - V\_oV\_i}{R\_i + V\_i + amountIn} $$ All terms except those involving amountIn cancel out, so the final equation is: $$ amountOut = \frac{(R\_o + V\_o)amountIn}{R\_i + V\_i + amountIn} $$ ### Exact Out $$ (R\_i + V\_i)(R\_o + V\_o) = (R\_i + V\_i + amountIn)(R\_o + V\_o - amountOut) $$ So, isolating `amountIn` we have: $$ amountIn = \frac{(R\_i + V\_i)(R\_o + V\_o)}{R\_o + V\_o - amountOut} - (R\_i + V\_i) $$ We can use the same denominator on the right: $$ amountIn = \frac{(R\_i + V\_i)(R\_o + V\_o) - (R\_i + V\_i)(R\_o + V\_o - amountOut)}{R\_o + V\_o - amountOut} $$ Now, if we expand the multiplications, we have: $$ amountIn = \frac{R\_oR\_i + R\_oV\_i + V\_oR\_i + V\_oV\_i - R\_oR\_i - R\_oV\_i + R\_iamountOut - V\_oR\_i - V\_oV\_i + V\_iamountOut}{R\_o + V\_o - amountOut} $$ All terms except those involving amountOut cancel out, so the final equation is: $$ amountIn = \frac{(R\_i + V\_i)amountOut}{R\_o + V\_o - amountOut} $$ ## AutoRange Pools ### Overview AutoRange Pools are **fungible concentrated liquidity pools** that focus liquidity within a predefined price range, allowing LPs to earn greater fees with less capital—especially when the market price remains within that range. This concentration is initialized with a **target price** and **range bounds**, enabling the pool to deliver capital efficiency over traditional constant-product models. What sets AutoRange Pools apart is their **adaptive nature**. As trading activity or liquidity operations shift the market price, AutoRange Pools are able to **automatically move** their price range—up or down the price curve—without any intervention from users or governance. This "re-centering" behavior activates only when the pool becomes sufficiently unbalanced, based on a margin threshold defined at deployment. Once triggered, the pool begins gradually shifting its range in the direction of market pressure, ensuring liquidity stays useful and active. To enable this adaptive behavior, AutoRange Pools are configured with a few key parameters: * A **target price**, often set near the current market price at deployment. * A **price range**, where all the pool’s liquidity is initially concentrated. * A **margin**, expressed as a percentage, defining how much imbalance is tolerated before the range begins shifting. * A **daily shift exponent**, which controls how quickly the pool adjusts its range when out of balance. For example, if the margin is set at 20%, the pool tolerates up to a 60/40 imbalance (±10% deviation from a 50/50 balance). Once this threshold is crossed, the pool begins migrating its range incrementally over time, following the market. This design offers LPs the benefit of concentrated liquidity **without the need for manual range resets**. It suits passive LPs seeking to stay aligned with market trends while still capturing the fee benefits of a tighter range. Those familiar with other concentrated liquidity designs may notice echoes of similar mechanisms—like pools that start with fixed ranges around a target price—but unlike those, an AutoRange Pool's range is not locked. Instead, it evolves in response to the market, maintaining efficiency over time without additional user action. The following diagram shows a pool that is `OUT OF RANGE`. The current price is within the price range (as it must be), but above the margin. In this state, the pool will be shifting the price range "up" toward higher prices, following the market, and attempting to bring the market price back inside the margins. ![AutoRange pool out of range](/images/PoolRange.png) \::: info AutoRange Pools are always two-token pools. * The minimum swap fee percentage is 0.001% (note - same as the Weighted Pool) * The maximum swap fee is 10% * The invariant bounds are unused in this pool, as liquidity can only be added or removed proportionally * The initialization and other parameters are described in detail below \::: Note that the swap fee and invariant limits are defined in `ReClammPool` through implementing the `ISwapFeePercentageBounds` and `IUnbalancedLiquidityInvariantRatioBounds` interfaces, which are included in `IBasePool`. See [here](../../../integration-guides/aggregators/pool-maths-and-details.md) for a more detailed reference. ### Advantages of AutoRange Pools * All the benefits of concentrated liquidity: higher fees and better capital efficiency (when in range, same math as UniV3) * None of the maintenance required with traditional concentrated liquidity pools: LP-and-forget * Moreover, unlike with third-party ALMs, the price range adjustment is entirely transparent * Fungible positions can be incentivized, making them ideal for guaranteeing deep DAO token liquidity * Calculations simplified by having all LPs share the same price range * AutoRange Pools automatically adjust to market conditions; LPs should always be earning fees * While designed to be maintenance-free, AutoRange Pools are tunable by admins if necessary in extreme conditions These are designed for maintaining deep liquidity, and should not be used for token launches, or with tokens that have low liquidity or otherwise manipulable prices (e.g., using direct collateral or relying on non-aggregated on-chain oracles). ### Price range mechanism One fundamental thing to understand is how the price range is defined and enforced in the first place, given that the price curve is essentially "weighted math" (constant product), and the price is normally determined by the token balances, which are unconstrained (beyond needing to be greater than 0). The answer is the introduction of "offsets" to the real balances called "virtual" balances, such that the token balances used to calculate the invariant are redefined as the sum of the real and virtual balances. These virtual balances fix the price curve on both ends, cutting off the long tail and ensuring non-zero minimum and maximum prices, even as the real balances approach zero. (This mechanism is shared by Gyro pools, but the terminology here is very slightly different.) Higher virtual balances (relative to the real balances) means higher concentration. ### Initialization What we really need internally is the "ratio" of the minimum and maximum prices, but since calculating this is unintuitive and a lot to ask of integrators, we created an initialization mechanism that takes very simple input: the actual minimum and maximum prices, and a target price. Two additional parameters are required to specify the behavior of the pool after initialization (margin and price shift exponent); those are described below. One question that arises immediately is: how do we define the price? There are after all *two* ways to define the price of a 2-token pool: A in terms of B, or B in terms of A. We have chosen to define the prices as B/A; i.e., prices represent the value of token A denominated in token B. In other words, how many B tokens equal the value of one A token. Since Balancer pool tokens must be registered in numerical order, the "direction" of the ratio is deterministic. When the pool is created from the factory, these initial values are stored immutably; they are only used during initialization. The initialization process essentially derives the "internal" parameters from the initial user input, and given the real token balances being supplied, checks that the resulting prices match their intended values: otherwise the pool would be vulnerable to losses through arbitrage. See the link below for the detailed math, but we start with the definition of the invariant: $L = (R\_a + V\_a)(R\_b + V\_b)$ And the price, recalling that it is defined as B/A: $P\_a = \frac{R\_b + V\_b}{R\_a + V\_a}$ We know the desired price *ratio* (max/min), and we see that these values can be derived by setting each real balance to 0. At the edges, one of the balances will be the maximum value, and the other will be 0. The "maximum balance" is really a placeholder needed for intermediate calculations (it will cancel out later), so we can assign it a convenient arbitrary value (e.g., 1000). Given the invariant and these price bounds, we can derive initial values for the virtual balances. Recall that the total balances (which determine the actual price) are defined as the sum of the real and virtual balances. We know the virtual balances and the target price, so it is now possible to calculate the "theoretical" real balances that would result in the target price. Given this information, we can calculate the "centeredness" of the pool (defined below). This is a measure of balance. A value of 1 means the pool is perfectly balanced, and 0 means it is at one of the edges of the range. We expect that the target price will be roughly in the middle of the target range, so that the initial centeredness is in the neighborhood of 1: but it does not need to be exact. What it does need to be is above the margin: otherwise, the pool would be out of range immediately. If this happens (i.e., the target price is too close to one of the edges), initialization will fail. The next step is to "scale" the virtual balances. This is basically the inverse of the operation above that calculated the theoretical virtual balances from arbitrary real balances. Now that we know the real token balances the initializer intends to deposit, we can use the ratios determined above to calculate the actual initial virtual balances. Finally, we validate that the ratio of the real balances corresponds to the theoretical ratio arising from the initial inputs, and that the actual price after initialization closely matches the initial target price. These values might not match exactly, due to rounding or precision errors, so there is a built-in tolerance of 0.01%. If any of these validations fail, initialization reverts, insuring the user against configuration errors. We provide a helper function, `computeInitialBalancesRaw`, to assist with these calculations. Given the actual intended deposit amount of one of the tokens - and the initial parameters set on deployment - the contract can calculate how much of the other token must be supplied to pass all the initialization checks. One final twist involves the handling of wrapped tokens with rate providers, which are expected to be commonly used in AutoRange Pools. Recall that the prices are passed in during initialization - but how were they calculated? It's possible the pool creator wants to use the direct price of the wrapped token (e.g., for non-boosted pools with tokens like wstETH). In this case, the price does not include the rate provider, even though the token has one. In other cases (e.g., boosted pools with tokens like waUSDC), the creator might want to use the price of the underlying token instead. In that case, the price does incorporate the rate, and the initialization calculation must accommodate that. Accordingly, along with the price range and target values, the pool is deployed with flags indicating whether to use the rate provider for each token during initialization. ### Centeredness Margin The centeredness margin is another parameter that must be set on deployment. Unlike the initial target and range, it is not immutable, and can be changed later by admin action. This is a percentage value in the range of 0 - 90%. A value of 0 would mean there is effectively no margin - real balances can go to 0, and the pool will never readjust. This degenerate case is effectively the same as a Gyro 2-CLP at the full price range: completely insensitive to price movement, until the pool goes out of range and effectively halts. (Technically, AutoRange Pools act like 2-CLPs constructed with the current range whenever they're in range and not updating the price ratio.) A value of 100% would mean the pool is always "out of range," unless it is *perfectly* balanced. This is maximal sensitivity to price changes; essentially it would always be shifting the range (and incurring somewhat higher gas costs). Since the margin can only be changed when the pool is in range both before and after, it would be very difficult to lower it from 100%. Mainly for this reason, the maximum was set to 90%. We expect most pools to be configured somewhere in the middle. ![Centeredness margin illustration](/images/centeredness.gif) Note that the centeredness measure is symmetric around the center point. On initialization, the pool centeredness should be very close to 1. As swaps move the real balances (with constant virtual balances), the centeredness will move up or down the price curve *away* from 1 and toward the margins (for these examples, we are using margins from 0 to 50%). When the centeredness falls below 50%, the market price point will be above the upper or below the lower price margin on the curve, heading toward one of the edges of the price range (where one of the real token balances would be 0). ![Centeredness illustration](/images/centeredness-56.png) ### Daily price shift exponent The daily price shift exponent is the final parameter (specific to AutoRange Pools) that must be set on deployment. Unlike the initial target and range, it is not immutable, and can be changed later by admin action. This is also a percentage, and it controls the "doubling rate" of the price shift. At 100%, the prices will double (or halve) in one day. This rate is non-linear, and means that the prices will be multiplied (or divided) by 2^(`dailyPriceShiftExponent`) per day. So 200% corresponds to 2^2 or 4x, and 300% corresponds to 2^3 or 8x. (The maximum is 100%, or doubling once per day.) Note that the math prevents the price from "overshooting" in either direction due to inactivity (i.e., shifting past the center point, where centeredness equals 1, if there is an extended period with no swaps). ### Admin actions AutoRange Pool admins can do three things: 1) change the centeredness margin (the threshold for updates); 2) change the daily price shift exponent (the speed of updates); and 3) initiate an update to the price interval (i.e., the distance, or ratio, between the minimum and maximum price bounds), or simply stop an ongoing update. All of these changes will update the virtual balances (and potentially slightly change the price). All of these functions require the pool to be initialized. To prevent manipulation, changing the margin also requires the Vault to be locked (i.e., not in the middle of a transaction, which could transiently set balances to arbitrary values), and the pool to be "in range" both before and after. It is not possible to "move the goal posts" by admin action in such a way as to make the pool start or stop an update. Similarly, the daily price shift exponent can only be changed when the Vault is locked. As it is only altering the speed of the update, it does not check for centeredness. As described above, the price shift exponent is capped at 100% (corresponding to doubling or halving once a day). Admins can also change the price ratio, supplying the new ratio and a start and end time. There is a minimum duration for the update (1 day), and a minimum amount of ratio change: 1e6 wei. (This is loosely analogous to Uniswap's "tick" resolution limit, introduced for similar reasons.) These are "best effort" checks to keep the pool well-behaved, but are not hard guarantees. There is also a way to simply stop an ongoing update, which will fix the price ratio at its current value. Note that it is not necessary to stop an ongoing update before starting a new one. Starting an update while one is ongoing is equivalent to stopping and immediately restarting with the new parameters. Note that it is possible for the price range to be both shifting up or down and expanding or contracting at the same time. Gas costs will be higher during these operations, compared to "in range" swaps with no ongoing price ratio update. ### Simulator A simulator is deployed [here](https://aclamm.web.app/reclamm). You can set the initial parameters manually - or load them from a real deployed AutoRange Pool, then change the settings to see how a real pool would respond (including simulating swaps). See [this page](./reclamm-pool-math.md) for details of the math. ## Stable Math ### Overview Stable Math is designed to allow for swaps between any assets that have the same price, or are "pegged" to the same asset. The most common examples are stablecoins that track US Dollars (DAI, USDT, USDC), and assets that track the price of Bitcoin (WBTC, renBTC, sBTC). Prices are determined by the pool balances, the *amplification parameter*, and amounts of the tokens that are being swapped. ### Implementations #### TypeScript Developers can use the TypeScript math implementations used by the Smart Order router (equivalent v2 reference). * [stableMath.ts](https://github.com/balancer/balancer-sor/blob/john/v2-package-linear/src/pools/stablePool/stableMath.ts) * [metaStableMath.ts](https://github.com/balancer/balancer-sor/blob/john/v2-package-linear/src/pools/metaStablePool/metaStableMath.ts) ### Invariant Since the Stable Math equation is quite complex, determining the invariant, $D$, is typically done iteratively. For an example of how to do this, please refer to [this function](https://github.com/georgeroman/balancer-v2-pools/blob/main/src/pools/stable/math.ts#L16). $$ A \cdot n^n \cdot \sum{x\_i} +D = A \cdot D \cdot n^n + { \frac{D^{n+1}}{{n}^{n}\cdot \prod{x\_i} } } $$ Where: * $n$ is the number of tokens * $x\_i$ is balance of token $i$ * $A$ is the amplification parameter ### Swap Equations Similar to determining the invariant, determining (out/in) amount given (in/out) amounts is also done iteratively. Both [outGivenIn](https://github.com/georgeroman/balancer-v2-pools/blob/db415173277bfa86d9aa6b0c1fbd15481c7a2398/src/pools/stable/math.ts#L88) and [inGivenOut](https://github.com/georgeroman/balancer-v2-pools/blob/db415173277bfa86d9aa6b0c1fbd15481c7a2398/src/pools/stable/math.ts#L138) use the same function, [getTokenBalanceGivenInvariantAndAllOtherBalances](https://github.com/georgeroman/balancer-v2-pools/blob/db415173277bfa86d9aa6b0c1fbd15481c7a2398/src/pools/stable/math.ts#L502). Note that these are v2 references; we don't use TS in this way in v3, and `computeBalance` is equivalent to `getTokenBalanceGivenInvariantAndAllOtherBalances` in v2. Otherwise, they are mathematically equivalent. #### outGivenIn $$ y^2 + (\frac{D}{An^n} + \sum\_{j \neq out}{x'*j} - D)y -\frac{D^{n+1}}{An^{2n} \prod*{j \neq out}{x'\_j}}= 0 $$ $$ a\_{out} = x\_{out} - x'*{out} = x*{out} - y $$ Where: * $x'\_i$ is the **ending** amount of each token * $a\_{out}$is the amount out * $x\_{out}$is the **starting** amount of the output token * $y = x'\_{out}$is the **ending** amount of the output token * $D$ is the pool invariant * $A$ is the amplification parameter * $n$ is the number of tokens #### inGivenOut $$ y^2 + (\frac{D}{An^n} + \sum\_{j \neq in}{x'*j} - D)y -\frac{D^{n+1}}{An^{2n} \prod*{j \neq in}{x'\_j}}= 0 $$ $$ a\_{in} = x'*{in} - x*{in} = y-x\_{in} $$ Where: * $x'\_i$ is the **ending** amount of each token * $a\_{in}$is the amount in * $x\_{in}$is the **starting** amount of the input token * $y = x'\_{in}$is the **ending** amount of the input token * $D$ is the pool invariant * $A$ is the amplification parameter * $n$ is the number of tokens ## Stable Pools ### Overview Stable Pools are designed for assets that are either expected to consistently swap at near parity, or at a known exchange rate. Stable Pools use [Stable Math](./stable-math.md) (based on StableSwap, popularized by Curve) which allows for swaps of significant size before encountering substantial price impact, vastly increasing capital efficiency for like-kind and correlated-kind swaps. \::: info Balancer v3 pools are limited at the Vault level to 8 tokens. Stable Pools have a safe maximum of 5 tokens, due to the constraints of Stable Math (same as in v2). Standard Stable Pools support 5 tokens. \::: #### Ideal For * **Pegged Tokens** - tokens that swap near 1:1, such as two stablecoins of the same currency (eg: DAI, USDC, USDT), or synthetic assets (eg: renBTC, sBTC, WBTC) * **Correlated Tokens** - tokens that swap near 1:$R$ with some slowly changing exchange rate $R$, like derivatives (eg: wstETH, wETH) #### Stable Swaps Under the Balancer Umbrella One of the key advantages to having Stable Pools on Balancer specifically is that they are plugged into the same protocol as all other pools. Swapping between stablecoins is frequently used for arbitrage when one token is paired with two different stablecoins in different pools. By leveraging Batch Swaps on Balancer, these swaps can be combined into a single, gas-efficient transaction. ##### Example With `StablePool[DAI, USDC, USDT]`, we can directly pair the LP token, or BPT, against WETH in a `WeightedPool[WETH, SP-BPT]`. This nesting allows us to consolidate liquidity into some of the most common groupings, which results in deeper liquidity and better prices throughout Balancer. In this example, it also saves you the trouble of making 3 WeightedPools `[WETH, DAI]`, `[WETH, USDC]`, `[WETH, USDT]`. ### Use Cases #### **The Lido wstETH/WETH Liquidity Pool** [Lido](https://lido.fi/) is a liquid staking solution for ETH 2.0 backed by industry-leading staking providers. Lido lets users stake their ETH - without locking assets or maintaining their own infrastructure. The goal is to solve problems associated with initial ETH 2.0 staking: illiquidity, immovability and accessibility by making staked ETH liquid and allowing for participation with any amount of ETH to improve the security of the Ethereum network. stETH is a token that represents **Staked Ether**, combining the value of deposited ETH with staking returns. As an ERC20, stETH tokens can be swapped as one would swap WETH, allowing the benefits of ETH 2.0 staking while allowing users to continue using their staked Ether on decentralized finance products. Balancer Stable Pools are ideal for the wstETH-WETH pair as the stETH asset is highly correlated but not pegged 1:1 to ETH as it accrues staking returns. ## StableSurge Pools ### Overview StableSurge Pools are a type of Stable Pool designed for assets that usually trade at nearly the same value or have a predictable exchange rate. What makes StableSurge Pools different is that they use a special feature—called a hook—to automatically adjust the swap fee based on how balanced the pool is during a trade. Here’s how it works: Normally, swaps pay a base fee known as the "static swap fee," such as 1%. But if the pool becomes unbalanced—meaning one asset makes up more than a configurable "threshold" percentage of the pool value—the fee starts increasing, or "surging." This increase happens gradually (linearly) up to a maximum fee (like 0.50%) as the imbalance grows. For example, consider a two-token pool with 1000 of each token. A 300 token trade would result in a 30% imbalance: the balances would be 700 / 1300, or 35% / 65%. If the threshold were 20%, the pool would impose a surge fee. The formula is: $$ \text{surgeFee} = \text{staticFee} + \left( \text{maxFee} - \text{staticFee} \right) \cdot \frac{\text{pctImbalance} - \text{pctThreshold}}{1 - \text{pctThreshold}} $$ With a maximum of 50%, the fee would increase linearly from the base static fee of 1%, up to a maximum of 50%, starting at a 20% imbalance level. So in this example, the surge fee would be: $$ 1% + (50% - 1%) \cdot \frac{30% - 20%}{100% - 20%} = 7.125% \text{ (much higher than the standard 1%).} $$ Exactly at the threshold, the "surge" term is zero, and the user pays only the static fee. As the unbalanced proportion term approaches 1, the surge fee approaches: static + max - static \~ max fee. One important note: if a trade helps rebalance the pool (brings the asset split closer to 50/50), it only gets charged the lower base fee, encouraging balanced trading. StableSurge Pools use [Stable Math](./stable-math.md) (based on StableSwap, popularized by Curve) as well as the [StableSurge Math](./stablesurge-math.md) which \::: info Balancer v3 pools are limited at the Vault level to 8 tokens. Stable Pools have a safe maximum of 5 tokens, due to the constraints of Stable Math (same as in v2). Standard Stable Pools support 5 tokens. \::: #### Ideal For * **Pegged Tokens** - tokens that swap near 1:1, such as two stablecoins of the same currency (eg: DAI, USDC, USDT), or synthetic assets (eg: renBTC, sBTC, WBTC) * **Correlated Tokens** - tokens that swap near 1:$R$ with some slowly changing exchange rate $R$, like derivatives (eg: wstETH, wETH) * **Correlated Tokens with Redemption Delays** - tokens that swap near 1:$R$ with some slowly changing exchange rate $R$, like derivatives (eg: sUSDe, USDe) #### Stable Swaps Under the Balancer Umbrella One of the key advantages to having StableSurge Pools on Balancer specifically is that they are plugged into the same protocol as all other pools. Swapping between stablecoins is frequently used for arbitrage when one token is paired with two different stablecoins in different pools. By leveraging Batch Swaps on Balancer, these swaps can be combined into a single, gas-efficient transaction. Furthermore, utilizing StableSurge in combination with [Boosted Pool](../boosted-pool.md) technology makes Balancer pools the best option for correlated assets when compared to any other DEX. ##### Example Alternative decentralized exchanges only permit `[GHO, USDC, USDT]` with a static or non-directional fee algorithm. Many exchanges do not even support more than 2 tokens. On Balancer you can have a `StableSurge [Aave-GHO, Aave-USDC, Aave-USDT]` pool, meaning liquidity providers earn the yield from Aave's Core stablecoin lending markets, and only traders who oversell one of the tokens pay an increased trading fee. Any market makers or traders reinforcing the parity of the assets will pay only the standard static fee percentage. ### Use Cases #### **The Lido Fluid wstETH/WETH Liquidity Pool** [Lido](https://lido.fi/) is a liquid staking solution for ETH 2.0 backed by industry-leading staking providers. Lido lets users stake their ETH - without locking assets or maintaining their own infrastructure. The goal is to solve problems associated with initial ETH 2.0 staking: illiquidity, immovability and accessibility by making staked ETH liquid and allowing for participation with any amount of ETH to improve the security of the Ethereum network. stETH is a token that represents **Staked Ether**, combining the value of deposited ETH with staking returns. As an ERC20, stETH tokens can be swapped as one would swap WETH, allowing the benefits of ETH 2.0 staking while allowing users to continue using their staked Ether on decentralized finance products. Balancer StableSurge Pools are ideal for the wstETH-WETH pair as the stETH asset is highly correlated but not pegged 1:1 to ETH as it accrues staking returns. By rehypothecating these assets into Fluid, a higher capital efficiency is achieved by earning Balancer liquidity providers higher returns on their position, and offers Fluid borrowers more idle assets to utilize in their strategies. *** ## StableSurge Math ### Overview StableSurge Math leverages [Stable Math](./stable-math.md) for the pool invariant, and the hook itself only affects the swap fee. The swap fee is calculated linearly based on the absolute delta values of pool token balances based on an add, remove, or swap event, resulting in a fee increase when the surge criteria are met. The hook utilizes all parameters which inherited from the traditional stable pool but introduces 2 new variables which are mutable and controlled by the swap fee manager: Inherited: * `Number of tokens (n)`: Nominal amount of unique assets in the pool; not balances. For example for wETH/wstETH, n=2 * `staticSwapFeePercentage`: The base fee a trade will be charged when the pool is within the surge threshold range, or whenever a trade pushes the ratio of assets towards parity. Specific to StableSurge: * `surgeThresholdPercentage`: This value is the percentage away from parity (50:50) at which a pool will begin to charge traders surging fees. For example if set to 10% the fee will begin to increase once the pool balance percentages reach (55:45) * `maxSurgeFeePercentage`: This is the maximum fee the pool can experience, which would happen if the asset ratios strayed as close to 99%/1% as technically possible. This setting defines how quickly the fee will surge up from the base fee, once the threshold is hit. ### Implementation ```solidity Here is the main function in `StableSurgeHook` that calculations the surge fee: function _getSurgeFeePercentage( PoolSwapParams calldata params, address pool, uint256 staticFeePercentage, uint256[] memory newBalances ) internal view returns (uint256 surgeFeePercentage) { SurgeFeeData memory surgeFeeData = _surgeFeePoolData[pool]; // No matter where the imbalance is, the fee can never be smaller than the static fee. if (surgeFeeData.maxSurgeFeePercentage < staticFeePercentage) { return staticFeePercentage; } uint256 newTotalImbalance = StableSurgeMedianMath.calculateImbalance(newBalances); bool isSurging = _isSurging(surgeFeeData, params.balancesScaled18, newTotalImbalance); if (isSurging) { surgeFeePercentage = staticFeePercentage + (surgeFeeData.maxSurgeFeePercentage - staticFeePercentage).mulDown( (newTotalImbalance - surgeFeeData.thresholdPercentage).divDown( uint256(surgeFeeData.thresholdPercentage).complement() ) ); } else { surgeFeePercentage = staticFeePercentage; } } function _isSurging( SurgeFeeData memory surgeFeeData, uint256[] memory currentBalances, uint256 newTotalImbalance ) internal pure returns (bool isSurging) { if (newTotalImbalance == 0) { return false; } uint256 oldTotalImbalance = StableSurgeMedianMath.calculateImbalance(currentBalances); // Surging if imbalance grows and we're currently above the threshold. return (newTotalImbalance > oldTotalImbalance && newTotalImbalance > surgeFeeData.thresholdPercentage); } The `StableSurgeMedianMath` library contains the functions that calculate the imbalance. Note that though the examples use two-token pools for simplicity, the imbalance calculation applies to any stable pool (up to 5 tokens). Essentially, it is measuring the total deviation from a perfectly balance pool (where all balances are equal). function calculateImbalance(uint256[] memory balances) internal pure returns (uint256) { uint256 median = findMedian(balances); uint256 totalBalance = 0; uint256 totalDiff = 0; for (uint i = 0; i < balances.length; i++) { totalBalance += balances[i]; totalDiff += absSub(balances[i], median); } return totalDiff.divDown(totalBalance); } function findMedian(uint256[] memory balances) internal pure returns (uint256) { uint256[] memory sortedBalances = balances.sort(); uint256 mid = sortedBalances.length / 2; if (sortedBalances.length % 2 == 0) { return (sortedBalances[mid - 1] + sortedBalances[mid]) / 2; } else { return sortedBalances[mid]; } } function absSub(uint256 a, uint256 b) internal pure returns (uint256) { unchecked { return a > b ? a - b : b - a; } } ``` ## 80/20 Pools :::info page coming soon. ::: ## Impermanent Loss Impermanent Loss, sometimes referred to as divergent loss, is simply the opportunity cost of adding liquidity into an AMM pool vs. holding the individual tokens. Impermanent loss occurs when the prices of two assets diverge in price after initial liquidity provision. For example, if all assets in a pool increase by 20%, there is no impermanent loss. However, if only one asset increases in value by 20%, uncorrelated with the other tokens, there will be some impermanent loss. It is "impermanent" because it can be reversed; e.g., if the asset returned to its initial value, or the other tokens also increased 20%. For a basic example of impermanent loss followed by a reversal, see the next page. $ IL ={\frac {PoolValue}{HodlValue}}-1 $ ![Impermanent Loss - Relationship shown based on a two token pool with one asset and one stable coin. ](/images/impermanent-loss.png) \::: tabs @tab 50/50 Pools ## 50/50 Pools We will start with a simple 50/50 pool featuring COMP/WETH. We want to deposit $5,000 worth of each token, for a total value of $10,000. At initial investment time, WETH is priced at $2,000, and COMP is $250, so we deposit 2.5 WETH and 20 COMP. Some time later, we find that COMP has doubled to $500, while WETH has increased 15%, to $2,300. Although the value of the position has gone up along with the tokens, any uncorrelated deviation from the initial prices will result in some level of impermanent loss: we are missing out on some portion of the theoretical gains. *Here we calculate the invariant from the value function:* $$ V= \prod\_t B\_t^{W\_t} \\$$ $$$$ $$ B\_{i-WETH} = 2.5 \ W\_{i-WETH}=0.5 $$ $$ B\_{i-COMP} = 20 \ W\_{i-COMP}=0.5 \\$$ $$$$ $$ Initial \ Invariant: V = B\_{i-WETH}^{W\_{i-WETH}} \* B\_{i-COMP}^{W\_{i-COMP}} \\$$ $$ V = 2.5^{0.5} \* 20^{0.5} = 7.0710678 \\$$ $$$$ $$ After \ Arbitrage: 2.5 \ WETH \ and \ 20 \ COMP \ yields: $$ $$(2.5*1.15)^{0.5} \* (20*2)^{0.5} = 10.723805 $$ $$$$ Our gains will be determined by the invariant ratio, this value can be used for our token balances as well. $$Invariant \ Ratio\_{LP} = {\frac{10.723805}{7.0710678}} = 1.51657509$$ $$Ratio\_{HODL} = (1.15*0.5) + (2*0.5) = 1.575$$ $$$$ Here we can consider the USD values to be the same in the numerator and denominator therefore not needed to determine the ratio between the two. $$IL = {\frac{Invariant \ Ratio\_{LP}}{Ratio\_{HODL}}} = {\frac{1.51657509}{1.575}} - 1 = -0.03709518$$ $$ IL = -3.709518%$$ $$$$ For the new token balances we consider the invariant ratio compared to the price action of the individual asset. This proportion will yield the new balance of each token in relation to the initial join amount. $$ New \ Token \ Balance: B\_{t'}= B\_{t} \*{\frac{Ratio\_{LP}}{Price \ Action\_{t}}}$$ $$ WETH: 2.5 \* {\frac{1.5657509}{1.15}} = 3.2969 $$ $$ COMP: 20 \* {\frac{1.5657509}{2}} = 15.1657509 $$ $$$$ ***Please note these calculations can take place over any time frame. These occurred in roughly 12 days between June 25th and July 7th, 2021. This same price action could just as well take place over the course of 1 year. With 4% or more in swap fees or liquidity mining incentives, the LP position would become the more attractive option.*** These calculations are depicted by the following tables. **Initial Liquidity Position Amount: $10,000.00** $$ \begin{array} {|r|r|}\hline Token & Initial \ Value($) & Balance & USD \ Amount & Weight \ \hline WETH & 2,000 & 2.5 & 5,000 & 0.5 \ \hline COMP & 250 & 20 & 5,000 & 0.5 \ \hline \end{array} $$ **HODL Total: $15,750.00** $$ \begin{array} {|r|r|}\hline Token & Initial \ Value($) & Balance & USD \ Amount & Weight \ \hline WETH & 2,300 & 2.5 & 5,750 & 0.5 \ \hline COMP & 500 & 20 & 10,000 & 0.5 \ \hline \end{array} $$ **LP Total $15,165.75; with 4% Annual yield $15,772.38** $$ \begin{array} {|r|r|}\hline Token & Initial \ Value($) & Balance & USD \ Amount & Weight \ \hline WETH & 2,300 & 3.2969 & 7,582.87 & 0.5 \ \hline COMP & 500 & 15.16575 & 7,582.87545 & 0.5 \ \hline \end{array} $$ In our prior example, COMP and WETH went through uncorrelated price changes, and we observed the potential loss of value through impermanent loss. Now, if the prices continue to change, we will look at an example where WETH goes up to $3,000.00, while COMP decreases to $375.00. Perhaps surprisingly, this leads to a 50% gain in both assets relative to our original Liquidity Position. $$ V=20^{0.5}*2.5^{0.5}=7.071068 $$ $$ V\_2= (20*1.5)^{0.5}*(2.5*1.5)^{0.5}=10.6066017 $$ $$ Invariant\ Ratio= {\frac {V\_2}{V}}={\frac {10.6066017}{7.071068}}=1.5 $$ $$$$ Therefore, because the invariant ratio matches the price ratio, there will be no impermanent loss. $$ IL = {\frac{Invariant \ Ratio\_{LP}}{Ratio\_{HOD}}} = {\frac{1.5}{1.5}} - 1 = 0 \ or \ 0.00%$$ $$$$ This calculation will be performed from the impermanent loss state to the current state, in order to prove that the “loss” is indeed reversible under the proper conditions. **Initially:** $$ 3.2969 \ WETH \ at \ 2,300.00 \ each \ and \ 15.16575 \ COMP \ at \ 500.00 \ each $$ $$ V = 15.16575^{0.5}\* 3.2969^{0.5} = 7.071068 $$ **After Price Change:** $$ V\_{2} = (15.16575 \* 0.75)^{0.5} \* (3.2969 \* 1.30435)^{0.5} = 6.9937835 $$ $$ Invariant \ Ratio = {\frac{V\_{2}}{V}} = {\frac{6.99378335}{7.071068}} = 0.9890703 $$ $$$$ New Token Balances can be calculated as follows: $$ New\ Token\ Balances\:B\_{t'}*{\frac {Ratio\_{LP}}{Price\ Action\_t}} $$ $$ WETH:3.2969*{\frac {0.9890703}{1.30435}}=2.5 $$ $$ COMP: 15.16575\*{\frac {0.9890703}{0.75}}=20 $$ $$$$ These balances match our initial liquidity position, meaning overall we lost nothing to impermanent loss. The price action is still in our favor by 50% for both assets as we hold the same initial number of each. Also, we would have likely collected swap fees from traders, making our gains slightly larger. This shows that even large impermanent losses can always be reversed through subsequent price action. This can occur countless times as the asset prices in a pool fluctuate. It is important to understand the assets you are holding and how comfortable you are with volatility. In theory, great volatility will be coupled with large swap volumes, making the swap fees and gains for liquidity providers increase. Weighing the risk of impermanent loss against the accumulation of swap fees or “volatility farming” is the game a liquidity provider is playing over the long term. @tab 80/20 Pools ## 80/20 Pools Here we will examine how disproportionate pool weights affect the calculation of impermanent loss, using the same scenario described above, but for an 80/20 vs. a 50/50 pool. ##### COMP WETH 80/20 To illustrate the benefits of uneven pool weights, we calculate impermanent loss under conditions favoring COMP. We will hold 1 WETH @ $2000.00 and 32 COMP @ $250.00. ($8,000 in COMP and $2,000 in WETH: same $10,000 total as before). $$ B\_{i-WETH} = 1 \ \ W\_{i-WETH} = 0.2 $$ $$ B\_{i-COMP}= 32 \ \ W\_{i-COMP} = 0.8 $$ $$$$ $$ Initial \ Invariant \ = 1^{0.2} \* 32^{0.8} = 16.00 $$ $$ After \ Arbitrage: \ 1 \ WETH \ and \ 32 \ COMP \ yields: $$ $$ (1\*1.15)^{0.2} \* (32 \* 2)^{0.8} = 28.647290182 $$ $$$$ Our gains will then be determined by the invariant ratio. This can be used for our token balances as well. $$ Invariant \ Ratio\_{LP} = {\frac{28.647290182}{16}} = 1.7904556364 $$ $$ Ratio\_{HODL} = (1.15 \* 0.2) + (2 \* 0.8) = 1.83 $$ $$$$ Here we can consider the USD values to be the same in the numerator and denominator, so we don't need to determine their ratio. $$ IL = {\frac{Invariant \ Ratio\_{LP}}{Ratio\_{HODL}}} = {\frac{1.17904556364}{1.83}} = -0.02160893 \ or \ 2.160894%$$ $$$$ While impermanent loss still occurs in this scenario, the losses are nearly cut in half compared to the 50/50 pool (reduced by a factor of 0.5825, to be precise). When dealing with very large liquidity positions, these small amounts can make a large difference in value. Ultimately, this weighting strategy can protect liquidity providers from impermanent loss: or actually increase their exposure, depending on their token choices. Of course, if prices return to their initial values, or move proportionally, at a certain point the “losses” will revert to zero regardless of the weights. @tab Multi-token Pools ## Multi-token Pools Balancer's multi-token pools are one of our unique features. Below is an example of how impermanent loss on one of these pools can occur, including details on volatility and stable coins. ##### Advanced Example – Multi-token Pool Below we show an example of a multi-token pool, and how impermanent loss can occur and then be reversed. We will look at a Polygon pool: 25% USDC, 25% WMATIC, 25% BAL, 25% WETH. Initially we will assume we joined with $10,000 in USD, distributed evenly over all the assets. ##### Initial Conditions: $$ USDC \ at \ $1.00 = 2500 \ USDC \ \ \ WMATIC \ at \ $1.25 = 2,000 \ WMATIC $$ $$ BAL \ at \ $25.00 = 100 BAL \ \ \ WETH \ at \ $2,500.00 = 1.0 \ WETH $$ $$$$ We will assume USDC stays at a constant value, BAL increases by 15% to $28.75, WMATIC decreases by 4% to $1.20, and WETH increases 50% to $3,750.00. $$ V\_{initial} = 2500^{0.25} \* 2,000^{0.25} \* 100^{0.25} \* 1^{0.25} = 149.534878 $$ $$ V\_{2} = (2,500)^{0.25} \* (2,000 \* 0.96)^{0.25} \* (100 \* 1.15)^{0.25} \* (1 \* 1.5)^{0.25} = 169.631923 $$ $$$$ $$ Invariant \ Ratio = {\frac{V\_{2}}{V\_{initial}}} = {\frac{169.31923}{149.534878}} = 1.13439704 $$ $$ Ratio\_{HODL} = (1 \* 0.25) + (0.96 \* 0.25) + (1.15 \* 0.25) + (1.5 \* 0.25) = 1.1525 $$ $$$$ $$ IL = {\frac{Invariant \ Ratio\_{LP}}{Ratio\_{HODL}}} = {\frac{1.13439704}{1.1525}} - 1 = 0.0157076 \ or \ 1.57076% $$ ##### New Token Balances can be calculated as follows: $$ New \ Token \ Balances \ B\_{t'} = B\_{t} \* {\frac{Ratio\_{LP}}{Price \ Action\_{t}}} $$ $$$$ $$ USDC = 2,500 \* {\frac{1.13439704}{1}} = 2,836 \ USDC $$ $$ WMATIC = 2,000 \* {\frac{1.13439704}{0.96}} = 2,363.327 \ WMATIC $$ $$ BAL = 100 \* {\frac{1.13439704}{1.15}} = 98.643 \ BAL $$ $$ WMATIC = 1 \* {\frac{1.13439704}{1.5}} = 0.7562647 \ WETH $$ Since the pool contains a stable coin (USDC), impermanent loss is nearly inevitable unless all tokens return to their initial value at the time of liquidity provision. Therefore, Liquidity Providers for the pool above or any pool combining stable and non-stable assets should mostly be concerned with volatility and earning a return from swap fees or liquidity mining incentives. They are essentially positioning themselves with the expectations of this type of price pattern: ![Price Chart for mid-July 2022 COMP](/images/Multi-token-chart.png) ##### One may provide liquidity to a pool in which USDC and an asset (X) at 14.01 are present in mid-July. Then by the time the chart comes to an end the price is still 14.01 after going through stages of upward and downward price changes. This will yield an amount of swap fees to be collected as profit as well as 0% impermanent loss. At any point when all prices revert to their initial values, arbitragers will bring the tokens back to their initial balances. With a stable token present, this means no gains can be made without impermanent loss or swap fees. $$$$ $$ V\_{current} = 2,836^{0.25} \* 2,363.327^{0.25} \* 98.643^{0.25} \* 0.7562647^{0.25} = 149.53489 $$ $$ V\_{2}= (2,836)^{0.25} \* (2,363.327 \* 1.04167)^{0.25} \* (98.643 \* 0.8696)^{0.25} \* $$ $$(0.7562647 \* 0.67)^{0.25} = 131.8204$$ $$ The \ values \ above \ were \ shortened \ for \ formatting $$ $$$$ $$ {\frac{131.8204}{149.53489}} = 0.881536 \ The \ calculations \ below \ will \ yield \ the \ $$ $$ initial \ token \ balances \ therefore \ IL = 0%$$ $$$$ $$ USDC = 2,836 \* {\frac{0.881536}{1}} = 2,500 \ USDC $$ $$ WMATIC = 2,363.327 \* {\frac{0.881536}{1.04167}} = 2,000 \ WMATIC $$ $$ BAL = 98.643 \* {\frac{0.881536}{0.8696}} = 100 \ BAL $$ $$ WETH = 0.7562647 \* {\frac{0.881536}{0.6667}} = 1 \ WETH $$ \::: ## Weighted Math ### Overview Weighted Math is designed to allow for swaps between any assets whether or not they have any price correlation. Prices are determined by the pool balances, pool weights, and amounts of the tokens that are being swapped. Balancer's Weighted Math equation is a generalization of the $x\*y=k$ constant product formula, accounting for cases with $n \geq2$ tokens as well as weightings that are not an even 50/50 split. For more formulas and derivations of the below formulas, please refer to the [Balancer Whitepaper](/whitepaper.pdf). ### Implementations #### TypeScript Developers can use the TypeScript math implementations used by the Smart Order router (equivalent v2 reference). * [weightedMath.ts](https://github.com/balancer/balancer-sor/blob/john/v2-package-linear/src/pools/weightedPool/weightedMath.ts) #### Python There are also Python implementations in progress * [weightedMath.py](https://github.com/officialnico/balancerv2cad/blob/main/src/balancerv2cad/WeightedMath.py) ### Invariant The value function $V$is defined as: $$ V= \prod\_t B\_t^{W\_t} $$ Where * $t$ ranges over the tokens in the pool * $B\_t$ is the balance of the token in the pool * $W\_t$​is the normalized weight of the tokens, such that the sum of all normalized weights is 1. ### Spot Price Each pair of tokens in a pool has a spot price defined entirely by the weights and balances of just that pair of tokens. The spot price between any two tokens,$SpotPrice^o\_i$, or in short $SP^o\_i$, is the ratio of the token balances normalized by their weights: $$ SP^o\_i = \frac{\frac{B\_i}{W\_i}}{\frac{B\_o}{W\_o}} $$ * $B\_i$ is the balance of token $i$, the token being sold by the swapper which is going into the pool * $B\_o$ is the balance of token $o$, the token being bought by the swapper which is going out of the pool * $W\_i$ is the weight of token $i$ * $W\_o$ is the weight of token $o$ #### Spot Price with Swap Fees When we consider swap fees, we do exactly the same calculations as without fees, but using $A\_i \cdot (1-swapFee)$ instead of $A\_i$ since fees are taken out of the input amount. The equation then becomes: $$ SP^o\_i = \frac{\frac{B\_i}{W\_i}}{\frac{B\_o}{W\_o}} \cdot \frac{1}{1-swapFee} $$ ### Swap Equations #### outGivenIn When a user sends tokens $i$ to get tokens $o$, all other token balances remain the same. Therefore, if we define $A\_i$ and $A\_o$ as the amount of tokens $i$ and $o$ exchanged, and since the value function $V$ must be constant before and after the swap, we can calculate the amount $A\_o$ a users gets when sending $A\_i$. $$ A\_o = B\_o \cdot \left(1-\left(\frac{B\_i}{B\_i + A\_i}\right)^{\frac{W\_i}{W\_o}}\right) $$ \::: info If you're computing this value yourself, remember that the pool collects swap fees as a percentage of the **input token**. In the equation above,$A\_i$ is the amount that the pool actually swaps into the output token, not the amount sent by a swapper, $A\_{sent}$. To calculate through, we must compute:$A\_i = A\_{sent} \* (1-swapFee)$ \::: #### inGivenOut It is also very useful for swappers to know how much they need to send of the input token $A\_i$ to get a desired amount of output token $A\_o$: $$ A\_i = B\_i \cdot \left(\left(\frac{B\_o}{B\_o - A\_o}\right)^{\frac{W\_o}{W\_i}}-1\right) $$ ## Weighted Pools ### Overview Weighted Pools are an extension of the classical $x \* y = k$ AMM pools popularized by Uniswap v1. Weighted Pools use [Weighted Math](./weighted-math.md), which makes them great for general cases, including tokens that don't necessarily have any price correlation (ex. DAI/WETH). Unlike pools in other AMMs that only provide 50/50 weightings, Balancer Weighted Pools enable users to build pools with more than two tokens and custom weights, such as pools with 80/20 or 60/20/20 weights. \::: info Balancer v3 pools are limited at the Vault level to 8 tokens, and Standard Weighted Pools support any token count up to this limit. Weighted Pools have additional security constraints based on Weighted Math. (These are the same as in v2.) * The minimum token weight is 1% * Weights must sum to 100% * Swaps amounts cannot exceed 30% of the token balance * The invariant cannot decrease below 70% or increase beyond 300% on liquidity operations * The swap fee must be between 0.001% and 10%. (Note that the lower limit is higher than in v2.) \::: Note that the swap fee and invariant limits are defined in `WeightedPool` through implementing the `ISwapFeePercentageBounds` and `IUnbalancedLiquidityInvariantRatioBounds` interfaces, which are included in `IBasePool`. \::: chart Weighted Pool ```json { "type": "pie", "data": { "labels": ["WETH", "BAL", "DAI"], "datasets": [ { "label": "%", "data": [33, 33, 33], "backgroundColor": [ "rgba(255, 99, 132, 0.2)", "rgba(54, 162, 235, 0.2)", "rgba(255, 206, 86, 0.2)" ], "borderColor": [ "rgba(255, 99, 132, 1)", "rgba(54, 162, 235, 1)", "rgba(255, 206, 86, 1)" ], "borderWidth": 1 } ] }, "options": {} } ``` \::: ### Advantages #### Exposure Control Weighted Pools allow users to choose their levels of exposure to certain assets while still maintaining the ability to provide liquidity. The higher a token's weight in a pool, the less impermanent loss it will experience in the event of a price surge. For example, if a user wants to provide liquidity for WBTC and WETH, they can choose the weight that most aligns with their strategy. A pool more heavily favoring WBTC implies they expect bigger gains for WBTC, while a pool more heavily favoring WETH implies bigger gains for WETH. An evenly balanced pool is a good choice for assets whose value is expected to remain proportional in the long run. #### Impermanent Loss [Impermanent Loss](./impermanent-loss.md) is the difference in value between holding a set of assets and providing liquidity for those same assets. For pools that heavily weight one token over another, there is far less impermanent loss, but this doesn't come for free: very asymmetric pools do have higher slippage on swaps, due to the fact that one side has much less liquidity. 80/20 pools have emerged as a happy medium, combining optimistic liquidity provision with Impermanent Loss mitigation. ## veBAL FAQ #### How do I get veBAL? When you provide liquidity into a Balancer pool, you take out an ERC-20 token we call a "Balancer Pool Token", or BPT for short. In veBAL, the used BPT is from a B-80BAL-20WETH pool. You will need to have BAL tokens or WETH to invest in the B-80BAL-20WETH pool. You can deposit a single asset, which will incur some price impact, or you can deposit both assets in the correct weights. You will receive BPT which you can then time lock here to receive veBAL. The length of time the BPT is locked corresponds to how much veBAL you'll get. veBAL is a function of time and asset. 1 veBAL equals 1 BPT locked for 52 weeks. Where a 1 week lock of 1 BPT will give 1/52 veBAL. #### Can I transfer BPTs or veBAL? Yes, you can transfer BPTs. Rewards will accrue in the wallet where they are held. veBAL is a non-standard ERC-20 token and cannot be transferred. #### Do veBAL holders receive a portion of the protocol fees? Yes. For more information, see [Protocol Fee Operations](../protocol-fees.md) and the [Protocol Fee Model](../protocol-fee-model/protocol-fee-model.md). #### How are the protocol fees paid? veBAL holders receive protocol fees distributed in **USDC**. The fee distribution varies depending on whether fees come from core pools or non-core pools—see the [Protocol Fee Model](../protocol-fee-model/protocol-fee-model.md) for the current distribution splits. #### When are incentives paid? Incentives on mainnet are now accrued each block. Protocol fees are distributed on a weekly basis. #### Is there a way to view how much total veBAL there is? Yes, that information can be found on the [veBAL Dune Dashboard](https://dune.com/balancer/veBAL) #### How much BPT (B-80BAL-20WETH) do I stake to maximize my multiplier? What amount do you need to stake at 1 year to hit the 2.5x boost for liquidity incentives? The incentives boost is related to your share of the pool and share of veBAL. Range limited from 1x to 2.5x. Community contributors have developed a very useful [veBAL Boost Calculator](https://balancer.tools/veBAL) tool and the math is explained [here](/reference/vebal-and-gauges/boost-calculations.html). #### If veBAL is on mainnet, can it boost staking incentives on L2? Yes, Balancer now supports cross-chain gauges to receive incentive boosts. The boost depends on what fraction of the gauge staked liquidity you hold and what fraction of the total veBAL you hold. See more on boosting [here](/reference/vebal-and-gauges/boost-calculations.html) #### How do I extend my veBAL lock up? Go to the [veBAL site](https://app.balancer.fi/vebal#/ethereum/vebal), see "Lock until" , click "+", choose the time desired, and confirm. #### Will voters vote on how much emissions go to each network or is that preset? Voters will determine the amount of emissions going to gauge listed pools on Ethereum mainnet and on L2 chains. The voting will happen on Ethereum mainnet. #### Is the veBAL gauge vote on-chain, and does it require gas fees? Yes, gauge votes are on-chain and will cost a gas fee. They can be cast in the [Balancer dAPP](https://app.balancer.fi/#/ethereum/vebal). Other governance decisions (i.e. approving new gauges) are done by [snapshot](https://snapshot.org/#/balancer.eth) and cost no gas. There is a weekly vote for veBAL holders which ends at 00:00 UTC on Thursdays. If the same pools will be selected each epoch, no additional vote, transaction, or gas is needed. veBAL holders only have to vote once, unless they want to change their allocation. #### Can I delegate my votes? veBAL gauge voting can not be delegated, however Snapshot voting covering general governance for DAO operations and management of gauges can be delegated [here](https://snapshot.org/#/delegate/balancer.eth). A list of delegates and more information about them and how/why they vote can be found [in the Delegate Citadel](https://forum.balancer.fi/c/delegate-citadel/14) on the Balancer Forum. #### How do I make a pool eligible for gauge voting? Need to make a governance proposal. See [Governance Process](../process.md) and [The instructions for a Gauge request on the Forum](https://forum.balancer.fi/t/instructions-overview/2674). #### Does veBAL support Gnosis Safe? It's normal for vote escrowed (ve) systems to not allow arbitrary contracts to lock as otherwise it's easy to tokenize the ve tokens which defeats the point if the tokenomics of said derivative does not require appropriate locking. Users can lock up veBAL from an EOA and delegate it to your Gnosis Safe to earn boosts. Entities interested in making a large investment in veBAL may appeal to governance to have a multisig whitelisted for veBAL participation. #### Is there a repository for the contract addresses of all the new staking contracts and veBAL contracts? \| Contract | Purpose | \|:----------------------------------------------------------------------------------------------------------------|:-----------------------------------------------------| \| [veBAL](https://etherscan.io/tx/0xaa29cd251cdb024c415b0e13f67a0ca74fe5abc3de9a9fedd1ae26fd39be4025) | Locks BPTs and reports veBAL balances | \| [Gauge Controller](https://etherscan.io/address/0xC128468b7Ce63eA702C1f104D55A2566b13D3ABD) | Manages Gauges and emissions | \| [Gauge Adder](https://etherscan.io/address/0x2fFB7B215Ae7F088eC2530C7aa8E1B24E398f26a) | Adds new gauges approved by governance to the system | \| [Mainnet Uncapped Gauge Factory](https://etherscan.io/address/0x4e7bbd911cf1efa442bc1b2e9ea01ffe785412ec) | Create gauges with no cap on Mainnet | \| [Mainnet Capped Gauge Factory](https://etherscan.io/address/0xf1665e19bc105be4edd3739f88315cc699cc5b65) | Create gauges with possible cap on Mainnet | \| [Polygon Capped Gauge Factory](https://etherscan.io/address/0xa98bce70c92ad2ef3288dbcd659bc0d6b62f8f13) | Create gauges with a possible CAP on Polygon | \| [Polygon Child Chain Gauge Factory](https://polygonscan.com/address/0x3b8ca519122cdd8efb272b0d3085453404b25bd0) | Create child gauge to hold LP tokens on Polygon | \| [Arbitrum Capped Gauge Factory](https://etherscan.io/address/0x1c99324edc771c82a0dccb780cc7dda0045e50e7) | Create gauges with a possible CAP on Arbitrum | \| [Arbitrum Child Chain Gauge Factory](https://arbiscan.io/address/0xb08e16cfc07c684daa2f93c70323badb2a6cbfd) | Create child gauge to hold LP tokens on Arbitrum | ## veBAL ### Overview veBAL (vote-escrow BAL) is a vesting system based on [Curve's veCRV mechanism](https://curve.readthedocs.io/dao-vecrv.html) which locks 80/20 BAL/WETH Balancer Pool Tokens for a maximum of 1 year. The veBAL and Gauge system is designed to promote long-term token-holder alignment and facilitate fair protocol fee distribution. By locking the BAL/WETH 80/20 BPT, holders are given veBAL, entitling them to governance rights and protocol fee collection. A user's veBAL balance is directly proportional to the amount of BAL/WETH 80/20 BPT locked and the duration of time left in the lock period. In short if a user locks 1 BPT for 52 weeks, they will receive the same amount of “vote escrowed” strength as someone who locks 2 BPT for 26 weeks. Implications: * veBAL equates to boosted liquidity mining emissions for all gauges. The share of a given staked pool, and the lock multiplier are both factors in the amount a user will be entitled to in liquidity mining emissions. * veBAL holders receive a share of [protocol fees](../protocol-fees.md). The distribution varies by pool type (see [Protocol Fee Model](../protocol-fee-model/protocol-fee-model.md) for details): * **Non-core pools**: 82.5% of protocol fees go directly to veBAL holders as USDC payments * **Core pools**: 12.5% goes directly to veBAL holders, with an additional 70% distributed as voting incentives on core pools (requiring veBAL holders to vote for revenue-generating pools to capture this portion) * veBAL is the governance token of Balancer, used in Snapshot voting to authorize changes to the DAO including the management (adding/removing) of gauges and funding of service providers. * veBAL does have a gauge to direct emissions to the holders if chosen. This option is capped at 10% of total emissions of BAL at a given time in the inflation schedule. The overflow, if a vote goes over 10%, will go to the DAO treasury, where governance will have ownership of it. * As demonstrated by BIP-161 the handling and amount of protocol fees are subject to change based on the [Balancer Governance Process](../process.md) This gives veBAL holders the option to choose pools for which they have liquidity positions for increased emissions or a potential for "bribing" battles can ensue. [BIP-903](https://forum.balancer.fi/t/bip-903-transition-core-pool-incentive-program-to-stake-dao-s-votemarket-v2/6928) introduced StakeDAOs [Votemarket](https://votemarket.stakedao.org/balancer) as the default voting incentive marketplace. It allows projects to provide veBAL holders an incentivize to vote in a direction they prefer, hence the term “bribe”. In the same breath, the emission schedule for BAL has been defined and is set permanently. Before veBAL, 145,000 BAL was being emitted per week, which was unsustainable without a ceiling on emissions. The two key takeaways for the new inflation schedule will be a halving of the inflation rate every 4 years, and a total supply of BAL being capped at 94,000,000. #### How is veBAL different from veCRV? There are a few modifications that set veBAL apart: * Instead of locking pure BAL, users obtain veBAL by locking 80/20 BAL/WETH Balancer Pool Tokens (BPTs). This ensures that even if a large portion of BAL tokens are locked, there is deep liquidity. * veBAL's maximum locking period is 1 year, a decrease from veCRV's 4 year period. The minimum locking period is 1 week. DeFi moves quickly, and in the event governance decides to use a new voting system, this allows for a shorter, but still sufficiently long, waiting period to transition. ## Balancer API Balancer's API exposes data on Balancer's smart contracts accessible via graphql. The API is running as a graphql server and is deployed at . \:::info Want to keep up with changes to the API? You can subscribe to the [Telegram channel](https://t.me/BalBeetsApi) or check out the [repo](https://github.com/balancer/backend) to stay updated. \::: Queries are organized around these main domains: * Pools * Gauges * Events * Users * Tokens * Prices * SOR (Smart Order Router used for swaps) One of the conventions is to use "dynamicData" for querying changing parts of the state. Further documentation is available on the self documented [api server](https://api-v3.balancer.fi). ## Examples * [Pools - Get a v2 pool's details including APRs](./pool-details-with-apr.md) * [Pools - Pools with TVL greater than $10k](./pools-with-tvl.md) * [Pools - Top 10 pools ordered by TVL](./pools-top-ordered-tvl.md) * [Pools - Get swap events for a pool](./pool-swap-events.md) * [Swap - Query the Smart Order Router (SOR)](./swap-query-sor.md) * [User - Get pool balances for a user](./user-pool-balance.md) * [User - Get v2 pool add & remove events for a user](./user-pool-add-remove.md) ## Get a v2 pool's details including APRs ```graphql { poolGetPool(id: "0x7f2b3b7fbd3226c5be438cde49a519f442ca2eda00020000000000000000067d", chain:MAINNET) { id name type version allTokens { address name } poolTokens { address symbol balance hasNestedPool } dynamicData { totalLiquidity aprItems { title type apr } } } } ``` ## Get swap events for one or more pools This query returns all Swap events for the two pools in the filter. One is a CowAMM pool and the other a v2 weighted pool. As they have different event types, we also use the expanded `GqlPoolSwapEventCowAmm` and `GqlPoolSwapEventV3` types to get CowAMM and "standard" pool specific swap datas. ```graphql { poolEvents( where: { typeIn: [SWAP], chainIn: [MAINNET], poolIdIn: ["0xf08d4dea369c456d26a3168ff0024b904f2d8b91", "0x3de27efa2f1aa663ae5d458857e731c129069f29000200000000000000000588"] }, first: 1000 ) { type valueUSD timestamp poolId ... on GqlPoolSwapEventCowAmm { surplus { address amount valueUSD } fee { address amount valueUSD } } ... on GqlPoolSwapEventV3 { fee { address amount valueUSD } } } } ``` ## Query to find top 10 pools ordered by TVL This query also returns the pools APRs and staking gauge. One of the conventions is to use "dynamicData" for querying changing parts of the state. ```graphql { poolGetPools(first:10, orderBy:totalLiquidity) { id name chain dynamicData { totalLiquidity aprItems { apr } } staking { gauge { gaugeAddress } } } } ``` ## Query all pools on Arbitrum and Avalanche that have TVL greater than $10k ```graphql { poolGetPools(where: {chainIn: [AVALANCHE, ARBITRUM], minTvl: 10000}) { id address name } } ``` ## Swap - Query the Smart Order Router (SOR) In this example we query for best paths to swap 1WETH to USDC. Note the use of human scaled amount. ```graphql { sorGetSwapPaths( chain: MAINNET swapAmount: "1" swapType: EXACT_IN tokenIn: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2" tokenOut: "0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48" ) { swapAmountRaw returnAmountRaw priceImpact { priceImpact error } } } ``` ## Get add and removes for a user This query returns all add and removes of a specific user for a specific pool. It also returns the token amounts with which the user has added or removed liquidity from the pool. ```graphql { poolEvents( where: { typeIn: [ADD, REMOVE], chainIn: [MAINNET], poolIdIn: ["0x3de27efa2f1aa663ae5d458857e731c129069f29000200000000000000000588"], userAddress: "0x741AA7CFB2c7bF2A1E7D4dA2e3Df6a56cA4131F3" } first: 1000 ) { type valueUSD timestamp poolId ... on GqlPoolAddRemoveEventV3{ tokens{ address amount valueUSD } } } } ``` ## Get pool balances for a user This query returns all pools that the user has a balance. We differentiate between staked and wallet balances. Wallet balances mean that the user holds the BPT in his wallet. Staked balances mean that the BPT is staked in a supported service, such as a gauge or on Aura. ```graphql { poolGetPools(where:{chainIn:[MAINNET], userAddress:"0x..."}){ address userBalance{ stakedBalances{ balance balanceUsd stakingType } walletBalance walletBalanceUsd totalBalance totalBalanceUsd } } } ``` ## Dashboards **Balancer Labs' data team** works on building dashboards where internal and external stakeholders can gather as much information as possible on the protocol. If you have any questions/feedback on our dashboards, feel free to reach out by filling out this [form](https://docs.google.com/forms/d/e/1FAIpQLScHCgRxCGfyJp02Dl_nK6shDnXY1FDDXpsd-sqjTeIsv5EteQ/viewform) or directly on [Discord](https://discord.balancer.fi/). **Here i a run-down of our dashboards:** \| Dashboard Name | Main information | Filters | \| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | ---------------------------------------- | \| [veBAL](https://dune.com/balancer/vebal) | Total veBAL, veBAL locking, power by top LPs, votes by gauge and network, vlAURA distribution | | \| [veBAL Analysis](https://dune.com/balancer/vebal-analysis) | Power and votes by provider | Provider | \| [veBAL Gauge Votes](https://dune.com/balancer/vebal-gauge-analysis) | Voters on gauge | Gauge | \| [veBAL Wrappers](https://dune.com/balancer/vebal-wrappers) | Volume and Pegs for auraBAL, sdBAL and TetuBAL over time | | \| [Balancer Overview](https://dune.com/balancer/overview) | Volume (by source, cumulative, 24h and 7d), TVL, average swap fees, liquidity utilization, Balancer on 1inch and CowSwap | Pool ID, Start Date, End Date, and blockchain | \| [Balancer Exchange](https://dune.com/balancer/exchange) | Volume (by token, 24h and 7d), # of swaps, new/old traders, fees distribution, gas costs | Aggregation, Start Date, End Date, and blockchain | \| [Balancer Report](https://dune.com/balancer/report) | TVL, Volume (cumulative, market share, by token), swap fees revenue | Pool ID, Start Date, End Date, and blockchain | \| [TVL](https://dune.com/balancer/tvl) | Daily data for TVL, by chain and pool | Date Range in Days, Pool Rank by Daily TVL | \| [Volume](https://dune.com/balancer/volume) | 24h, 7d, 30d, moving averages, volume by pool, daily volume changes, by blockchain (median swap, # of swaps, new traders, Net token inflow/outflow | | \| [Balancer Volume Breakdown](https://dune.com/balancer/volume-breakdown) | Overview on volume by token, pool, and source | Source Address, -Source Name, Pool ID, Start Date, End Date, and blockchain | \| [Balancer Volume - Source Breakdown](https://dune.com/balancer/volume-source-breakdown) | Weekly, daily, and hourly volume breakdown by source (DEXs and Aggregators, Heavy Traders and MEV bots). Addresses used are listed on this [link](https://dune.com/queries/3004790) | Source Address, -Source Name, Pool ID, Start Date, End Date, and blockchain | \| [Balancer Volume - Pool Breakdown](https://dune.com/balancer/volume-pool-breakdown) | Weekly, daily, and hourly volume breakdown by top pools | Start Date, End Date, and blockchain | \| [Balancer Volume - Token Breakdown](https://dune.com/balancer/volume-token-breakdown) | All-time volume by token, Monthly and hourly top tokens volume | Blockchain, Aggregation, Top x tokens | \| [Pools Overview](https://dune.com/balancer/pools) | Balancer Pools, TVL, Volume (24h, 7d), Pools Created (by blockchain and pool type), Fees distribution | Start Date, End Date, and blockchain | \| [Pool Analysis](https://dune.com/balancer/pool-analysis) | Volume (daily, 24h, 7d, by source), TVL, Swap Fees, LPs, Liquidity Utilization | Pool ID, Start Date, End Date, and blockchain | \| [8020 Initiative](https://dune.com/balancer/8020-initiative) | ve8020 Pools, TVL, Impermanent Loss, Price Volatility, Pool Balancer, Volume by Source | Blockchain, Pool Address | \| [Built on Balancer](https://dune.com/balancer/built-on-balancer) | TVL, volume and Liquidity Utilization for projects built on Balancer | Project, Start Date | \| [Balancer Governance](https://dune.com/balancer/governance) | BAL Minted, holders, LPs, price, supply, emission rates | | \| [Balancer v2 LBPs](https://dune.com/balancer/v2-lbps) | Volume, amount raised, transactions, participants, tokens sold, indirect volume | LBP | \| [Gas Costs](https://dune.com/balancer/gas-costs) | Gas Costs per swap, pool\_type and blockchain | TimeFrame, days, Pool Type, blockchain | \| [Balancer v2 Revenues Overview](https://dune.com/balancer/v2-revenues) | Weekly swap fee revenues, LP revenues by pool and token | Pool ID, Start Date, End Date, blockchain, and Token Address | \| [Protocol Fees](https://dune.com/balancer/protocol-fees) | Protocol Fees Collected per pool, blockchain, pool type, Fees Collected and BAL emissions per round, Core Pools Fees Collected per Epoch| Pool ID, Start Date, End Date, blockchain, Only Core Pools, Round ID, Fee Epoch | \| [LSTs](https://dune.com/balancer/lst) | Liquidity, Volume, Fees Collected and Liquidity Utilization on LST Pools and tokens | Pool ID, Start Date, End Date, blockchain, and Token Address | \| [LST / LRT DEX Volume](https://dune.com/balancer/dex-volume-lst) | LST / LRT Volume by DEX, Market Share by DEX, Relative Weight of LSTs / LRTs on Trades| Start Date, Aggregation, blockchain, and Token Pair | \| [Balancer - CoWSwap AMMs - Overview](https://dune.com/balancer/balancer-cowswap-amm) | TVL, Volume, Pools created by Blockchain, Surplus, Liquidity Utilization| Start Date, Blockchain| \| [Balancer - CoWSwap AMMs - Pool Analysis](https://dune.com/balancer/balancer-cowswap-amm-pool) |Volume (daily, 24h, 7d), TVL, Surplus, Liquidity Utilization, Token Balances | Pool Address, Start Date, End Date, blockchain| \| [Balancer On Mode](https://dune.com/balancer/balancer-on-mode) | Volume (cumulative, 24h and 7d), Daily Swaps, TVL, liquidity utilization, Pools | Start Date, End Date | \| [Balancer On Fraxtal](https://dune.com/balancer/balancer-on-fraxtal) | Volume (cumulative, 24h and 7d), Daily Swaps, TVL, liquidity utilization, Pools | Start Date, End Date | \| [Arbitrum Liquidity Growth Program](https://dune.com/balancer/arbitrum-lgp) | Volume (cumulative, 24h and 7d), Daily Swaps, DAU, TVL, liquidity utilization, Pools, LST / LRT Liquidity, Yield-Bearing Stablecoins Liquidity, ARB Incentives by Gauge and Tx., BAL Emissions | Start Date, End Date, TVL Currency | \| [Optimism LST/LRT Grant](https://dune.com/balancer/optimism-lstlrt) | Volume (cumulative, 24h and 7d), Daily Swaps, DAU, TVL, liquidity utilization, LST / LRT Liquidity and Volume, OP Incentives by Gauge and Tx. | Start Date, End Date | \| [Rate Providers Review](https://dune.com/balancer/rate-providers) | Historical Exchange Rates| Blockchain, Contract Address, Start Block, Function Name | ## Overview Welcome to Balancer's data analytics powered by Dune! [Dune Analytics](https://dune.com/home) is a powerful data analytics platform that enables users to explore, query, and visualize on-chain data from various DeFi protocols, including Balancer. It provides a user-friendly interface to interact with blockchain data, empowering both data analysts and DeFi enthusiasts to gain valuable insights. Balancer Labs' data team is dedicated to providing with up-to-date and comprehensive analytics. We constantly update our Dune [dashboards](https://dune.com/browse/dashboards?team=balancer), [spells](https://github.com/duneanalytics/spellbook/tree/main/models/balancer), and [queries](https://dune.com/browse/queries?team=balancer) to ensure that you have access to the latest information on Balancer Protocol. Explore our Dune dashboards, dive into the data, and discover the insights that matter most to you. If you have any questions or need assistance with specific queries, feel free to reach out by filling out this [form](https://docs.google.com/forms/d/e/1FAIpQLScHCgRxCGfyJp02Dl_nK6shDnXY1FDDXpsd-sqjTeIsv5EteQ/viewform) or directly on [Discord](https://discord.balancer.fi/). ## Spells ### Introduction Unlock the power of Balancer data with our meticulously crafted Dune Spells! Balancer Labs' data team is dedicated to the relentless pursuit of excellence, continuously refining and updating our Dune Spells to deliver the most accurate, insightful, and up-to-date analytics for the Balancer community. ### Explore the Spellbook Embark on your journey through the world of Balancer's data by exploring our Spellbook. Visit to delve into the intricacies of our models, gaining access to the very spells that empower your understanding of Balancer's ecosystem. **Here are the updated dashboards:** \| Spell | Description | Upstream Spells | Chains | \| ----------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------- | ------------------------------------------------ | \| [balancer\_trades](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/dex/models/_projects/balancer/trades/balancer_trades.sql) | All trades on Balancer, with information on date, tx\_hash, tx\_from, tx\_to, tokens, amounts, version of Balancer in which the trade happened, the pool and its respective swap fee | balancer\_v1\_ethereum\_trades, balancer\_v2\_\[chain]*trades, balancer\_cowswap\_amm*\[chain]*trades | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_liquidity](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/liquidity/balancer_liquidity.sql) | Daily liquidity information for balancer pools, by each token contained in a pool. It is also divided in pool\_liquidity\_usd, which also includes BPTs (Balancer Pool Tokens) balances and and protocol\_liquidity\_usd, which excludes BPTs. | balancer\_v1\_ethereum\_liquidity, balancer\_v2*\[chain]*liquidity, balancer\_cowswap\_amm*\[chain]*liquidity | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_pools\_fees](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/dex/models/_projects/balancer/pools/balancer_pools_fees.sql) | Balancer v2 swap fees stored at the pool level, including information on the transaction where the fee was set. | balancer\_v2*\[chain]*pools\_fees | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_protocol\_fees](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/protocol_fee/balancer_protocol_fee.sql) | Daily Protocol Fee collected and Treasury Revenue by pool and token. | balancer\_v2*\[chain]*protocol\_fees | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_bpt\_supply](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/bpt/balancer_bpt_supply.sql) | BPT supply over time of ComposableStablePools versions 4+ | balancer\_v2*\[chain]*bpt\_supply | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_bpt\_prices](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/bpt/balancer_bpt_prices.sql) | Balancer Pool Token (BPT) hourly median price by pool. | balancer\_v2*\[chain]*bpt\_prices | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_flashloans](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/flashloans/balancer_flashloans.sql) | All Balancer flashloans | balancer\_v2*\[chain]*flashloans | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_transfers\_bpt](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/bpt/balancer_transfers_bpt.sql) | Balancer Pool Token (BPT) transfer logs on Balancer, | balancer\_v2*\[chain]*transfers\_bpt | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_bpt\_supply\_changes](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/bpt/balancer_bpt_supply_changes.sql) | All BPTs mints and burns | balancer*\[chain]*token\_balance\_changes | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_bpt\_supply\_changes\_daily](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/bpt/balancer_bpt_supply_changes_daily.sql) | Daily Deltas in BPT supply | balancer*\[chain]*token\_balance\_changes\_daily | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_pools\_tokens\_weights](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/pools/balancer_pools_tokens_weights.sql) | Token weights in Balancer’s weighted pools | balancer*\[chain]*pools\_tokens\_weights | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_token\_balance\_changes](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/balances/balancer_token_balance_changes.sql) | All token adds and removes on Balancer pools | balancer*\[chain]*token\_balance\_changes | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_token\_balance\_changes\_daily](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/balances/balancer_token_balance_changes_daily.sql) | Daily Deltas in token balances per pool | balancer*\[chain]*token\_balance\_changes\_daily | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [balancer\_ethereum\_balances](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/balances/ethereum/balancer_ethereum_balances.sql) | Daily running cumulative balance for ERC20 tokens on balancer v1 pools | | Ethereum | \| [balancer\_ethereum\_vebal\_slopes](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/vebal/ethereum/balancer_ethereum_vebal_slopes.sql) | Slope and bias of veBAL per wallet after each balance update | | Ethereum | \| [balancer\_ethereum\_vebal\_balances\_day](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/vebal/ethereum/balancer_ethereum_vebal_balances_day.sql) | Daily balances of veBAL per wallet | | Ethereum | \| [balancer\_ethereum\_vebal\_votes](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/hourly_spellbook/models/_project/balancer/vebal/ethereum/balancer_ethereum_vebal_votes.sql) | Records of votes for Balancer gauges by provider at each voting round | | Ethereum | \| [balancer\_cowswap\_amm\_balances](https://github.com/duneanalytics/spellbook/blob/75d2f0b1afc965564cef9b92f982bbc3590c276b/dbt_subprojects/hourly_spellbook/models/_project/balancer_cowswap_amm/balancer_cowswap_amm_balances.sql) | Daily running cumulative balance for ERC20 tokens on Balancer CoWSwap AMM pools pools | | Arbitrum, Ethereum, Gnosis | \| [labels\_balancer\_v1\_pools](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/dex/models/_projects/balancer/labels/labels_balancer_v1_pools.sql) | Names Balancer v1 pools, based on tokens and weights | labels\_balancer\_v1\_pools\_ethereum | Ethereum | \| [labels\_balancer\_v2\_pools](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/dex/models/_projects/balancer/labels/labels_balancer_v2_pools.sql) | Names Balancer v2 pools, based on tokens and weights. Also returns pool type. | labels\_balancer\_v2\_pools*{{chain}} | Arbitrum, Avalanche, Base, Ethereum, Gnosis, Optimism, Polygon PoS, Polygon ZkEVM | \| [labels\_balancer\_cowswap\_amm\_pools](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/dex/models/_projects/balancer_cowswap_amm/labels_balancer_cowswap_amm_pools.sql) | Names Balancer CoWSwap AMM pools, based on tokens and weights. Also returns pool type. | labels\_balancer\_cowswap\_amm\_pools\_{{chain}} | Arbitrum, Ethereum, Gnosis | \| [labels\_balancer\_v2\_gauges](https://github.com/duneanalytics/spellbook/blob/main/dbt_subprojects/daily_spellbook/models/_sector/labels/addresses/__single_category_labels__/balancer_v2/labels_balancer_v2_gauges.sql) | Names Balancer v2 gauges, based on their respective blockchain and pool | labels\_balancer\_v2\_gauges\_{{chain}} | | ### Contribute to the Magic We invite you to not only explore but also contribute to the magic. As we strive for excellence, collaboration is at the heart of our mission. Your insights, feedback, and contributions are invaluable in shaping the future of Balancer's data analytics. To do so, you can create pull requests to Dune's spellbook or reach out to Balancer Labs' data team directly on [Discord](https://discord.balancer.fi/). **You can leverage our spells with queries such as:** ### 1, 7 and 30 day volume on Balancer ```sql SELECT SUM(amount_usd)/1e6 AS "Volume on Balancer" , 1 AS rn FROM balancer.trades WHERE block_time >= CAST(NOW() AS TIMESTAMP) - INTERVAL '1' DAY UNION ALL SELECT SUM(amount_usd)/1e6 AS "Volume on Balancer" , 2 AS rn FROM balancer.trades WHERE block_time >= CAST(NOW() AS TIMESTAMP) - INTERVAL '7' DAY UNION ALL SELECT SUM(amount_usd)/1e6 AS "Volume on Balancer" , 3 AS rn FROM balancer.trades WHERE block_time >= CAST(NOW() AS TIMESTAMP) - INTERVAL '30' DAY ORDER BY rn ASC ``` ### All swaps on the last 24 hours ```sql SELECT block_date , tx_hash , project_contract_address AS pool_address , token_bought_address , token_bought_amount , token_sold_address , token_sold_amount , tx_from , tx_to , amount_usd , swap_fee FROM balancer.trades WHERE block_time >= now() - interval '24' hour ORDER BY 1 ASC ``` ### Daily TVL by Blockchain ```sql SELECT blockchain , CAST(day AS TIMESTAMP) AS day , sum(protocol_liquidity_usd) AS chain_tvl FROM balancer.liquidity x GROUP BY 1, 2 ORDER BY 2 DESC, 3 DESC ``` ### Current TVL by pool, from highest to lowest ```sql SELECT blockchain , pool_id , pool_symbol , sum(pool_liquidity_usd) AS pool_tvl FROM balancer.liquidity x WHERE day >= current_date GROUP BY 1, 2, 3 ORDER BY 4 DESC ``` ### Daily Liquidity Utilization ```sql WITH swaps AS ( SELECT date_trunc('day', d.block_time) AS day , SUM(amount_usd) AS volume FROM balancer.trades d ) GROUP BY 1 ), total_tvl AS ( SELECT CAST(day as timestamp) as day , SUM(protocol_liquidity_usd) AS tvl FROM balancer.liquidity ) GROUP BY 1 ) SELECT CAST(t.day as timestamp) as day, (s.volume)/(t.tvl) AS liquidity_utilization, FROM total_tvl t LEFT JOIN swaps s ON s.day = t.day ORDER BY 1 ``` ## Overview You can find relevant Abis here: * [Router](./router.md) * [Batch Router](./batch-router.md) * [Buffer Router](./buffer-router.md) * [Composite Liquidity Router](./composite-liquidity-router.md) ## Batch Router ABI ```json [ { "inputs": [ { "internalType": "contract IVault", "name": "vault", "type": "address" }, { "internalType": "contract IWETH", "name": "weth", "type": "address" }, { "internalType": "contract IPermit2", "name": "permit2", "type": "address" }, { "internalType": "string", "name": "routerVersion", "type": "string" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "target", "type": "address" } ], "name": "AddressEmptyCode", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "AddressInsufficientBalance", "type": "error" }, { "inputs": [], "name": "ErrorSelectorNotFound", "type": "error" }, { "inputs": [], "name": "EthTransfer", "type": "error" }, { "inputs": [], "name": "FailedInnerCall", "type": "error" }, { "inputs": [], "name": "InputLengthMismatch", "type": "error" }, { "inputs": [], "name": "InsufficientEth", "type": "error" }, { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, { "inputs": [ { "internalType": "uint8", "name": "bits", "type": "uint8" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "SafeCastOverflowedUintDowncast", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "token", "type": "address" } ], "name": "SafeERC20FailedOperation", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" } ], "name": "SenderIsNotVault", "type": "error" }, { "inputs": [], "name": "SwapDeadline", "type": "error" }, { "inputs": [], "name": "TransientIndexOutOfBounds", "type": "error" }, { "inputs": [], "name": "getSender", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } ], "name": "multicall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "nonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" } ], "internalType": "struct IRouterCommon.PermitApproval[]", "name": "permitBatch", "type": "tuple[]" }, { "internalType": "bytes[]", "name": "permitSignatures", "type": "bytes[]" }, { "components": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint160", "name": "amount", "type": "uint160" }, { "internalType": "uint48", "name": "expiration", "type": "uint48" }, { "internalType": "uint48", "name": "nonce", "type": "uint48" } ], "internalType": "struct IAllowanceTransfer.PermitDetails[]", "name": "details", "type": "tuple[]" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "sigDeadline", "type": "uint256" } ], "internalType": "struct IAllowanceTransfer.PermitBatch", "name": "permit2Batch", "type": "tuple" }, { "internalType": "bytes", "name": "permit2Signature", "type": "bytes" }, { "internalType": "bytes[]", "name": "multicallData", "type": "bytes[]" } ], "name": "permitBatchAndCall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "exactAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", "name": "paths", "type": "tuple[]" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "querySwapExactIn", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsOut", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensOut", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "exactAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", "name": "paths", "type": "tuple[]" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IBatchRouter.SwapExactInHookParams", "name": "params", "type": "tuple" } ], "name": "querySwapExactInHook", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsOut", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensOut", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", "name": "paths", "type": "tuple[]" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "querySwapExactOut", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsIn", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensIn", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", "name": "paths", "type": "tuple[]" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IBatchRouter.SwapExactOutHookParams", "name": "params", "type": "tuple" } ], "name": "querySwapExactOutHook", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsIn", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensIn", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "exactAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", "name": "paths", "type": "tuple[]" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "swapExactIn", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsOut", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensOut", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "exactAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountIn[]", "name": "paths", "type": "tuple[]" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IBatchRouter.SwapExactInHookParams", "name": "params", "type": "tuple" } ], "name": "swapExactInHook", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsOut", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensOut", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", "name": "paths", "type": "tuple[]" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "swapExactOut", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsIn", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensIn", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "components": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "bool", "name": "isBuffer", "type": "bool" } ], "internalType": "struct IBatchRouter.SwapPathStep[]", "name": "steps", "type": "tuple[]" }, { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" } ], "internalType": "struct IBatchRouter.SwapPathExactAmountOut[]", "name": "paths", "type": "tuple[]" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IBatchRouter.SwapExactOutHookParams", "name": "params", "type": "tuple" } ], "name": "swapExactOutHook", "outputs": [ { "internalType": "uint256[]", "name": "pathAmountsIn", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensIn", "type": "address[]" }, { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "version", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ] ``` ## Buffer Router ABI ```json [ { "inputs": [ { "internalType": "contract IVault", "name": "vault", "type": "address" }, { "internalType": "contract IWETH", "name": "weth", "type": "address" }, { "internalType": "contract IPermit2", "name": "permit2", "type": "address" }, { "internalType": "string", "name": "routerVersion", "type": "string" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "target", "type": "address" } ], "name": "AddressEmptyCode", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "AddressInsufficientBalance", "type": "error" }, { "inputs": [], "name": "ErrorSelectorNotFound", "type": "error" }, { "inputs": [], "name": "EthTransfer", "type": "error" }, { "inputs": [], "name": "FailedInnerCall", "type": "error" }, { "inputs": [], "name": "InputLengthMismatch", "type": "error" }, { "inputs": [], "name": "InsufficientEth", "type": "error" }, { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, { "inputs": [ { "internalType": "uint8", "name": "bits", "type": "uint8" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "SafeCastOverflowedUintDowncast", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "token", "type": "address" } ], "name": "SafeERC20FailedOperation", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" } ], "name": "SenderIsNotVault", "type": "error" }, { "inputs": [], "name": "SwapDeadline", "type": "error" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "maxAmountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "maxAmountWrappedIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactSharesToIssue", "type": "uint256" } ], "name": "addLiquidityToBuffer", "outputs": [ { "internalType": "uint256", "name": "amountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "amountWrappedIn", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "maxAmountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "maxAmountWrappedIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactSharesToIssue", "type": "uint256" }, { "internalType": "address", "name": "sharesOwner", "type": "address" } ], "name": "addLiquidityToBufferHook", "outputs": [ { "internalType": "uint256", "name": "amountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "amountWrappedIn", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "getSender", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountWrappedIn", "type": "uint256" }, { "internalType": "uint256", "name": "minIssuedShares", "type": "uint256" } ], "name": "initializeBuffer", "outputs": [ { "internalType": "uint256", "name": "issuedShares", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountWrappedIn", "type": "uint256" }, { "internalType": "uint256", "name": "minIssuedShares", "type": "uint256" }, { "internalType": "address", "name": "sharesOwner", "type": "address" } ], "name": "initializeBufferHook", "outputs": [ { "internalType": "uint256", "name": "issuedShares", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } ], "name": "multicall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "nonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" } ], "internalType": "struct IRouterCommon.PermitApproval[]", "name": "permitBatch", "type": "tuple[]" }, { "internalType": "bytes[]", "name": "permitSignatures", "type": "bytes[]" }, { "components": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint160", "name": "amount", "type": "uint160" }, { "internalType": "uint48", "name": "expiration", "type": "uint48" }, { "internalType": "uint48", "name": "nonce", "type": "uint48" } ], "internalType": "struct IAllowanceTransfer.PermitDetails[]", "name": "details", "type": "tuple[]" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "sigDeadline", "type": "uint256" } ], "internalType": "struct IAllowanceTransfer.PermitBatch", "name": "permit2Batch", "type": "tuple" }, { "internalType": "bytes", "name": "permit2Signature", "type": "bytes" }, { "internalType": "bytes[]", "name": "multicallData", "type": "bytes[]" } ], "name": "permitBatchAndCall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactSharesToIssue", "type": "uint256" } ], "name": "queryAddLiquidityToBuffer", "outputs": [ { "internalType": "uint256", "name": "amountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "amountWrappedIn", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactSharesToIssue", "type": "uint256" } ], "name": "queryAddLiquidityToBufferHook", "outputs": [ { "internalType": "uint256", "name": "amountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "amountWrappedIn", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountWrappedIn", "type": "uint256" } ], "name": "queryInitializeBuffer", "outputs": [ { "internalType": "uint256", "name": "issuedShares", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmountUnderlyingIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmountWrappedIn", "type": "uint256" } ], "name": "queryInitializeBufferHook", "outputs": [ { "internalType": "uint256", "name": "issuedShares", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactSharesToRemove", "type": "uint256" } ], "name": "queryRemoveLiquidityFromBuffer", "outputs": [ { "internalType": "uint256", "name": "removedUnderlyingBalanceOut", "type": "uint256" }, { "internalType": "uint256", "name": "removedWrappedBalanceOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" }, { "internalType": "uint256", "name": "exactSharesToRemove", "type": "uint256" } ], "name": "queryRemoveLiquidityFromBufferHook", "outputs": [ { "internalType": "uint256", "name": "removedUnderlyingBalanceOut", "type": "uint256" }, { "internalType": "uint256", "name": "removedWrappedBalanceOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "version", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ] ``` ## Composite Liquidity Router ABI ```json [ { "inputs": [ { "internalType": "contract IVault", "name": "vault", "type": "address" }, { "internalType": "contract IWETH", "name": "weth", "type": "address" }, { "internalType": "contract IPermit2", "name": "permit2", "type": "address" }, { "internalType": "string", "name": "routerVersion", "type": "string" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "target", "type": "address" } ], "name": "AddressEmptyCode", "type": "error" }, { "inputs": [ { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" } ], "name": "AmountInAboveMax", "type": "error" }, { "inputs": [ { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "amountOut", "type": "uint256" }, { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" } ], "name": "AmountOutBelowMin", "type": "error" }, { "inputs": [ { "internalType": "contract IERC4626", "name": "wrappedToken", "type": "address" } ], "name": "BufferNotInitialized", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "duplicateToken", "type": "address" } ], "name": "DuplicateTokenIn", "type": "error" }, { "inputs": [], "name": "ElementNotFound", "type": "error" }, { "inputs": [], "name": "ErrorSelectorNotFound", "type": "error" }, { "inputs": [], "name": "EthTransfer", "type": "error" }, { "inputs": [], "name": "FailedCall", "type": "error" }, { "inputs": [], "name": "InputLengthMismatch", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "balance", "type": "uint256" }, { "internalType": "uint256", "name": "needed", "type": "uint256" } ], "name": "InsufficientBalance", "type": "error" }, { "inputs": [], "name": "InsufficientEth", "type": "error" }, { "inputs": [], "name": "InvalidAddLiquidityKind", "type": "error" }, { "inputs": [], "name": "InvalidTokenType", "type": "error" }, { "inputs": [], "name": "OperationNotSupported", "type": "error" }, { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, { "inputs": [ { "internalType": "uint8", "name": "bits", "type": "uint8" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "SafeCastOverflowedUintDowncast", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "token", "type": "address" } ], "name": "SafeERC20FailedOperation", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" } ], "name": "SenderIsNotVault", "type": "error" }, { "inputs": [], "name": "SwapDeadline", "type": "error" }, { "inputs": [], "name": "TransientIndexOutOfBounds", "type": "error" }, { "inputs": [ { "internalType": "address[]", "name": "actualTokensOut", "type": "address[]" }, { "internalType": "address[]", "name": "expectedTokensOut", "type": "address[]" } ], "name": "WrongTokensOut", "type": "error" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "enum AddLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct AddLiquidityHookParams", "name": "params", "type": "tuple" }, { "internalType": "bool[]", "name": "wrapUnderlying", "type": "bool[]" } ], "name": "addLiquidityERC4626PoolProportionalHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "enum AddLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct AddLiquidityHookParams", "name": "params", "type": "tuple" }, { "internalType": "bool[]", "name": "wrapUnderlying", "type": "bool[]" } ], "name": "addLiquidityERC4626PoolUnbalancedHook", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "bool[]", "name": "wrapUnderlying", "type": "bool[]" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "addLiquidityProportionalToERC4626Pool", "outputs": [ { "internalType": "uint256[]", "name": "", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "parentPool", "type": "address" }, { "internalType": "address[]", "name": "tokensIn", "type": "address[]" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensToWrap", "type": "address[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "addLiquidityUnbalancedNestedPool", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "enum AddLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct AddLiquidityHookParams", "name": "params", "type": "tuple" }, { "internalType": "address[]", "name": "tokensIn", "type": "address[]" }, { "internalType": "address[]", "name": "tokensToWrap", "type": "address[]" } ], "name": "addLiquidityUnbalancedNestedPoolHook", "outputs": [ { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "bool[]", "name": "wrapUnderlying", "type": "bool[]" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "addLiquidityUnbalancedToERC4626Pool", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "getPermit2", "outputs": [ { "internalType": "contract IPermit2", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getSender", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getVault", "outputs": [ { "internalType": "contract IVault", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getWeth", "outputs": [ { "internalType": "contract IWETH", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } ], "name": "multicall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "nonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" } ], "internalType": "struct IRouterCommon.PermitApproval[]", "name": "permitBatch", "type": "tuple[]" }, { "internalType": "bytes[]", "name": "permitSignatures", "type": "bytes[]" }, { "components": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint160", "name": "amount", "type": "uint160" }, { "internalType": "uint48", "name": "expiration", "type": "uint48" }, { "internalType": "uint48", "name": "nonce", "type": "uint48" } ], "internalType": "struct IAllowanceTransfer.PermitDetails[]", "name": "details", "type": "tuple[]" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "sigDeadline", "type": "uint256" } ], "internalType": "struct IAllowanceTransfer.PermitBatch", "name": "permit2Batch", "type": "tuple" }, { "internalType": "bytes", "name": "permit2Signature", "type": "bytes" }, { "internalType": "bytes[]", "name": "multicallData", "type": "bytes[]" } ], "name": "permitBatchAndCall", "outputs": [ { "internalType": "bytes[]", "name": "", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "bool[]", "name": "wrapUnderlying", "type": "bool[]" }, { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryAddLiquidityProportionalToERC4626Pool", "outputs": [ { "internalType": "uint256[]", "name": "", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "parentPool", "type": "address" }, { "internalType": "address[]", "name": "tokensIn", "type": "address[]" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensToWrap", "type": "address[]" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryAddLiquidityUnbalancedNestedPool", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "bool[]", "name": "wrapUnderlying", "type": "bool[]" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryAddLiquidityUnbalancedToERC4626Pool", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "bool[]", "name": "unwrapWrapped", "type": "bool[]" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryRemoveLiquidityProportionalFromERC4626Pool", "outputs": [ { "internalType": "uint256[]", "name": "", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "parentPool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "address[]", "name": "tokensOut", "type": "address[]" }, { "internalType": "address[]", "name": "tokensToUnwrap", "type": "address[]" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryRemoveLiquidityProportionalNestedPool", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "enum RemoveLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct RemoveLiquidityHookParams", "name": "params", "type": "tuple" }, { "internalType": "bool[]", "name": "unwrapWrapped", "type": "bool[]" } ], "name": "removeLiquidityERC4626PoolProportionalHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "bool[]", "name": "unwrapWrapped", "type": "bool[]" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "removeLiquidityProportionalFromERC4626Pool", "outputs": [ { "internalType": "uint256[]", "name": "", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "parentPool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "address[]", "name": "tokensOut", "type": "address[]" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "address[]", "name": "tokensToUnwrap", "type": "address[]" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "removeLiquidityProportionalNestedPool", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "enum RemoveLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct RemoveLiquidityHookParams", "name": "params", "type": "tuple" }, { "internalType": "address[]", "name": "tokensOut", "type": "address[]" }, { "internalType": "address[]", "name": "tokensToUnwrap", "type": "address[]" } ], "name": "removeLiquidityProportionalNestedPoolHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "version", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ] ``` ## Router ABI ```json [ { "inputs": [ { "internalType": "contract IVault", "name": "vault", "type": "address" }, { "internalType": "contract IWETH", "name": "weth", "type": "address" }, { "internalType": "contract IPermit2", "name": "permit2", "type": "address" }, { "internalType": "string", "name": "routerVersion", "type": "string" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "target", "type": "address" } ], "name": "AddressEmptyCode", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "account", "type": "address" } ], "name": "AddressInsufficientBalance", "type": "error" }, { "inputs": [], "name": "ErrorSelectorNotFound", "type": "error" }, { "inputs": [], "name": "EthTransfer", "type": "error" }, { "inputs": [], "name": "FailedInnerCall", "type": "error" }, { "inputs": [], "name": "InputLengthMismatch", "type": "error" }, { "inputs": [], "name": "InsufficientEth", "type": "error" }, { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, { "inputs": [ { "internalType": "uint8", "name": "bits", "type": "uint8" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "SafeCastOverflowedUintDowncast", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "token", "type": "address" } ], "name": "SafeERC20FailedOperation", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" } ], "name": "SenderIsNotVault", "type": "error" }, { "inputs": [], "name": "SwapDeadline", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "addLiquidityCustom", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "enum AddLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IRouterCommon.AddLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "addLiquidityHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "addLiquidityProportional", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "addLiquiditySingleTokenExactOut", "outputs": [ { "internalType": "uint256", "name": "amountIn", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "addLiquidityUnbalanced", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "donate", "outputs": [], "stateMutability": "payable", "type": "function" }, { "inputs": [], "name": "getPermit2", "outputs": [ { "internalType": "contract IPermit2", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getSender", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getWeth", "outputs": [ { "internalType": "contract IWETH", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20[]", "name": "tokens", "type": "address[]" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "initialize", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20[]", "name": "tokens", "type": "address[]" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IRouter.InitializeHookParams", "name": "params", "type": "tuple" } ], "name": "initializeHook", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } ], "name": "multicall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "nonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" } ], "internalType": "struct IRouterCommon.PermitApproval[]", "name": "permitBatch", "type": "tuple[]" }, { "internalType": "bytes[]", "name": "permitSignatures", "type": "bytes[]" }, { "components": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint160", "name": "amount", "type": "uint160" }, { "internalType": "uint48", "name": "expiration", "type": "uint48" }, { "internalType": "uint48", "name": "nonce", "type": "uint48" } ], "internalType": "struct IAllowanceTransfer.PermitDetails[]", "name": "details", "type": "tuple[]" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "sigDeadline", "type": "uint256" } ], "internalType": "struct IAllowanceTransfer.PermitBatch", "name": "permit2Batch", "type": "tuple" }, { "internalType": "bytes", "name": "permit2Signature", "type": "bytes" }, { "internalType": "bytes[]", "name": "multicallData", "type": "bytes[]" } ], "name": "permitBatchAndCall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryAddLiquidityCustom", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "enum AddLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IRouterCommon.AddLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "queryAddLiquidityHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryAddLiquidityProportional", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryAddLiquiditySingleTokenExactOut", "outputs": [ { "internalType": "uint256", "name": "amountIn", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryAddLiquidityUnbalanced", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryRemoveLiquidityCustom", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "enum RemoveLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IRouterCommon.RemoveLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "queryRemoveLiquidityHook", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryRemoveLiquidityProportional", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" } ], "name": "queryRemoveLiquidityRecovery", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" } ], "name": "queryRemoveLiquidityRecoveryHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryRemoveLiquiditySingleTokenExactIn", "outputs": [ { "internalType": "uint256", "name": "amountOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "queryRemoveLiquiditySingleTokenExactOut", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "enum SwapKind", "name": "kind", "type": "uint8" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "amountGiven", "type": "uint256" }, { "internalType": "uint256", "name": "limit", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IRouter.SwapSingleTokenHookParams", "name": "params", "type": "tuple" } ], "name": "querySwapHook", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "exactAmountIn", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "querySwapSingleTokenExactIn", "outputs": [ { "internalType": "uint256", "name": "amountCalculated", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "querySwapSingleTokenExactOut", "outputs": [ { "internalType": "uint256", "name": "amountCalculated", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "removeLiquidityCustom", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "enum RemoveLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IRouterCommon.RemoveLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "removeLiquidityHook", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "removeLiquidityProportional", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" } ], "name": "removeLiquidityRecovery", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" } ], "name": "removeLiquidityRecoveryHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "removeLiquiditySingleTokenExactIn", "outputs": [ { "internalType": "uint256", "name": "amountOut", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "removeLiquiditySingleTokenExactOut", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "exactAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "minAmountOut", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "swapSingleTokenExactIn", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "exactAmountOut", "type": "uint256" }, { "internalType": "uint256", "name": "maxAmountIn", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "name": "swapSingleTokenExactOut", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "enum SwapKind", "name": "kind", "type": "uint8" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "amountGiven", "type": "uint256" }, { "internalType": "uint256", "name": "limit", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct IRouter.SwapSingleTokenHookParams", "name": "params", "type": "tuple" } ], "name": "swapSingleTokenHook", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "version", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ] ``` ## Unbalanced Add Via Swap Router ABI ```json [ { "inputs": [ { "internalType": "contract IVault", "name": "vault", "type": "address" }, { "internalType": "contract IWETH", "name": "weth", "type": "address" }, { "internalType": "contract IPermit2", "name": "permit2", "type": "address" }, { "internalType": "string", "name": "routerVersion", "type": "string" } ], "stateMutability": "nonpayable", "type": "constructor" }, { "inputs": [ { "internalType": "address", "name": "target", "type": "address" } ], "name": "AddressEmptyCode", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, { "internalType": "uint256", "name": "maxAdjustableAmount", "type": "uint256" } ], "name": "AmountInAboveMaxAdjustableAmount", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "amountIn", "type": "uint256" }, { "internalType": "uint256", "name": "exactAmount", "type": "uint256" } ], "name": "AmountInDoesNotMatchExact", "type": "error" }, { "inputs": [], "name": "ErrorSelectorNotFound", "type": "error" }, { "inputs": [], "name": "EthTransfer", "type": "error" }, { "inputs": [], "name": "FailedCall", "type": "error" }, { "inputs": [], "name": "InputLengthMismatch", "type": "error" }, { "inputs": [ { "internalType": "uint256", "name": "balance", "type": "uint256" }, { "internalType": "uint256", "name": "needed", "type": "uint256" } ], "name": "InsufficientBalance", "type": "error" }, { "inputs": [], "name": "InsufficientEth", "type": "error" }, { "inputs": [ { "internalType": "contract IERC20", "name": "token", "type": "address" } ], "name": "InsufficientPayment", "type": "error" }, { "inputs": [], "name": "NotTwoTokenPool", "type": "error" }, { "inputs": [], "name": "OperationNotSupported", "type": "error" }, { "inputs": [], "name": "ReentrancyGuardReentrantCall", "type": "error" }, { "inputs": [ { "internalType": "uint8", "name": "bits", "type": "uint8" }, { "internalType": "uint256", "name": "value", "type": "uint256" } ], "name": "SafeCastOverflowedUintDowncast", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "token", "type": "address" } ], "name": "SafeERC20FailedOperation", "type": "error" }, { "inputs": [ { "internalType": "address", "name": "sender", "type": "address" } ], "name": "SenderIsNotVault", "type": "error" }, { "inputs": [], "name": "SwapDeadline", "type": "error" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "enum AddLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct AddLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "addLiquidityHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "components": [ { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "contract IERC20", "name": "exactToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmount", "type": "uint256" }, { "internalType": "uint256", "name": "maxAdjustableAmount", "type": "uint256" }, { "internalType": "bytes", "name": "addLiquidityUserData", "type": "bytes" }, { "internalType": "bytes", "name": "swapUserData", "type": "bytes" } ], "internalType": "struct IUnbalancedAddViaSwapRouter.AddLiquidityAndSwapParams", "name": "params", "type": "tuple" } ], "name": "addLiquidityUnbalanced", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "components": [ { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "contract IERC20", "name": "exactToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmount", "type": "uint256" }, { "internalType": "uint256", "name": "maxAdjustableAmount", "type": "uint256" }, { "internalType": "bytes", "name": "addLiquidityUserData", "type": "bytes" }, { "internalType": "bytes", "name": "swapUserData", "type": "bytes" } ], "internalType": "struct IUnbalancedAddViaSwapRouter.AddLiquidityAndSwapParams", "name": "operationParams", "type": "tuple" } ], "internalType": "struct IUnbalancedAddViaSwapRouter.AddLiquidityAndSwapHookParams", "name": "hookParams", "type": "tuple" } ], "name": "addLiquidityUnbalancedHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "getPermit2", "outputs": [ { "internalType": "contract IPermit2", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getSender", "outputs": [ { "internalType": "address", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getVault", "outputs": [ { "internalType": "contract IVault", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [], "name": "getWeth", "outputs": [ { "internalType": "contract IWETH", "name": "", "type": "address" } ], "stateMutability": "view", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20[]", "name": "tokens", "type": "address[]" }, { "internalType": "uint256[]", "name": "exactAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct InitializeHookParams", "name": "params", "type": "tuple" } ], "name": "initializeHook", "outputs": [ { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "bytes[]", "name": "data", "type": "bytes[]" } ], "name": "multicall", "outputs": [ { "internalType": "bytes[]", "name": "results", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "address", "name": "owner", "type": "address" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "amount", "type": "uint256" }, { "internalType": "uint256", "name": "nonce", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" } ], "internalType": "struct IRouterCommon.PermitApproval[]", "name": "permitBatch", "type": "tuple[]" }, { "internalType": "bytes[]", "name": "permitSignatures", "type": "bytes[]" }, { "components": [ { "components": [ { "internalType": "address", "name": "token", "type": "address" }, { "internalType": "uint160", "name": "amount", "type": "uint160" }, { "internalType": "uint48", "name": "expiration", "type": "uint48" }, { "internalType": "uint48", "name": "nonce", "type": "uint48" } ], "internalType": "struct IAllowanceTransfer.PermitDetails[]", "name": "details", "type": "tuple[]" }, { "internalType": "address", "name": "spender", "type": "address" }, { "internalType": "uint256", "name": "sigDeadline", "type": "uint256" } ], "internalType": "struct IAllowanceTransfer.PermitBatch", "name": "permit2Batch", "type": "tuple" }, { "internalType": "bytes", "name": "permit2Signature", "type": "bytes" }, { "internalType": "bytes[]", "name": "multicallData", "type": "bytes[]" } ], "name": "permitBatchAndCall", "outputs": [ { "internalType": "bytes[]", "name": "", "type": "bytes[]" } ], "stateMutability": "payable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "maxAmountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "minBptAmountOut", "type": "uint256" }, { "internalType": "enum AddLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct AddLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "queryAddLiquidityHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" }, { "internalType": "uint256", "name": "bptAmountOut", "type": "uint256" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "components": [ { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "contract IERC20", "name": "exactToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmount", "type": "uint256" }, { "internalType": "uint256", "name": "maxAdjustableAmount", "type": "uint256" }, { "internalType": "bytes", "name": "addLiquidityUserData", "type": "bytes" }, { "internalType": "bytes", "name": "swapUserData", "type": "bytes" } ], "internalType": "struct IUnbalancedAddViaSwapRouter.AddLiquidityAndSwapParams", "name": "params", "type": "tuple" } ], "name": "queryAddLiquidityUnbalanced", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "components": [ { "internalType": "uint256", "name": "exactBptAmountOut", "type": "uint256" }, { "internalType": "contract IERC20", "name": "exactToken", "type": "address" }, { "internalType": "uint256", "name": "exactAmount", "type": "uint256" }, { "internalType": "uint256", "name": "maxAdjustableAmount", "type": "uint256" }, { "internalType": "bytes", "name": "addLiquidityUserData", "type": "bytes" }, { "internalType": "bytes", "name": "swapUserData", "type": "bytes" } ], "internalType": "struct IUnbalancedAddViaSwapRouter.AddLiquidityAndSwapParams", "name": "operationParams", "type": "tuple" } ], "internalType": "struct IUnbalancedAddViaSwapRouter.AddLiquidityAndSwapHookParams", "name": "params", "type": "tuple" } ], "name": "queryAddLiquidityUnbalancedHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsIn", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "enum RemoveLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct RemoveLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "queryRemoveLiquidityHook", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" } ], "name": "queryRemoveLiquidityRecoveryHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "enum SwapKind", "name": "kind", "type": "uint8" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "amountGiven", "type": "uint256" }, { "internalType": "uint256", "name": "limit", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct SwapSingleTokenHookParams", "name": "params", "type": "tuple" } ], "name": "querySwapHook", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" }, { "internalType": "uint256", "name": "maxBptAmountIn", "type": "uint256" }, { "internalType": "enum RemoveLiquidityKind", "name": "kind", "type": "uint8" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct RemoveLiquidityHookParams", "name": "params", "type": "tuple" } ], "name": "removeLiquidityHook", "outputs": [ { "internalType": "uint256", "name": "bptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" }, { "internalType": "bytes", "name": "returnData", "type": "bytes" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "uint256", "name": "exactBptAmountIn", "type": "uint256" }, { "internalType": "uint256[]", "name": "minAmountsOut", "type": "uint256[]" } ], "name": "removeLiquidityRecoveryHook", "outputs": [ { "internalType": "uint256[]", "name": "amountsOut", "type": "uint256[]" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [ { "components": [ { "internalType": "address", "name": "sender", "type": "address" }, { "internalType": "enum SwapKind", "name": "kind", "type": "uint8" }, { "internalType": "address", "name": "pool", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenIn", "type": "address" }, { "internalType": "contract IERC20", "name": "tokenOut", "type": "address" }, { "internalType": "uint256", "name": "amountGiven", "type": "uint256" }, { "internalType": "uint256", "name": "limit", "type": "uint256" }, { "internalType": "uint256", "name": "deadline", "type": "uint256" }, { "internalType": "bool", "name": "wethIsEth", "type": "bool" }, { "internalType": "bytes", "name": "userData", "type": "bytes" } ], "internalType": "struct SwapSingleTokenHookParams", "name": "params", "type": "tuple" } ], "name": "swapSingleTokenHook", "outputs": [ { "internalType": "uint256", "name": "", "type": "uint256" } ], "stateMutability": "nonpayable", "type": "function" }, { "inputs": [], "name": "version", "outputs": [ { "internalType": "string", "name": "", "type": "string" } ], "stateMutability": "view", "type": "function" }, { "stateMutability": "payable", "type": "receive" } ] ``` ## Arbitrum Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## Avalanche Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Authorization Contracts #### Gauges and Governance ## Base Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## Gnosis Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## HyperEVM Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## Mainnet Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## Monad Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## Optimism Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Authorization Contracts #### Gauges and Governance ## Plasma Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## Polygon Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Authorization Contracts #### Gauges and Governance ## Sepolia Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Authorization Contracts #### Gauges and Governance ## XLayer Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Hooks and Peripherals #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Routers #### Authorization Contracts #### Gauges and Governance ## Zkevm Deployment Addresses ### Active Contracts \::: info More Details For more information on specific deployments as well as changelogs for different contract versions, please see the [deployment tasks](https://github.com/balancer/balancer-deployments/tree/master/v3/tasks). \::: #### Core Contracts #### Routers #### Pool Factories #### Authorization Contracts #### Gauges and Governance ### Deprecated Contracts These deployments were in use at some point, and may still be in active operation, for example in the case of pools created with old factories. In general it's better to interact with newer versions when possible. \::: warning Note If you can only find the contract you are looking for in the deprecated section and it is not an old pool, try checking the deployments tasks to find it or ask in the Discord before using a deprecated contract. \::: #### Core Contracts #### Pool Factories #### Authorization Contracts #### Gauges and Governance ## V3 Pool Deployments Here is the current list of official Balancer Pool deployments: ![V3 Pools](/images/pool-deployments.svg) Each entry has the protocol version (V2 or V3), the pool type name, the deployment date (i.e., the date on the deployment task: not necessarily when the contract was actually deployed on-chain), the pool version (e.g., v1, v2, v3, ...), and whether it is active or deprecated. In general, only the latest version will be "active." For instance: V3 Weighted Pool 2024-12-05 (v1, active) From this, it is easy to navigate to the corresponding task in the deployments repo: [/v3/tasks/20241205-v3-weighted-pool/readme.md](https://github.com/balancer/balancer-deployments/blob/master/v3/tasks/20241205-v3-weighted-pool/readme.md) Similarly, v2 tasks are under /v2. Each protocol version has /tasks and /deprecated directories for active and deprecated deployments, respectively. In addition to a description of the contract, this readme contains a link to the commit in the repo it was produced from (at least for V3 deployments), and links to the deployed contract addresses on all networks. ## V3 Router Deployments Here is the current list of official Balancer Router deployments: ![V3 Pools](/images/router-deployments.svg) Each entry has the protocol version (V2 or V3), the router name, the deployment date (i.e., the date on the deployment task: not necessarily when the contract was actually deployed on-chain), the router version (e.g., v1, v2, v3, ...), and whether it is active or deprecated. In general, only the latest version will be "active." For instance, here is the **original** V3 Router (since deprecated and replaced by V2): V3 Router 2024-12-05 (v1, deprecated) From this, it is easy to navigate to the corresponding task in the deployments repo: [/v3/deprecated/20241205-v3-router/readme.md](https://github.com/balancer/balancer-deployments/blob/master/v3/deprecated/20241205-v3-router/readme.md) Similarly, v2 tasks are under /v2. Each protocol version has /tasks and /deprecated directories for active and deprecated deployments, respectively. The current V3 Router is: V3 Router V2 2025-03-07 (v2, active) The corresponding task in the deployments repo is: [/v3/tasks/20250307-v3-router-v2/readme.md](https://github.com/balancer/balancer-deployments/blob/master/v3/tasks/20250307-v3-router-v2/readme.md) Since it is "active," we look under /tasks. In addition to a description of the contract, this readme contains a link to the commit in the repo it was produced from (at least for V3 deployments), and links to the deployed contract addresses on all networks. ## Boosted Pools Boosted Pools represent a significant evolution in DeFi yield generation, combining the benefits of DEX liquidity provision and lending market yields in a single position. These pools maximize capital efficiency while maintaining a simple, passive user experience. ### Overview Boosted Pools in Balancer v3 enable: * 100% utilization of pool liquidity in lending markets * Simultaneous earning from swap fees and lending yields * Gas-efficient swaps through an innovative buffer system * Simple UX with permissionless entry and exit ![Boosted Pool Overview](/images/boostedTokens.png) ### How Boosted Pools Work #### Architecture Boosted Pools deploy 100% of liquidity into yield-generating strategies (e.g., Aave) while maintaining full swap functionality through a buffer system: 1. **Underlying Assets**: Users deposit base assets (e.g., USDC, DAI) 2. **Yield Generation**: Assets are automatically converted to yield-bearing tokens (e.g., aUSDC, aDAI) 3. **Buffer System**: Facilitates efficient swaps between base assets \::: tip Buffer Mechanism Buffers are simple two-token systems that: * Hold small amounts of both base and yield-bearing tokens * Enable gas-efficient swaps without external calls for most transactions * Automatically rebalance when needed for larger swaps \::: #### Key Benefits For Liquidity Providers: * Earn both swap fees and lending yields * Simplified position management * Permissionless entry and exit * Full exposure to yield-bearing assets For Traders: * Seamless swaps between base assets * Gas-efficient transactions * Deep, reliable liquidity * No additional complexity ### Implementation Boosted Pools in v3 improve upon previous versions by: * Eliminating nested pool structures * Introducing efficient buffer mechanics * Optimizing gas costs for all operations * Maintaining 100% capital efficiency \::: info Security Buffers implement simple, limited logic adjacent to the vault, significantly reducing potential security risks compared to previous implementations. \::: ### Use Cases Boosted Pools are ideal for: * Stablecoin liquidity provision * Passive yield optimization strategies * High-volume trading pairs * Long-term liquidity deployment ### Additional Resources * [Buffer Documentation](../../../concepts/explore-available-balancer-pools/boosted-pool.md) * [Buffer Documentation](../../../concepts/vault/buffer.md) * [Pool Creation Guide](../../balancer-v3/pool-creation.md) ## Hosting of Liquid Staking and Restaking Tokens Balancer is the most attuned decentralised financial technology layer to host yield-bearing assets. Yield-bearing tokens such as LSTs and interest-bearing stablecoins offer an additional layer of efficiency compared to their vanilla counterparts. These tokenised assets allow users to gain exposure to both on-chain and off-chain interest rates, compounded within a single token. Compared to traditional AMM design, Balancer uses a variety of new concepts to leverage yield-bearing token liquidity while offering optimal results to liquidity providers (LPs) and traders by optimizing trade routes and exchange rates between assets. This is achieved by utilizing three core components: 1. [Rate provider](../rate-providers.md) technology 2. [Core Pool Dynamics](../core-pools.md) 3. [Boosted pools](../products/boostedpools.md) ##### Rate provider technology By utilizing [rate provider](../rate-providers.md) technology, Balancer guarantees optimal swaps for traders by quoting an on-chain rate of the yield-bearing asset. As a result a trader gets the most up-to-date quote and trading amount when routed through Balancer while LPs get more trading volume by optimizing and rebalancing the token composition in stableswap pools. ##### Revenue Share Model: Core Pool Dynamics To align token emissions with asset performance, Balancer governance introduced the [core pool framework](../core-pools.md). In short, if a pool consists of at least 50% yield-bearing assets it qualifies for receiving a share of the fees that the DAO collects on secondary layers such as voting markets which then increase token emissions to pools. ##### Boosted Pools With Balancer v3, you have the opportunity to combine your yield-bearing asset with boosted pool tokens to maximize yield for liquidity providers. Balancers [vault architecture](../../../concepts/vault/) guarantees deep liquidity out of the box. This is an optional but additional attractive feature that can be leveraged when onboarding your token to Balancer. ### Yield-Bearing Token Onboarding Do you want to onboard your yield-bearing token to Balancer and leverage its core advantages? You have several options on how to onboard your liquidity depending on which Balancer deployment you would like to choose: * [Go here](/partner-onboarding/balancer-v2/onboard-yb-token) if you want to onboard your liquidity to Balancer v2 and take part of the core pool flywheel. Much of this is the same for v3 (LM incentives live permanently on v2). * Explore [Gyroscope pools](https://app.gyro.finance/) for highly customized trading curves utilizing Balancer v2 (and v3) at the core. ## Resources If you want to learn more about these concepts in more detail, follow these in-depth articles for more information * Articles about YB tokens * [Best of Both: Stability and Yield with Frax v3](https://beefy.com/articles/frax/) * [Balancer Integrates Chainlink Price Feeds](https://medium.com/balancer-protocol/balancer-integrates-chainlink-price-feeds-to-help-secure-staked-eth-composable-stable-pools-c649d8181510) * [Elliptical Concentrated Liquidity](https://medium.com/balancer-protocol/built-on-balancer-elliptical-concentrated-liquidity-77f289d346f9) * [Inside Balancer Contracts — A composable symphony](https://medium.com/balancer-protocol/inside-balancer-contracts-a-composable-symphony-1-229f6e90224d) * Twitter Threads about YB Tokens * [Yield Bearing Stablecoins](https://twitter.com/Balancer/status/1752319055821000922) * [Liquid Restaking Tokens - Ether Fi](https://x.com/Balancer/status/1750541715457589455?s=20) * [LRTs](https://x.com/Balancer/status/1749779120450601256?s=20) * [LPDs on Balancer](https://x.com/Balancer/status/1760673085131518220?s=20) * [Points On Balancer](https://x.com/Balancer/status/1759582409526521859?s=20) ### Overview The simplistic beauty of the Vault’s architecture is that it supercharges the ability to build a fully functioning and integrated AMM. Stable coin pools can fully leverage deep liquidity on Balancer by tapping into deep liquidity upon pool creation. Innovative products like customized concentrated liquidity pools allow for highly efficient trading pairs. Generally speaking, stable pools are designed to host assets that are either expected to consistently swap at near parity, or at a known exchange rate. Therefore, stable pools are ideal for hosting pegged tokens such as DAI USDC and USDT or synthetic assets like renBTC, sBTC and WBTC. \::: tip Want to deep dive into stable math? Check out our [stable math docs](../../../concepts/explore-available-balancer-pools/stable-pool/stable-math.md)! \::: Balancer offers different pool configurations for hosting stable coin liquidity: 1. Stable Pools 2. Composable Stable Pools 3. Composable Stable Pools with rate providers 4. Customized stable pools implementing their own trading curves ### Customized liquidity Curves: Elliptical concentrated liquidity with Gyroscope Pools Composable stable pools aren’t the only pools that harness rate provider technology to accurately account for YB liquidity, this tech also serves as a powerful mechanism for external developers who are developing their own invariants. One prominent example is [Gyroscopes E-CLP invariant.](https://twitter.com/GyroStable/status/1727366719097000060) In addition to asymmetric concentrated liquidity, E-CLPs leverage rate provider technology. [Rate providers](../../../concepts/core-concepts/rate-providers.md) make yield-bearing asset pools more efficient by: * Automating liquidity management * Mitigating LVR ## Governance Tokenomics :::info Single Asset staking is an outdated governance tokenomics model that reduces a tokens liquidity, increases token volatility and slippage, Incentivizes mercenary capital, and creates fragmented and expensive incentive programs. ve8020 combines the flexibility of asymmetric weighted pools with the efficiency of vote-escrowed mechanics to unlock the next evolution of DAO Governance tokenomics. ::: ### Challenges surrounding Governance Tokenomics Single Asset staking is an outdated governance tokenomics model that reduces a tokens liquidity, increases token volatility/slippage, Incentivizes mercenary capital, and creates fragmented and expensive incentive programs. ### The ve8020 Initiative as a Solution for Governance Tokenomics ve8020 combines the flexibility of asymmetric weighted pools, with the efficiency of vote-escrowed mechanics to unlock the next evolution of Governance tokenomics to unlock deep Liquidity, asymmetric asset exposure with minimised IL, and highly efficient incentive programs. ### Increase Your Token Liquidity Due to incentivising single-staked positions and removing a majority of the token supply available for trading, all single-sided staked protocols run into a token liquidity problem. ve8020 will directly deepen your protocol’s native token via the integration of an 8020 pool token for governance. #### Drawbacks of single-staked tokenomics and reduced liquidity While incentivising staking successfully increases governance participation, it also (un)effectively reduces the circulating supply available for swaps. As a result, a smaller portion of capital is supplied to liquidity pools and this lack of liquidity leads to a number of issues. * Increased slippage * Inability to facilitate larger trades * Higher price volatility for the underlying tokens #### Benefits of ve8020 for increasing token liquidity Harnessing a liquidity pool token as the governance token, the 8020 model circumnavigates the issue of locking single-staked assets liquidity away. Instead of staking the token itself, users stake the pool token, allowing the underlying tokens to actively participate in swaps. For the first time in governance history, as the sum of staked governance tokens grow, the available trading liquidity increases rather than decreases. Additionally, via combining this with vote escrowed mechanics, a protocol ensures liquidity is available for long time durations. * Reduced slippage * Ability to facilitate large trades * Lower price volatility #### Balancer's BAL tokenomics Case Study By addressing the limitations of traditional single-sided staking, the 8020 model fosters an ecosystem where liquidity thrives, enabling smoother and more efficient trading experiences for users. For reference, at the time of writing, Balancer’s 8020 governance pool hosts a TVL of [over $145m](https://app.balancer.fi/#/ethereum/pool/0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014), leading to BAL being one of the most liquid tokens in DeFi. ### Unlock highly efficient incentive programs Unlike a single staking model which requires DAOs to direct incentives at other positions to ensure liquidity is available for swaps, a ve8020 model ensures a SINGLE, concentrated source for protocols to direct incentives at. Combined with swap fees, core pools, and BAL liquidity flywheels, this unlocks an extremely efficient incentive program for protocols. #### Traditional single staked position inefficiencies for protocol incentive programs With the need to facilitate swaps, the traditional single-sided model necessitates incentives for both the staking pool and other liquidity pools. By integrating the governance token into an 8020 liquidity pool, the need to split and direct incentives to other DEXs/CEXs is eliminated. Incentives can be directed to one place offering users a more enticing position. #### Benefits of ve8020 in optimizing incentive programs In essence, the 8020 model consolidates all incentives, removing the need to fractionalize liquidity across multiple markets. As a result token supply is concentrated in one primary pool, increasing liquidity deposits, vastly reducing slippage and offering a simpler, more cost-effective incentivization program. But that’s not all, by utilising a liquidity pool BPT for governance an additional stream of incentives automatically presents itself — Swap Fees. A portion of these fees flow to liquidity providers as an incentive for providing liquidity. Additionally, Balancer implements an 8020 Launchpad/ core pool models that can recycle protocol fees back into the pool. Check out the pages below to find out more. * Liquidity Mining * 8020 Launchpad * Core Pool Status ##### Radiant Capital Case Study 28 days after adopting the 8020 model, Radiant increased token liquidity to almost $43M, facilitated 44,046 swaps and generated $644,744 in swap fee. To date, the radiant 8020 LP hosts over $73 million in liquidity with Liquidity Providers earning an additional $1.7 million in swap fees that would never have been possible with a single-staked model. ### Offer LPs Asymmetric Upside with minimal IL Ensure investors still have asymmetric exposure to your underlying native token while harnessing the many benefits of an 80/20 pool. #### Drawbacks of utilizing traditional 50/50 LP positions for governance tokenomics A 50/50 pool is generally avoided for this purpose as it offers limited exposure to the native token while posing increased IL potential should the native token appreciate substantially. While there is still some inherent risk of impermanent loss, as illustrated in the figure below, the 80/20 split offers substantially reduced exposure to IL, while retaining the benefits of deep liquidity and exposure to the base token ![80 20 IL Curve](/images/8020.png) #### Benefits of 80/20 token exposure By striking the right balance between token composition, the 8020 model maximises exposure to the underlying native token with liquidity providers enjoying the advantages of deep liquidity, asymmetric exposure to the base token, and minimised impermanent loss. ### Enter a well-connected and welcoming ecosystem By utilising a ve8020 governance position, protocols enter a budding collective of DAOs known as the 8020 Initiative. This collective of notable protocols have helped amplify all new participants entering the ecosystem. Balancer will also provide comms, host twitter spaces, and create in-depth content in relation to your protocol / governance tokenomics. Below are a few of our successful 8020 ecosystem participants: * [Radiant Capital](https://app.radiant.capital/) * [Alchemix](https://alchemix.fi/) * [Timeless](https://timelessfi.com/) * [Beethoven-X](https://beets.fi/) * [Aave](https://app.aave.com/) ### Onboarding ve8020 onboarding requires the creation of a weighted pool with a vote escrowed position on top. Protocols only need to incentivize the locked position to ensure long-term liquidity, with a Balancer grant there is a possibility to boost these incentives further. There are also means to generate liquidity mining incentives LP on Balancer. - Core Pool Designation. \::: info Follow our Balancer v2 [Onboarding Guide](../../balancer-v2/v2-overview.md) \::: ### Resources If you want to learn more about these concepts in more detail, follow these in-depth articles for more information * Articles about ve8020 tokenomics * [The 8020 Initiative](https://medium.com/balancer-protocol/the-8020-initiative-64a7a6cab976) * [Balancer Grants ve8020 Launchpad](https://medium.com/balancer-protocol/introducing-balancers-80-20bpt-launchpad-f5e4ffdb3511) * Twitter Threads about ve8020 * [Radiant: A Case Study](https://x.com/Balancer/status/1654557833621323778?s=20) * [80/20 - An Overview](https://x.com/Balancer/status/1681319744442626048?s=20) * [Aave Case Study](https://x.com/Balancer/status/1663576105155170305) * Analytics resources * [ve8020 Dune Dashboard](https://dune.com/balancer/8020-initiative) * [Uniswap Migration tool](https://github.com/alchemix-finance/migrate2balancer)