Balancer
Search…
Development & Examples
Documentation for working with the @balancer-labs/sor package. For a description of the SOR and math, see this page.
Please take caution as the SOR is under heavy development and may have breaking changes.
The SOR package includes a primary SOR object with an SOR.getSwaps function and several helper functions for retireving Balancer pool data.

SOR Object

When instantiating a new SOR object we must pass five parameters to the constructor:
const SOR = new sor.SOR(Provider: JsonRpcProvider, GasPrice: BigNumber, MaxPools: number, ChainId: number, PoolsUrl: string)
Where:

Fetching Pool Data

The SOR requires an up to date list of pool data when calculating swap information and retrieves on-chain token balances for each pool. There are two available methods:

await SOR.fetchPools()

This will fetch all pools (using the URL in constructor) and on-chain balances. Returns true on success or false if there has been an error.

await SOR.fetchFilteredPairPools(TokenIn, TokenOut)

A subset of valid pools for token pair, TokenIn/TokenOut, is found and on-chain balances retrieved. Returns true on success or false if there has been an error. This can be a quicker alternative to using fetchPools but will need to be called for every token pair of interest.

Processing Swaps

async SOR.getSwaps(...)

The getSwaps function will use the pool data and the trade parameters to perform an optimization for the best price execution. It returns swap information and the total that can then be used to execute the swaps on-chain.
1
[swaps, total] = await SOR.getSwaps(
2
tokenIn,
3
tokenOut,
4
swapType,
5
swapAmount
6
);
Copied!
tokenIn - string: address of token in
tokenOut - string: address of token out
swapType - string: either swapExactIn or swapExactOut
swapAmount - BigNumber: amount to be traded, in Wei

async SOR.setCostOutputToken(tokenOut)

The cost of the output token in ETH multiplied by the gas cost to perform the swap. This is used to determine whether the lower price obtained through including an additional pool in the transaction outweigh the gas costs. This function can be called before getSwaps to retrieve and cache the cost which will then be used in any getSwap calls using that token. Defaults to 0 for a token if not previously set.
Notice that tokenOut is tokenOut if swapType == 'swapExactIn' and tokenIn if swapType == 'swapExactOut.

Example - Using SOR To Get List Of Swaps

Below is an example snippet that uses the SOR to return a final list of swaps and the expected output. The swaps returned can then be passed on to the exchange proxy or otherwise used to atomically execute the trades.
1
require('dotenv').config();
2
import { SOR } from '@balancer-labs/sor';
3
import { BigNumber } from 'bignumber.js';
4
import { JsonRpcProvider } from '@ethersproject/providers';
5
6
// MAINNET
7
const tokenIn = '0x6B175474E89094C44Da98b954EedeAC495271d0F'; // DAI
8
const tokenOut = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; // WETH
9
10
11
(async function() {
12
const provider = new JsonRpcProvider(
13
`https://mainnet.infura.io/v3/${process.env.INFURA}`
14
);
15
16
const poolsUrl = `https://ipfs.fleek.co/ipns/balancer-team-bucket.storage.fleek.co/balancer-exchange/pools`;
17
18
const gasPrice = new BigNumber('30000000000');
19
20
const maxNoPools = 4;
21
22
const chainId = 1;
23
24
const sor = new SOR(provider, gasPrice, maxNoPools, chainId, poolsUrl);
25
26
// isFetched will be true on success
27
let isFetched = await sor.fetchPools();
28
29
await sor.setCostOutputToken(tokenOut);
30
31
const swapType = 'swapExactIn';
32
33
const amountIn = new BigNumber('1000000000000000000');
34
35
let [swaps, amountOut] = await sor.getSwaps(
36
tokenIn,
37
tokenOut,
38
swapType,
39
amountIn
40
);
41
console.log(`Total Return: ${amountOut.toString()}`);
42
console.log(`Swaps: `);
43
console.log(swaps);
44
})()
Copied!

Example - SOR & ExchangeProxy

Balancer labs makes use of a ExchangeProxy contract that allows users to batch execute swaps recommended by the SOR. The following example shows how SOR and ExchangeProxy can be used together to execute on-chain trades.
1
require('dotenv').config();
2
import { SOR } from '@balancer-labs/sor';
3
import { BigNumber } from 'bignumber.js';
4
import { JsonRpcProvider } from '@ethersproject/providers';
5
import { Wallet } from '@ethersproject/wallet';
6
import { MaxUint256 } from '@ethersproject/constants';
7
import { Contract } from '@ethersproject/contracts';
8
9
async function makeSwap() {
10
// If running this example make sure you have a .env file saved in root DIR with INFURA=your_key, KEY=pk_of_wallet_to_swap_with
11
const isMainnet = true;
12
13
let provider, WETH, USDC, DAI, chainId, poolsUrl, proxyAddr;
14
15
const ETH = '0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';
16
// gasPrice is used by SOR as a factor to determine how many pools to swap against.
17
// i.e. higher cost means more costly to trade against lots of different pools.
18
// Can be changed in future using SOR.gasPrice = newPrice
19
const gasPrice = new BigNumber('25000000000');
20
// This determines the max no of pools the SOR will use to swap.
21
const maxNoPools = 4;
22
const MAX_UINT = MaxUint256;
23
24
if (isMainnet) {
25
// Will use mainnet addresses - BE CAREFUL, SWAP WILL USE REAL FUNDS
26
provider = new JsonRpcProvider(
27
`https://mainnet.infura.io/v3/${process.env.INFURA}`
28
);
29
WETH = '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'; // Mainnet WETH
30
USDC = '0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'; // Mainnet USDC
31
DAI = '0x6B175474E89094C44Da98b954EedeAC495271d0F';
32
chainId = 1;
33
poolsUrl = `https://ipfs.fleek.co/ipns/balancer-team-bucket.storage.fleek.co/balancer-exchange/pools`;
34
proxyAddr = '0x3E66B66Fd1d0b02fDa6C811Da9E0547970DB2f21'; // Mainnet proxy
35
} else {
36
// Will use Kovan addresses
37
provider = new JsonRpcProvider(
38
`https://kovan.infura.io/v3/${process.env.INFURA}`
39
);
40
WETH = '0xd0A1E359811322d97991E03f863a0C30C2cF029C'; // Kovan WETH
41
USDC = '0x2F375e94FC336Cdec2Dc0cCB5277FE59CBf1cAe5'; // Kovan USDC
42
DAI = '0x1528F3FCc26d13F7079325Fb78D9442607781c8C'; // Kovan DAI
43
chainId = 42;
44
poolsUrl = `https://ipfs.fleek.co/ipns/balancer-team-bucket.storage.fleek.co/balancer-exchange-kovan/pools`;
45
proxyAddr = '0x4e67bf5bD28Dd4b570FBAFe11D0633eCbA2754Ec'; // Kovan proxy
46
}
47
48
const sor = new SOR(provider, gasPrice, maxNoPools, chainId, poolsUrl);
49
50
// This fetches all pools list from URL in constructor then onChain balances using Multicall
51
console.log('Fetching pools...');
52
await sor.fetchPools();
53
console.log('Pools fetched, get swap info...');
54
55
let tokenIn = WETH;
56
let tokenOut = USDC;
57
let swapType = 'swapExactIn';
58
let amountIn = new BigNumber('1e16');
59
// This calculates the cost to make a swap which is used as an input to sor to allow it to make gas efficient recommendations.
60
// Can be set once and will be used for further swap calculations.
61
// Defaults to 0 if not called or can be set manually using: await sor.setCostOutputToken(tokenOut, manualPriceBn)
62
await sor.setCostOutputToken(tokenOut);
63
64
let [swaps, amountOut] = await sor.getSwaps(
65
tokenIn,
66
tokenOut,
67
swapType,
68
amountIn
69
);
70
71
console.log(`Total Expected Out Of Token: ${amountOut.toString()}`);
72
73
console.log('Exectuting Swap Using Exchange Proxy...');
74
75
const wallet = new Wallet(process.env.KEY, provider);
76
const proxyArtifact = require('./abi/ExchangeProxy.json');
77
let proxyContract = new Contract(proxyAddr, proxyArtifact.abi, provider);
78
proxyContract = proxyContract.connect(wallet);
79
80
console.log(`Swapping using address: ${wallet.address}...`);
81
/*
82
This first swap is WETH>TOKEN.
83
The ExchangeProxy can accept ETH in place of WETH and it will handle wrapping to Weth to make the swap.
84
*/
85
86
let tx = await proxyContract.multihopBatchSwapExactIn(
87
swaps,
88
ETH, // Note TokenIn is ETH address and not WETH as we are sending ETH
89
tokenOut,
90
amountIn.toString(),
91
amountOut.toString(), // This is the minimum amount out you will accept.
92
{
93
value: amountIn.toString(), // Here we send ETH in place of WETH
94
gasPrice: gasPrice.toString(),
95
}
96
);
97
console.log(`Tx Hash: ${tx.hash}`);
98
await tx.wait();
99
100
console.log('New Swap, ExactOut...');
101
/*
102
Now we swap TOKEN>TOKEN & use the swapExactOut swap type to set the exact amount out of tokenOut we want to receive.
103
ExchangeProxy will pull required amount of tokenIn to make swap so tokenIn approval must be set correctly.
104
*/
105
tokenIn = USDC;
106
tokenOut = DAI;
107
swapType = 'swapExactOut'; // New Swap Type.
108
amountOut = new BigNumber(1e18); // This is the exact amount out of tokenOut we want to receive
109
110
const tokenArtifact = require('./abi/ERC20.json');
111
let tokenInContract = new Contract(tokenIn, tokenArtifact.abi, provider);
112
tokenInContract = tokenInContract.connect(wallet);
113
console.log('Approving proxy...');
114
tx = await tokenInContract.approve(proxyAddr, MAX_UINT);
115
await tx.wait();
116
console.log('Approved.');
117
118
await sor.setCostOutputToken(tokenOut);
119
120
// We want to fetch pools again to make sure onchain balances are correct and we have most accurate swap info
121
console.log('Update pool balances...');
122
await sor.fetchPools();
123
console.log('Pools fetched, get swap info...');
124
125
[swaps, amountIn] = await sor.getSwaps(
126
tokenIn,
127
tokenOut,
128
swapType,
129
amountOut
130
);
131
132
console.log(`Required token input amount: ${amountIn.toString()}`);
133
134
console.log('Exectuting Swap Using Exchange Proxy...');
135
136
tx = await proxyContract.multihopBatchSwapExactOut(
137
swaps,
138
tokenIn,
139
tokenOut,
140
amountIn.toString(), // This is the max amount of tokenIn you will swap.
141
{
142
gasPrice: gasPrice.toString(),
143
}
144
);
145
console.log(`Tx Hash: ${tx.hash}`);
146
await tx.wait();
147
console.log('Check Balances');
148
}
149
150
makeSwap();
Copied!
Last modified 5mo ago