Metapool Factory: Pools

Factory pools are permissionless metapools that can be deployed by anyone. New pools are deployed using Factory.deploy_metapool.

Source code for the implementation contracts may be viewed on Github.

Implementation Contracts

Each pool deployed by the factory is a thin proxy contract created with Vyper’s create_forwarder_to. The implementation contract targetted by the proxy is determined based on the base pool used. This is the same technique that was used to create pools in Uniswap V1.

The implementation contracts used for pools are deployed to the mainnet at the following addresses:

When interacting with a factory pool you should use the ABI at the corresponding implementation address:

>>> implementation = Contract("0x5F890841f657d90E081bAbdB532A05996Af79Fe6")
>>> abi = implementation.abi
>>> pool = Contract.from_abi("ESD Pool", "0xFD9f9784ac00432794c8D370d4910D2a3782324C", abi)

Getting Pool Info

StableSwap.coins(i: uint256) → address: view

Getter for the array of swappable coins within the pool. The last coin will always be the LP token of the base pool.

>>> pool.coins(0)
'0x36F3FD68E7325a35EB768F1AedaAe9EA0689d723'
StableSwap.balances(i: uint256) → uint256: view

Getter for the pool balances array.

>>> pool.balances(0)
4898975297808622168122
StableSwap.A() → uint256: view

The amplification coefficient for the pool.

>>> pool.A()
10
StableSwap.get_virtual_price() → uint256: view

The current price of the pool LP token relative to the underlying pool assets. Given as an integer with 1e18 precision.

>>> pool.get_virtual_price()
1006391979770742306
StableSwap.fee() → uint256: view

The pool swap fee, as an integer with 1e10 precision.

>>> pool.fee()
4000000
StableSwap.admin_fee() → uint256: view

The percentage of the swap fee that is taken as an admin fee, as an integer with with 1e10 precision.

For factory pools this is hardcoded at 50% (5000000000).

>>> pool.admin_fee()
5000000000

Making Exchanges

StableSwap.get_dy(i: int128, j: int128, dx: uint256) → uint256: view

Get the amount received (“dy”) when performing a swap between two assets within the pool.

Index values can be found using the coins public getter method, or get_coins within the factory contract.

  • i: Index value of the coin to send.

  • j: Index value of the coin to receive.

  • dx: The amount of i being exchanged.

Returns the amount of j received.

>>> pool.get_dy(0, 1, 10**18)
460306318211728896
StableSwap.get_dy_underlying(i: int128, j: int128, dx: uint256) → uint256: view

Get the amount received (“dy”) when swapping between two underlying assets within the pool.

Index values can be found using get_underlying_coins within the factory contract.

  • i: Index value of the token to send.

  • j: Index value of the token to receive.

  • dx: The amount of i being exchanged.

Returns the amount of j received.

>>> pool.get_dy_underlying(0, 1, 10**18)
463415003137589177
StableSwap.exchange(i: int128, j: int128, dx: uint256, min_dy: uint256, _receiver: address = msg.sender) → uint256: nonpayable

Performs an exchange between two tokens.

Index values can be found using the coins public getter method, or get_coins within the factory contract.

  • i: Index value of the token to send.

  • j: Index value of the token to receive.

  • dx: The amount of i being exchanged.

  • min_dy: The minimum amount of j to receive. If the swap would result in less, the transaction will revert.

  • _receiver: An optional address that will receive j. If not given, defaults to the caller.

Returns the amount of j received in the exchange.

>>> expected = pool.get_dy(0, 1, 10**18) * 0.99
>>> pool.exchange(0, 1, 10**18, expected, {'from': alice})
StableSwap.exchange_underlying(i: int128, j: int128, dx: uint256, min_dy: uint256, _receiver: address = msg.sender) → uint256: nonpayable

Perform an exchange between two underlying coins.

Index values can be found using get_underlying_coins within the factory contract.

  • i: Index value of the underlying token to send.

  • j: Index value of the underlying token to receive.

  • dx: The amount of i being exchanged.

  • min_dy: The minimum amount of j to receive. If the swap would result in less, the transaction will revert.

  • _receiver: An optional address that will receive j. If not given, defaults to the caller.

Returns the amount of j received in the exchange.

>>> expected = pool.get_dy_underlying(0, 3, 10**18) * 0.99
>>> pool.exchange_underlying(0, 3, 10**18, expected, {'from': alice})

Adding and Removing Liquidity

Note that if you wish to add or remove liqudity using the underlying assets within the base pool, you must use a depositor contract.

StableSwap.calc_token_amount(_amounts: uint256[2], _is_deposit: bool) → uint256: view

Estimate the amount of LP tokens minted or burned based on a deposit or withdrawal.

This calculation accounts for slippage, but not fees. It should be used as a basis for determining expected amounts when calling add_liquidity or remove_liquidity_imbalance, but should not be considered to be precise!

  • _amounts: Amount of each coin being deposited. Amounts correspond to the tokens at the same index locations within coins.

  • _is_deposit: set True for deposits, False for withdrawals.

Returns the expected amount of LP tokens minted or burned.

StableSwap.calc_withdraw_one_coin(_burn_amount: uint256, i: int128) → uint256: view

Calculate the amount received when withdrawing and unwrapping in a single coin. Useful for setting _max_burn_amount when calling remove_liquidity_one_coin.

  • _pool: Address of the pool to deposit into.

  • _token_amount: Amount of LP tokens to burn in the withdrawal.

  • i: Index value of the underlying coin to withdraw. Can be found using the coins getter method.

Returns the expected amount of coin received.

StableSwap.add_liquidity(_deposit_amounts: uint256[2], _min_mint_amount: uint256, _receiver: address = msg.sender) → uint256: nonpayable

Deposits coins into to the pool and mints new LP tokens.

  • _deposit_amounts: List of amounts of underlying coins to deposit. Amounts correspond to the tokens at the same index locations within coins.

  • _min_mint_amount: Minimum amount of LP tokens to mint from the deposit.

  • _receiver: Optional address that receives the LP tokens. If not specified, they are sent to the caller.

Returns the amount of LP tokens that were minted in the deposit.

>>> amounts = [1e18, 1e18]
>>> expected = pool.calc_token_amount(amounts, True) * 0.99
>>> pool.add_liquidity(amounts, expected, {'from': alice})
StableSwap.remove_liquidity(_burn_amount: uint256, _min_amounts: uint256[2], _receiver: address = msg.sender) → uint256[2]: nonpayable

Withdraws coins from the pool and burns LP tokens.

Withdrawal amounts are based on current deposit ratios. Withdrawals using this method do not incur a fee.

  • _burn_amount: Quantity of LP tokens to burn in the withdrawal. Amounts correspond to the tokens at the same index locations within coins.

  • _min_amounts: Minimum amounts of coins to receive.

  • _receiver: Optional address that receives the withdrawn coins. If not specified, the coins are sent to the caller.

Returns a list of the amounts of coins that were withdrawn.

>>> amount = pool.balanceOf(alice)
>>> pool.remove_liquidity(pool, amount, 0, {'from': alice})
StableSwap.remove_liquidity_imbalance(_amounts: uint256[2], _max_burn_amount: uint256, _receiver: address = msg.sender) → uint256: nonpayable

Withdraw coins in an imbalanced amount.

  • _amounts: List of amounts of underlying coins to withdraw. Amounts correspond to the tokens at the same index locations within coins.

  • _max_burn_amount: Maximum number of LP token to burn in the withdrawal.

  • _receiver: Optional address that receives the withdrawn coins. If not specified, the coins are sent to the caller.

Returns the amount of the LP tokens burned in the withdrawal.

>>> amounts = [1e18, 1e18]
>>> expected = pool.calc_token_amount(amounts, False) * 1.01
>>> pool.remove_liquidity_imbalance(pool, amounts, expected, {'from': alice})
StableSwap.remove_liquidity_one_coin(_burn_amount: uint256, i: int128, _min_received: uint256, _receiver: address = msg.sender) → uint256: nonpayable

Withdraw a single asset from the pool.

  • _burn_amount: Amount of LP tokens to burn in the withdrawal.

  • i: Index value of the coin to withdraw. Can be found using the coins getter method.

  • _min_amount: Minimum amount of the coin to receive

  • _receiver: Optional address that receives the withdrawn coin. If not specified, the coin is sent to the caller.

Returns the amount of the coin received in the withdrawal.

>>> amount = pool.balanceOf(alice)
>>> expected = pool.calc_withdraw_one_coin(pool, amount, 0) * 1.01
>>> pool.remove_liquidity_one_coin(amount, expected, 0, {'from': alice})

Claiming Admin Fees

StableSwap.withdraw_admin_fees(): nonpayable

Transfer admin fees to the fee distributor, allowing the fees to be claimed by veCRV holders.

Anyone can call this method. The destination address for the fees is hardcoded. To simplify fee distribution, this method swaps the admin balance of the non-base pool LP token into the base pool LP token.

LP Tokens

Factory pools differ from traditional Curve pools in that the pool contract is also the LP token. This improves gas efficiency and simplifies the factory deployment process.

Pool contracts adhere to the ERC-20 standard. As such, the following methods are available:

Token Info

StableSwap.name() → String[64]: view

The name of the pool / LP token.

StableSwap.symbol() → String[32]: view

The token symbol.

StableSwap.decimals() → uint256: view

The number of decimals for the token. Curve pool tokens always use 18 decimals.

StableSwap.totalSupply() → uint256: view

Balances and Allowances

StableSwap.balanceOf(_addr: address) → uint256: view

Getter for the current balance of an account.

StableSwap.allowance(_owner: address, _spender: address) → uint256: view

Getter for the number of tokens _owner has approve _spender to transfer on their behalf.

2**256-1 it is considered infinite approval. The approval amount will not decrease when tokens are transferred.

Transfers and Approvals

StableSwap.approve(_spender: address, _value: uint256) → bool: nonpayable

Approve _spender to transfer up to _value tokens on behalf of the caller.

If an approval is given for 2**256-1 it is considered infinite. The approval amount will not decrease when tokens are transferred, reducing gas costs.

  • _spender Address to set the approval for

  • _value Amount of the caller’s tokens that _spender is permitted to transfer

Returns True on success. Reverts on failure.

StableSwap.transfer(_to: address, _value: uint256) → bool: nonpayable

Transfer tokens from the caller to the given address.

  • _to: Address receiving the tokens.

  • _value: Amount of tokens to be transferred.

Returns True on a successful call. Reverts on failure.

StableSwap.transferFrom(_from: address, _to: address, _value: uint256) → bool: nonpayable

Transfer tokens between two addresses. The caller must have been given approval to transfer tokens on behalf of _from or the call will revert.

  • _from: The address to transfer the tokens from.

  • _to: Address receiving the tokens.

  • _value: mount of tokens to be transferred.

Returns True on a successful call. Reverts on failure.