Bunicorn
Launch Testnet App
  • What is Bunicorn?
  • Introduction
    • AMM DEX
    • Liquidity Mining
    • NFT Collectibles
  • Getting Started
    • Wallets that Bunicorn supports
    • Get BEP20 Tokens
    • Connect your wallet
    • Bunicorn testnet 101
  • Bunicorn Exchange TUTORIAL
    • Tutorial for Trader
    • Tutorial for Liquidity Provider
      • Setup Proxy
      • Swap native BNB to WBNB to farm
      • How to Add Liquidity on Bunicorn
        • How to add liquidity to a Flexible pool?
        • How to add liquidity to a Stable Pool?
      • How to create a new Flexible pool?
        • How to Create a New Smart Pool
        • How to Create a New Shared Pool
      • How to Remove Liquidity
    • How to Join Token Play Farms
      • Stake LPs to Token Play Farm
      • Stake Single Asset $TOP
    • How to Join Ancient Buni NFT Farms
      • NFT Farm Ends. What should I do?
      • Stake LPs to Join the Ancient Buni Farm
      • Stake Single Asset
      • Harvest NFTs (Ancient Buni)
      • How to transfer Ancient BUNI NFTs to another address?
    • How to Join Bunicorn NFT Farms
      • Prestaking Farm Ends. What's next?
      • Stake LP to Join NFT Farms
      • Stake Single Asset
      • Harvest NFTs (Chest/Egg)
      • How to unlock ingame items from Chest/Egg NFTs
      • How to Transfer Egg/Chest NFTs to another address
    • Fee Structure
  • FAQ
    • What is a Flexible Pool?
    • What is a Stable Pool?
    • Can users create Stable pools themselves?
    • Are there any constraints for setting up a Bunicorn Flexible pool?
    • What are the advantages of a Stable Pool?
  • Glossary
  • SMART CONTRACTS
    • Flexible Pools
      • Exchange Proxy
      • Smart Order Router
      • On Chain Registry
      • Smart Pools
        • Overview
        • Configurable Rights Pool
        • Component Libraries
          • Rights Manager
      • Interfaces
      • Addresses
      • Events
      • API Index
    • Stable Pools
      • Pool Addresses
      • Swap Execution
      • Providing Liquidity
      • Flash Swaps
      • Contract Addresses
      • BuniCornRouter02
      • Pool
      • Pool (ERC-20)
      • Library
  • Subgraph API
    • Stable Pools
      • Entities
      • Sample queries
    • Flexible Pools
      • Entities
      • Sample queries
Powered by GitBook
On this page
  • SOR Object
  • Fetching Pool Data
  • await SOR.fetchPools()
  • await SOR.fetchFilteredPairPools(TokenIn, TokenOut)
  • Processing Swaps
  • async SOR.getSwaps(...)
  • Example - Using SOR To Get List Of Swaps
  • Example - SOR & ExchangeProxy

Was this helpful?

  1. SMART CONTRACTS
  2. Flexible Pools

Smart Order Router

PreviousExchange ProxyNextOn Chain Registry

Last updated 3 years ago

Was this helpful?

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:

    • Mainnet:

    • Kovan:

    • Due to lots of IPNS caching issues the static storage can be used instead:

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.

[swaps, total] = await SOR.getSwaps(
        tokenIn,
        tokenOut,
        swapType,
        swapAmount
    );

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

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.

require('dotenv').config();
import { SOR } from '@bunicorn/sor';
import { BigNumber } from 'bignumber.js';
import { JsonRpcProvider } from '@ethersproject/providers';

// MAINNET
const tokenIn = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; // BNB
const tokenOut = '0x0E7BeEc376099429b85639Eb3abE7cF22694ed49'; // BUNI


(async function() {
    const provider = new JsonRpcProvider(
        'https://bsc-dataseed.binance.org'
    );

    const poolsUrl = `https://ipfs.fleek.co/ipns/bunicorn-bucket.storage.fleek.co/bunicorn/flexible-pools`;

    const gasPrice = new BigNumber('30000000000');

    const maxNoPools = 4;

    const chainId = 56;

    const sor = new SOR(provider, gasPrice, maxNoPools, chainId, poolsUrl);

    // isFetched will be true on success
    let isFetched = await sor.fetchPools();

    const swapType = 'swapExactIn';

    const amountIn = new BigNumber('1000000000000000000');

    let [swaps, amountOut] = await sor.getSwaps(
        tokenIn,
        tokenOut,
        swapType,
        amountIn
    );
    console.log(`Total Return: ${amountOut.toString()}`);
    console.log(`Swaps: `);
    console.log(swaps);
})()

Example - SOR & ExchangeProxy

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';

async function makeSwap() {
    // If running this example make sure you have a .env file saved in root DIR with KEY=pk_of_wallet_to_swap_with
    const isMainnet = true;

    let provider, WBNB, USDC, BUNI, chainId, poolsUrl, proxyAddr;

    const BNB = '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 = newPrice
    const gasPrice = new BigNumber('25000000000');
    // This determines the max no of pools the SOR will use to swap.
    const maxNoPools = 4;
    const MAX_UINT = MaxUint256;

    // Will use mainnet addresses - BE CAREFUL, SWAP WILL USE REAL FUNDS
    provider = new JsonRpcProvider(
        'https://bsc-dataseed.binance.org'
    );
    WBNB = '0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c'; // Mainnet WBNB
    USDC = '0x8AC76a51cc950d9822D68b83fE1Ad97B32Cd580d'; // Mainnet USDC
    DAI = '0x0E7BeEc376099429b85639Eb3abE7cF22694ed49';
    chainId = 56;
    poolsUrl = `https://ipfs.fleek.co/ipns/bunicorn-bucket.storage.fleek.co/bunicorn/flexible-pools`;
    proxyAddr = '0xcBC167c444d01Bc00dA1Bf0abd7F8C3c1f438d0B'; // Mainnet proxy

    const sor = new SOR(provider, gasPrice, maxNoPools, chainId, poolsUrl);

    // This fetches all pools list from URL in constructor then onChain balances using Multicall
    console.log('Fetching pools...');
    await sor.fetchPools();
    console.log('Pools fetched, get swap info...');

    let tokenIn = WETH;
    let tokenOut = USDC;
    let swapType = 'swapExactIn';
    let amountIn = new BigNumber('1e16');

    let [swaps, amountOut] = await sor.getSwaps(
        tokenIn,
        tokenOut,
        swapType,
        amountIn
    );

    console.log(`Total Expected Out Of Token: ${amountOut.toString()}`);

    console.log('Exectuting Swap Using Exchange Proxy...');

    const wallet = new Wallet(process.env.KEY, provider);
    const proxyArtifact = require('./abi/ExchangeProxy.json');
    let proxyContract = new Contract(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 = await proxyContract.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}`);
    await tx.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 = new BigNumber(1e18); // This is the exact amount out of tokenOut we want to receive

    const tokenArtifact = require('./abi/BEP20.json');
    let tokenInContract = new Contract(tokenIn, tokenArtifact.abi, provider);
    tokenInContract = tokenInContract.connect(wallet);
    console.log('Approving proxy...');
    tx = await tokenInContract.approve(proxyAddr, MAX_UINT);
    await tx.wait();
    console.log('Approved.');

    // We want to fetch pools again to make sure onchain balances are correct and we have most accurate swap info
    console.log('Update pool balances...');
    await sor.fetchPools();
    console.log('Pools fetched, get swap info...');

    [swaps, amountIn] = await sor.getSwaps(
        tokenIn,
        tokenOut,
        swapType,
        amountOut
    );

    console.log(`Required token input amount: ${amountIn.toString()}`);

    console.log('Exectuting Swap Using Exchange Proxy...');

    tx = await proxyContract.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}`);
    await tx.wait();
    console.log('Check Balances');
}

makeSwap();

Bunicorn makes use of a 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.

https://ipfs.fleek.co/ipns/bunicorn-bucket.storage.fleek.co/bunicorn/flexible-pools
https://ipfs.fleek.co/ipns/bunicorn-bucket.storage.fleek.co/bunicorn-kovan/flexible-pools
https://storageapi.fleek.co/bunicorn-bucket/bunicorn/flexible-pools
ExchangeProxy contract