This guide will walk through the process of interacting with Balancer on Kovan directly with the smart contracts along with additional tooling such as the subgraph and SOR.
This guide will use
seth - a tool built by Dapphub to interact directly with smart contracts. To install, run the below command. Note: the Dapp Tools suite installs Nix OS
curl https://dapp.tools/install | sh
Follow the remaining steps at: https://github.com/dapphub/dapptools/tree/master/src/seth#example-sethrc-file in order to select the
SETH_CHAIN and address / key.
Run the below lines in your terminal to setup environment variables that will be used later in the guide.
All pools use wrapped ETH. In order to get WETH either use the faucet at https://faucet.kovan.network and then
deposit() into the WETH contract. Or the faucet below may have some available WETH.
For all other tokens, Balancer has deployed a set of test tokens and a faucet on Kovan. The guide below will use DAI & MKR, but feel free to use any of the available tokens. An user can call
drip once per day per token.
drip on the faucet specifying a token address:
seth send $FAUCET "drip(address)" $DAI
Confirm that you received test tokens:
seth --from-wei $(seth --to-dec $(seth call $DAI "balanceOf(address)" $ETH_FROM))
Repeat the above steps for any additional tokens you would like to interact with on the Balancer test deployment.
Next we're going to create a new pool. This guide will create a 50% WETH, 25% DAI, 25% MKR pool. Feel free to use any of the tokens
seth send --gas 5000000 $BFACTORY "newBPool()"
Make note of the created contract address and set the below
BPool variable to the address returned. This can be found on etherscan by looking at the internal txs of the tx hash.
BPOOL=0x... (address returned from previous step)
Token approvals are needed for any tokens you plan on adding to the pool. Approvals per pool is only required when interacting with the contracts directly. The frontend interfaces will all use a proxy so there will only be 1 time approvals to interact with all pools in the Balancer ecosystem.
amount=$(seth --to-uint256 $(seth --to-wei 1000000 ether))seth send $WETH "approve(address,uint256)" $BPOOL $amountseth send $DAI "approve(address,uint256)" $BPOOL $amountseth send $MKR "approve(address,uint256)" $BPOOL $amount
After all approvals are confirmed, tokens are bound to a specific pool by calling
bind(address token, uint balance, uint denorm)`
denorm is the denormalized weight for the token being added. Weights are stored in this fashion to prevent unnecessary gas costs of recalculating weights every time a new token is added. In our example, since we want 50% WETH, 25% DAI, and 25% MKR we can use the following denormalized weights: 10, 5, 5. (Note: the denorm weights are in the 10^18 scale to allow for non-integer weights)
# Bind 1 WETH with a denormalized weight of 10amount=$(seth --to-uint256 $(seth --to-wei 1 ether))weight=$(seth --to-uint256 $(seth --to-wei 10 ether))seth send $BPOOL "bind(address, uint256, uint256)" $WETH $amount $weight
Since we also want to bind DAI and MKR and know the weights specified, we have to calculate a balance to bind so that given the asset prices the weights & balances are what we expect. This example will assume the following asset prices: ETH - $200, MKR - $400, DAI - $1.
Since we bound 1 WETH at a price of $200 and we want that to be 50% of the pool, we want to bind $100 of both DAI and MKR.
# Bind 100 DAI with a denormalized weight of 5amount=$(seth --to-uint256 $(seth --to-wei 100 ether))weight=$(seth --to-uint256 $(seth --to-wei 5 ether))seth send $BPOOL "bind(address, uint256, uint256)" $DAI $amount $weight# Bind 0.25 MKR with a denormalized weight of 5amount=$(seth --to-uint256 $(seth --to-wei 0.25 ether))weight=$(seth --to-uint256 $(seth --to-wei 5 ether))seth send $BPOOL "bind(address, uint256, uint256)" $MKR $amount $weight
Let's confirm that all the tokens were added by using some view functions. This will get a token's normalized weight, or percent of the pool, to make sure our original math was correct.
seth call $BPOOL "getNumTokens()"seth --from-wei $(seth --to-dec $(seth call $BPOOL "getNormalizedWeight(address)" $WETH))seth --from-wei $(seth --to-dec $(seth call $BPOOL "getNormalizedWeight(address)" $DAI))seth --from-wei $(seth --to-dec $(seth call $BPOOL "getNormalizedWeight(address)" $MKR))
Now that we're finished adding tokens, we need to set a swap fee for our pool. Otherwise we wouldn't earn anything on our assets and be more exposed to impermanent loss. In this example, we will set a swap fee of 0.3%:
fee=$(seth --to-uint256 $(seth --to-wei 0.003 ether))seth send $BPOOL "setSwapFee(uint256)" $fee
We have a valid pool with 3 bound tokens and a swap fee of 0.3%. At this point, a decision has to be made whether to
finalize the pool. A pool can either be private or shared. A private pool allows the owner to continually adjust tokens, balances, weights, and fees. But prevents anyone else from adding or removing liquidity to that pool - it wouldn't be fair if the owner changed weights if other users have contributed liquidity! A shared pool is created when the
finalize function is called and is a one-way transition. This locks all of the tokens, balances, weights, and opens the ability for outside users to add and remove liquidity.
For our example, we want other users to be able to add liquidity so let's finalize it. 100 Balancer Pool Tokens will be minted as part of the pool finalization regardless of bound token balances. Balancer Pool Tokens, or BPTs represent proportional ownership of a pools liquidity. Any future joins or exits are calculated based on the relative liquidity being added.
seth send $BPOOL "finalize()"
All done, now anyone can add liquidity and swap against the assets in the pool!
Let's confirm that we received BPTs by calling
balanceOf directly on the pool address (since the pools themselves are ERC20 tokens)
seth --from-wei $(seth --to-dec $(seth call $BPOOL "balanceOf(address)" $ETH_FROM))