Developers
Search…
Creation

Overview

Before creating a new pool, make sure there isn't already a similar pool; there's no advantage to fragmenting liquidity!
Anyone can create a pool on Balancer. WeightedPools can be deployed using the Pool Creator Interface (Polygon link) (Arbitrum link), but users can deploy any pools programmatically. It's highly recommended that you deploy a pool on a testnet before doing so on a production network.
You can deploy most if not all pools with balpy's poolCreation script -- more info.

What kind of pool should I deploy?

That totally depends on your use-case. You should read through the descriptions of different Balancer PoolTypes to decide what you want to deploy.

Deployment Process Summary

Step 1: Deploy the Pool From Factory

Each poolType has a factory from which users can deploy new pools. To deploy a pool, you must call that factory's create() function with the arguments that correspond to that specific pool. The below section goes over common arguments, and the subpages in this section go further into detail for the pool-specific arguments.

Step 2: Add liquidity with the INIT join

The INIT join can be done only once when the pool have a BPT totalSupply of 0. Almost all pools require you to use a JoinKind of type INIT before you can use the pool.
Linear Pools are an exception to this rule; you can swap into the pool right away to receive BPT out.
While StablePhantom Pools do use INIT joins, they require that in addition to the "normal" tokens, you must also pass the pools own BPT in a quantity of 2**112-1.

Common Arguments

name - The name of the pool corresponding Balancer Pool Token (BPT)
symbol - The short symbol for the BPT
tokens - A numerically sorted array of all tokens in the pool
swapFeePercentage - How much of a swap fee the pool collects (more below)
owner - The "owner" of the pool: account that has some limited control over pool parameters (more below)

Fees and Owners

Two factors your should consider before deploying your pool are how fees and owners should be set. Pools can have static fees or dynamic fees, read more about them in the main docs.

Fees

Static Fees

If you want static fees, you should set the fee you want the pool to have forever, and set the owner to the zero address 0x0000000000000000000000000000000000000000.

Dynamic Fees

If you want dynamic fees, you should set the fee to an initial value, and set the owner either as the address that you want to control the pool fee, or to the delegate address. An address that is set as the owner has permission to set the fee to anything between 0.0001% and 10% whenever they want.
If the pool owner is set to the delegate address (0xBA1BA1ba1BA1bA1bA1Ba1BA1ba1BA1bA1ba1ba1B) then Governance-approved fee-setters have permission to change the fee. Currently Gauntlet has this authority.

Owner Rights

Aside from setting swap fees, pool owners have other right on some pools that may play a role when deciding on an owner.

Deploying a pool with balpy

The Balancer Python library balpy supports deploying pools. Using the samples in the balpy GitHub repository, you can deploy pools from config files. There are sample config files for many pools including:
  • Weighted Pools
  • Oracle Pools (WeightedPool2Tokens)
  • Stable Pools
  • Liquidity Bootstrapping Pools
  • MetaStable Pools
  • Investment Pools
  • AaveLinear Pools
  • StablePhantom Pools
More pools configs will be added as new factories are deployed.
Once you have set up the necessary environment variables and created your virtual environment, you can run the sample script with the command below. The script will ensure that you have sufficient token balances and will execute token allowance approvals if you do not have sufficient allowances.
1
python3 poolCreationSample.py <yourModifiedPoolFile.json>
Copied!

Deploying a pool with TypeScript

This tutorial will illustrate deploying an Ethereum mainnet WeightedPool using hardhat and ethers. It also assumes that you have your artifacts built.
Modify accordingly if you wish to deploy a different PoolType, use a different network, or use JS/Buidler/Truffle/etc.

Defining Addresses

1
// Contracts
2
const VAULT = '0xBA12222222228d8Ba445958a75a0704d566BF2C8';
3
const WEIGHTED_POOL_FACTORY = '0x8E9aa87E45e92bad84D5F8DD1bff34Fb92637dE9';
4
const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
5
6
// Tokens -- MUST be sorted numerically
7
const MKR = '0x9f8F72aA9304c8B593d555F12eF6589cC3A579A2';
8
const WETH = '0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2';
9
const USDT = '0xdac17f958d2ee523a2206206994597c13d831ec7';
10
const tokens = [MKR, WETH, USDT];
Copied!
Your tokens array must be sorted numerically. All other corresponding arrays (ex. weights, defined below) should reflect this ordering

Pool Arguments

We now define the name, symbol, swap fee, and token weights. In this example, we want the weights to be 70/15/15 corresponding to MKR/WETH/USDT. Weights must add up to 1 represented with 18 decimals. Swap fee must be between 0.0001% and 10%, where 100% is 1 represented with 18 decimals.
1
const NAME = 'Three-token Test Pool';
2
const SYMBOL = '70MKR-15WETH-15USDT';
3
const swapFeePercentage = 0.005e18; // 0.5%
4
const weights = [0.7e18, 0.15e18, 0.15e18];
Copied!

Creating the Pool in the Factory

In this block, we call the create function on the WeightedPoolFactory to deploy a new WeightedPool. We then get the poolId from the newly deployed pool.
1
const factory = await ethers.getContractAt('WeightedPoolFactory',
2
WEIGHTED_POOL_FACTORY);
3
4
// If you're creating a different type of pool, look up the create
5
// function for your corresponding pool in that pool factory's ABI
6
const tx = await factory.create(NAME, SYMBOL, tokens, weights,
7
swapFeePercentage, ZERO_ADDRESS);
8
const receipt = await tx.wait();
9
10
// We need to get the new pool address out of the PoolCreated event
11
const events = receipt.events.filter((e) => e.event === 'PoolCreated');
12
const poolAddress = events[0].args.pool;
13
14
// We're going to need the PoolId later, so ask the contract for it
15
const pool = await ethers.getContractAt('WeightedPool', poolAddress);
16
const poolId = await pool.getPoolId();
Copied!

Adding Tokens to Your New Pool

Before we send tokens to the Vault, we must approve appropriate allowances so that it can move our tokens. We can send infinite approvals (2e256 - 1) or enough to satisfy the amounts we wish to move.
1
const vault = await ethers.getContractAt('Vault', VAULT);
2
3
// Tokens must be in the same order
4
// Values must be decimal-normalized! (USDT has 6 decimals)
5
const initialBalances = [16.667e18, 3.5714e18, 7500e6];
6
7
// Need to approve the Vault to transfer the tokens!
8
// Can do through Etherscan, or programmatically
9
for (var i in tokens) {
10
const tokenContract = await ethers.getContractAt('ERC20', tokens[i]);
11
await tokenContract.approve(VAULT, initialBalances[i]);
12
}
Copied!
Now we must join the pool using a JoinKind of type INIT (more info on the different types of JoinKind). This requires a list of initialBalances, which must be in the same order as the sorted token addresses. We must encode the userData for our join. We then call joinPool on the Vault, since that is where all tokens are held.
1
// Construct userData
2
const JOIN_KIND_INIT = 0;
3
const initUserData =
4
ethers.utils.defaultAbiCoder.encode(['uint256', 'uint256[]'],
5
[JOIN_KIND_INIT, initialBalances]);
6
7
const joinPoolRequest = {
8
assets: tokens,
9
maxAmountsIn: initialBalances,
10
userData: initUserData,
11
fromInternalBalance: false
12
}
13
14
// define caller as the address you're calling from
15
caller = '0x...YOUR_ADDRESS_HERE...';
16
17
// joins are done on the Vault
18
const tx = await vault.joinPool(poolId, caller, caller, joinPoolRequest);
19
20
// You can wait for it like this, or just print the tx hash and monitor
21
const receipt = await tx.wait();
Copied!
At this point you should have a funded WeightedPool, visible on the Balancer UI. In this mainnet example, you would be able to reach this page at: https://app.balancer.fi/#/pool/<yourPoolId>