cyotee-balancer-v3-pool-registration
SKILL.md
Balancer V3 Pool Registration & Initialization
This skill covers how pools are registered with the Vault and initialized with liquidity.
Pool Lifecycle
┌──────────────────────────────────────────────────────────────┐
│ POOL LIFECYCLE │
├──────────────────────────────────────────────────────────────┤
│ 1. DEPLOY: Factory deploys pool contract │
│ 2. REGISTER: Factory calls vault.registerPool() │
│ 3. INITIALIZE: Router calls vault.initialize() with liquidity│
│ 4. ACTIVE: Pool ready for swaps and liquidity operations │
└──────────────────────────────────────────────────────────────┘
Pool Registration
registerPool Function
function registerPool(
address pool,
TokenConfig[] memory tokenConfig,
uint256 swapFeePercentage,
uint32 pauseWindowEndTime,
bool protocolFeeExempt,
PoolRoleAccounts calldata roleAccounts,
address poolHooksContract,
LiquidityManagement calldata liquidityManagement
) external;
TokenConfig
struct TokenConfig {
IERC20 token;
TokenType tokenType; // STANDARD or WITH_RATE
IRateProvider rateProvider; // Required for WITH_RATE tokens
bool paysYieldFees; // Yield fees charged on this token
}
enum TokenType {
STANDARD, // No rate provider (1:1 scaling)
WITH_RATE // Has rate provider for wrapped/yield tokens
}
Token Requirements:
- 2-8 tokens per pool
- Max 18 decimals
- Tokens must be sorted by address
- No duplicate tokens
- No rebasing tokens
- No fee-on-transfer tokens
PoolRoleAccounts
struct PoolRoleAccounts {
address pauseManager; // Can pause/unpause pool
address swapFeeManager; // Can set static swap fees
address poolCreator; // Receives pool creator fees
}
If set to address(0), permissions default to the Authorizer.
LiquidityManagement
struct LiquidityManagement {
bool disableUnbalancedLiquidity; // Only proportional add/remove
bool enableAddLiquidityCustom; // Pool implements custom add
bool enableRemoveLiquidityCustom; // Pool implements custom remove
bool enableDonation; // Allow DONATION add liquidity
}
BasePoolFactory Pattern
All pool factories extend BasePoolFactory:
abstract contract BasePoolFactory {
IVault private immutable _vault;
uint32 private immutable _pauseWindowDuration;
// Deploy pool and register with Vault
function _create(bytes memory constructorArgs, bytes32 salt) internal returns (address pool) {
pool = Create2.deploy(0, salt, abi.encodePacked(creationCode, constructorArgs));
}
function _registerPoolWithVault(
address pool,
TokenConfig[] memory tokenConfig,
uint256 swapFeePercentage,
bool protocolFeeExempt,
PoolRoleAccounts memory roleAccounts,
address poolHooksContract,
LiquidityManagement memory liquidityManagement
) internal {
_vault.registerPool(
pool,
tokenConfig,
swapFeePercentage,
uint32(block.timestamp) + _pauseWindowDuration,
protocolFeeExempt,
roleAccounts,
poolHooksContract,
liquidityManagement
);
}
function getDefaultLiquidityManagement() public pure returns (LiquidityManagement memory) {
return LiquidityManagement({
disableUnbalancedLiquidity: false,
enableAddLiquidityCustom: false,
enableRemoveLiquidityCustom: false,
enableDonation: false
});
}
}
Pool Initialization
After registration, pools must be initialized with liquidity:
function initialize(
address pool,
address to, // BPT recipient
IERC20[] memory tokens, // Must match registration order
uint256[] memory exactAmountsIn,
uint256 minBptAmountOut,
bytes memory userData
) external returns (uint256 bptAmountOut);
Initialization Flow
┌──────────────────────────────────────────────────────────────┐
│ INITIALIZATION FLOW │
├──────────────────────────────────────────────────────────────┤
│ 1. Verify pool is registered but not initialized │
│ 2. Verify tokens match registration order │
│ 3. Call beforeInitialize hook (if enabled) │
│ 4. Compute initial invariant from pool.computeInvariant() │
│ 5. Mint initial BPT (invariant + minimum locked) │
│ 6. Lock MINIMUM_BPT to address(0) for safety │
│ 7. Transfer actual BPT to recipient │
│ 8. Call afterInitialize hook (if enabled) │
│ 9. Mark pool as initialized │
└──────────────────────────────────────────────────────────────┘
Minimum BPT Lock
// Protects against first-depositor attacks
uint256 internal constant _POOL_MINIMUM_TOTAL_SUPPLY = 1e6;
// During initialization:
// - Compute BPT = invariant
// - Lock MINIMUM to burn address
// - Give (BPT - MINIMUM) to depositor
Pool Configuration
After registration, pools have configuration stored:
struct PoolConfig {
LiquidityManagement liquidityManagement;
uint256 staticSwapFeePercentage;
uint256 aggregateSwapFeePercentage;
uint256 aggregateYieldFeePercentage;
uint40 tokenDecimalDiffs;
uint32 pauseWindowEndTime;
bool isPoolRegistered;
bool isPoolInitialized;
bool isPoolPaused;
bool isPoolInRecoveryMode;
}
Fee Configuration
// Swap fees (0.001% to 10%)
uint256 private constant _MIN_SWAP_FEE_PERCENTAGE = 0.001e16;
uint256 private constant _MAX_SWAP_FEE_PERCENTAGE = 10e16;
// Aggregate fees = protocol fee + pool creator fee
// Set via ProtocolFeeController
Creating a Pool (Factory Example)
// WeightedPoolFactory.create()
function create(
string memory name,
string memory symbol,
TokenConfig[] memory tokens,
uint256[] memory normalizedWeights,
PoolRoleAccounts memory roleAccounts,
uint256 swapFeePercentage,
address poolHooksContract,
bool enableDonation,
bool disableUnbalancedLiquidity,
bytes32 salt
) external returns (address pool) {
LiquidityManagement memory liquidityManagement = getDefaultLiquidityManagement();
liquidityManagement.enableDonation = enableDonation;
liquidityManagement.disableUnbalancedLiquidity = disableUnbalancedLiquidity;
pool = _create(
abi.encode(
WeightedPool.NewPoolParams({
name: name,
symbol: symbol,
numTokens: tokens.length,
normalizedWeights: normalizedWeights,
version: _poolVersion
}),
getVault()
),
salt
);
_registerPoolWithVault(
pool,
tokens,
swapFeePercentage,
false, // not protocol fee exempt
roleAccounts,
poolHooksContract,
liquidityManagement
);
}
Hooks During Registration
If a hooks contract is provided:
interface IHooks {
// Called during registration - must return true
function onRegister(
address factory,
address pool,
TokenConfig[] memory tokenConfig,
LiquidityManagement calldata liquidityManagement
) external returns (bool success);
// Returns which hooks the contract implements
function getHookFlags() external view returns (HookFlags memory);
}
Querying Pool State
// Check registration status
function isPoolRegistered(address pool) external view returns (bool);
function isPoolInitialized(address pool) external view returns (bool);
// Get pool tokens
function getPoolTokens(address pool) external view returns (IERC20[] memory);
// Get full pool data
function getPoolData(address pool) external view returns (PoolData memory);
// Get pool configuration
function getPoolConfig(address pool) external view returns (PoolConfig memory);
Pool Type Skills
For specific pool implementations, see:
| Pool Type | Skill | Use Case |
|---|---|---|
| Weighted Pool | balancer-v3-weighted-pool |
Uncorrelated assets with custom weights |
| Stable Pool | balancer-v3-stable-pool |
Correlated/pegged assets (stablecoins, LSTs) |
| Gyro Pools | balancer-v3-gyro-pool |
Concentrated liquidity (2-CLP, E-CLP) |
| CoW Pool | balancer-v3-cow-pool |
MEV-protected trading |
| RECLAMM Pool | balancer-v3-reclamm-pool |
Regenerating concentrated liquidity with auto-adjusting price range |
Reference Files
For complete implementation:
pkg/interfaces/contracts/vault/IVaultExtension.sol- Registration interfacepkg/pool-utils/contracts/BasePoolFactory.sol- Factory base classpkg/pool-weighted/contracts/WeightedPoolFactory.sol- Example factory
Weekly Installs
1
Source
smithery.ai/ski…strationFirst Seen
Feb 28, 2026
Installed on
mcpjam1
amp1
cline1
openclaw1
opencode1
cursor1