Balancer V3 Architecture Overview
Balancer V3 is a complete rewrite of the Balancer protocol, featuring a singleton Vault design with transient accounting for gas-efficient operations.
Core Architecture
Singleton Vault Pattern
All liquidity in Balancer V3 is managed by a single Vault contract:
code
┌─────────────────────────────────────────────────────────────┐ │ VAULT │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │ │ Pool A │ │ Pool B │ │ Pool C │ │ │ │ (Weighted) │ │ (Stable) │ │ (Custom) │ │ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │ │ │ Token Reserves: All ERC20 balances held centrally │ │ Pool Accounting: Per-pool balance tracking │ └─────────────────────────────────────────────────────────────┘
Contract Hierarchy
| Contract | Purpose |
|---|---|
| Vault | Main entry point. Core swap/liquidity operations. Inherits VaultCommon + Proxy pattern. |
| VaultExtension | Less frequently used functions (pool registration, queries). Delegate-called via Proxy. |
| VaultAdmin | Permissioned administrative functions. |
| Router | User-facing entry point. Handles ETH, Permit2, unlocking. |
Key Design Principles
- •Transient Accounting: Tokens aren't transferred during operation - only deltas tracked
- •Unlock Pattern: All operations happen within an
unlock()callback - •Pools as External Contracts: Pools contain only math logic, no token balances
- •Hooks: Extensibility via optional hook contracts per pool
Transient Accounting Model
Instead of transferring tokens on each swap, V3 tracks credits and debts:
solidity
// During an unlock session:
// 1. Operations create credits (tokens owed TO user) and debts (tokens owed BY user)
// 2. At session end, all deltas must net to zero
// 3. Only then do actual token transfers occur
function unlock(bytes calldata data) external transient returns (bytes memory result) {
// Callback to msg.sender with data
return (msg.sender).functionCall(data);
}
// The transient modifier ensures:
// - Sets isUnlocked = true
// - After callback, checks nonZeroDeltaCount == 0
// - Reverts with BalanceNotSettled() if unsettled
Credit/Debt Functions
solidity
// Give credit to caller (tokens owed TO them) function _supplyCredit(IERC20 token, uint256 amount) internal; // Take debt from caller (tokens owed BY them) function _takeDebt(IERC20 token, uint256 amount) internal; // Settle debt by transferring tokens to Vault function settle(IERC20 token, uint256 amountHint) external returns (uint256 credit); // Claim credit by receiving tokens from Vault function sendTo(IERC20 token, address to, uint256 amount) external;
Token Handling
Token Types
solidity
enum TokenType {
STANDARD, // No rate provider, 1:1 value
WITH_RATE // Has rate provider for yield-bearing or wrapped tokens
}
struct TokenConfig {
IERC20 token;
TokenType tokenType;
IRateProvider rateProvider; // For WITH_RATE tokens
bool paysYieldFees; // Yield fees charged on this token
}
Scaling System
All internal math uses 18-decimal precision ("scaled18"):
solidity
struct PoolData {
IERC20[] tokens;
uint256[] balancesRaw; // Native decimals
uint256[] balancesLiveScaled18; // 18 decimals, rate-adjusted
uint256[] tokenRates; // Rate provider values or FP(1)
uint256[] decimalScalingFactors; // 10^(18 - decimals)
}
Pools in V3
Pools are separate contracts that implement IBasePool:
solidity
interface IBasePool {
// Core swap math - called by Vault
function onSwap(PoolSwapParams memory request) external returns (uint256);
// Invariant calculation for add/remove liquidity
function computeInvariant(uint256[] memory balancesLiveScaled18, Rounding rounding)
external view returns (uint256);
// Balance computation for single-token operations
function computeBalance(
uint256[] memory balancesLiveScaled18,
uint256 tokenInIndex,
uint256 invariantRatio
) external view returns (uint256 newBalance);
}
Pool Tokens (BPT)
Each pool has its own ERC20 token (BPT - Balancer Pool Token):
- •Minted when adding liquidity
- •Burned when removing liquidity
- •Token contract IS the pool contract (via BalancerPoolToken)
- •Balances stored in Vault, not the token contract
ERC4626 Buffers
V3 supports efficient wrapped token operations via buffers:
solidity
// Buffers hold underlying + wrapped token reserves // Enables wrap/unwrap without external calls when liquidity available function erc4626BufferWrapOrUnwrap(BufferWrapOrUnwrapParams memory params) external;
Key Storage Locations
solidity
// Pool state mapping(address pool => PoolConfigBits) internal _poolConfigBits; mapping(address pool => IERC20[]) internal _poolTokens; mapping(address pool => mapping(uint256 index => bytes32)) internal _poolTokenBalances; // Token reserves (total Vault balance per token) mapping(IERC20 token => uint256) internal _reservesOf; // ERC4626 buffers mapping(IERC4626 => bytes32 packedBalance) internal _bufferTokenBalances;
Progressive Disclosure
For deeper understanding, see these related skills:
| Skill | Topic |
|---|---|
balancer-v3-vault | Detailed Vault operations (swap, add/remove liquidity) |
balancer-v3-pools | Pool registration and initialization |
balancer-v3-weighted-pool | Weighted Pool math and factory |
balancer-v3-router | Router patterns and user interaction |
balancer-v3-hooks | Hook system for pool customization |