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.
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:
- Deploy a factory contract that inherits from BasePoolFactory.sol
- Deploy the pool contract using the factory's
_create
function - Register the pool using the factory's
_registerPoolWithVault
function - Use Permit2 to approve the Router to spend the tokens that will be used to initialize the pool
- Call
router.initialize()
to seed the pool with initial liquidity
Tips
To see example foundry scripts for deploying a custom pool using a factory, check out Scaffold Balancer v3
Creating a Custom Pool Factory Contract
A factory contract should inherit the 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
Factory Constructor Parameters
IVault vault
: The address of the Balancer vaultuint32 pauseWindowDuration
: The period, starting from deployment of a factory, during which pools can be paused and unpaused, see FactoryWidePauseWindow.solbytes memory creationCode
: The creation bytecode of the pool contract used byCREATE3
for deployment
Pool Deployment Parameters
bytes memory constructorArgs
: The abi encoded constructor args for the custom poolbytes32 salt
: Used to compute a unique, deterministic address for each pool deployment
Pool Registration Parameters
TokenConfig[] memory tokens
: An array of descriptors for the tokens the pool will manage, see Token Typesuint256 swapFeePercentage
: Fee charged for each swap. For more information, see Swap feesbool protocolFeeExempt
: If true, the pool's initial aggregate fees will be set to 0PoolRoleAccounts memory roleAccounts
: Addresses allowed to change certain pool settings, see Pool Role Permissionsaddress poolHooksContract
: Contract that implements the hooks for the pool. If no hooks, use the zero addressLiquidityManagement memory liquidityManagement
: Specifies support for Custom Liquidity Operations
Info
Although deploying pools via a factory contract is the recommended approach, it is not mandatory since it is possible to call vault.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:
- Ensure the Permit2 contract has been granted sufficient allowance to spend tokens on behalf of the
msg.sender
- Transfer sufficient allowance to the Router with
Permit2.approve
- Call
router.initialize()
After a pool has been initialized, normal liquidity operations and swaps are instantly enabled.