Documentation for working with the @bunicorn/sor package.
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 retrieving Bunicorn flexible 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:
Provider is an Binance Smart Chain network provider (ex: local node or BSC's public nodes).
GasPrice is used by the SOR as a factor to determine how many pools to swap against. i.e. higher cost means more costly to trade against lots of different pools. This value can be changed.
MaxPools is the max number of pools to split the trade across. Limit to a reasonable number given gas costs.
ChainId is the network chain ID (i.e. 56=bsc mainnet, 42=Kovan)
PoolsUrl is a URL used to retrieve a JSON list of Bunicorn Flexible Pools to be considered. Bunicorn currently keeps an updated list at:
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.
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.
swapType - string: either swapExactIn or swapExactOut
swapAmount - BigNumber: amount to be traded, in Wei
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.
Bunicorn 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.
require('dotenv').config();import { SOR } from'@bunicorn/sor';import { BigNumber } from'bignumber.js';import { JsonRpcProvider } from'@ethersproject/providers';import { Wallet } from'@ethersproject/wallet';import { MaxUint256 } from'@ethersproject/constants';import { Contract } from'@ethersproject/contracts';asyncfunctionmakeSwap() {// If running this example make sure you have a .env file saved in root DIR with KEY=pk_of_wallet_to_swap_withconstisMainnet=true;let provider,WBNB,USDC,BUNI, chainId, poolsUrl, proxyAddr;constBNB='0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE';// gasPrice is used by SOR as a factor to determine how many pools to swap against.// i.e. higher cost means more costly to trade against lots of different pools.// Can be changed in future using SOR.gasPrice = newPriceconstgasPrice=newBigNumber('25000000000');// This determines the max no of pools the SOR will use to swap.constmaxNoPools=4;constMAX_UINT= MaxUint256;// Will use mainnet addresses - BE CAREFUL, SWAP WILL USE REAL FUNDS provider =newJsonRpcProvider('https://bsc-dataseed.binance.org' );WBNB='0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; // Mainnet WBNBUSDC='0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d'; // Mainnet USDCDAI='0x0E7BeEc376099429b85639Eb3abE7cF22694ed49'; chainId =56; poolsUrl =`https://ipfs.fleek.co/ipns/bunicorn-bucket.storage.fleek.co/bunicorn/flexible-pools`; proxyAddr ='0xcBC167c444d01Bc00dA1Bf0abd7F8C3c1f438d0B'; // Mainnet proxyconstsor=newSOR(provider, gasPrice, maxNoPools, chainId, poolsUrl);// This fetches all pools list from URL in constructor then onChain balances using Multicallconsole.log('Fetching pools...');awaitsor.fetchPools();console.log('Pools fetched, get swap info...');let tokenIn =WETH;let tokenOut =USDC;let swapType ='swapExactIn';let amountIn =newBigNumber('1e16');let [swaps, amountOut] =awaitsor.getSwaps( tokenIn, tokenOut, swapType, amountIn );console.log(`Total Expected Out Of Token: ${amountOut.toString()}`);console.log('Exectuting Swap Using Exchange Proxy...');constwallet=newWallet(process.env.KEY, provider);constproxyArtifact=require('./abi/ExchangeProxy.json');let proxyContract =newContract(proxyAddr,proxyArtifact.abi, provider); proxyContract =proxyContract.connect(wallet);console.log(`Swapping using address: ${wallet.address}...`);/* This first swap is WBNB>TOKEN. The ExchangeProxy can accept BNB in place of WBNB and it will handle wrapping to WBNB to make the swap. */let tx =awaitproxyContract.multihopBatchSwapExactIn( swaps,BNB,// Note TokenIn is BNB address and not WBNB as we are sending BNB tokenOut,amountIn.toString(),amountOut.toString(),// This is the minimum amount out you will accept. { value:amountIn.toString(),// Here we send BNB in place of WBNB gasPrice:gasPrice.toString(), } );console.log(`Tx Hash: ${tx.hash}`);awaittx.wait();console.log('New Swap, ExactOut...');/* Now we swap TOKEN>TOKEN & use the swapExactOut swap type to set the exact amount out of tokenOut we want to receive. ExchangeProxy will pull required amount of tokenIn to make swap so tokenIn approval must be set correctly. */ tokenIn =USDC; tokenOut =BUNI; swapType ='swapExactOut'; // New Swap Type. amountOut =newBigNumber(1e18); // This is the exact amount out of tokenOut we want to receiveconsttokenArtifact=require('./abi/BEP20.json');let tokenInContract =newContract(tokenIn,tokenArtifact.abi, provider); tokenInContract =tokenInContract.connect(wallet);console.log('Approving proxy...'); tx =awaittokenInContract.approve(proxyAddr,MAX_UINT);awaittx.wait();console.log('Approved.');// We want to fetch pools again to make sure onchain balances are correct and we have most accurate swap infoconsole.log('Update pool balances...');awaitsor.fetchPools();console.log('Pools fetched, get swap info...'); [swaps, amountIn] =awaitsor.getSwaps( tokenIn, tokenOut, swapType, amountOut );console.log(`Required token input amount: ${amountIn.toString()}`);console.log('Exectuting Swap Using Exchange Proxy...'); tx =awaitproxyContract.multihopBatchSwapExactOut( swaps, tokenIn, tokenOut,amountIn.toString(),// This is the max amount of tokenIn you will swap. { gasPrice:gasPrice.toString(), } );console.log(`Tx Hash: ${tx.hash}`);awaittx.wait();console.log('Check Balances');}makeSwap();