import { BigNumber , BigNumberish , parseFixed } from '@ethersproject/bignumber' ; import { Provider } from '@ethersproject/providers' ; import cloneDeep from 'lodash.clonedeep' ; import { BigNumber as OldBigNumber } from './utils/bignumber' ; import { getBestPaths } from './router' ; import { getWrappedInfo , setWrappedInfo } from './wrapInfo' ; import { formatSwaps } from './formatSwaps' ; import { PoolCacher } from './poolCacher' ; import { RouteProposer } from './routeProposal' ; import { filterPoolsByType } from './routeProposal/filtering' ; import { SwapCostCalculator } from './swapCostCalculator' ; import { getLidoStaticSwaps , isLidoStableSwap } from './pools/lido' ; import { isSameAddress } from './utils' ; import { EMPTY_SWAPINFO } from './constants' ; import { SwapInfo , SwapTypes , NewPath , PoolFilter , Swap , SubgraphPoolBase , SwapOptions , TokenPriceService , PoolDataService , SorConfig , } from './types' ; import { Zero } from '@ethersproject/constants' ; export class SOR { private readonly poolCacher : PoolCacher ; private readonly routeProposer : RouteProposer ; readonly swapCostCalculator : SwapCostCalculator ; private readonly defaultSwapOptions : SwapOptions = { gasPrice : parseFixed ( '50' , 9 ) , swapGas : BigNumber . from ( '35000' ) , poolTypeFilter : PoolFilter . All , maxPools : 4 , timestamp : Math . floor ( Date . now ( ) / 1000 ) , forceRefresh : false , } ; param { Provider } provider param { SorConfig } config param { PoolDataService } poolDataService param { TokenPriceService } tokenPriceService constructor ( public provider , private readonly config , poolDataService , tokenPriceService ) { this . poolCacher = new PoolCacher ( poolDataService ) ; this . routeProposer = new RouteProposer ( config ) ; this . swapCostCalculator = new SwapCostCalculator ( config , tokenPriceService ) ; } getPools ( ) { return this . poolCacher . getPools ( ) ; } returns { boolean } async fetchPools ( ) { return this . poolCacher . fetchPools ( ) ; } param { string } tokenIn param { string } tokenOut param { SwapTypes } swapType param { BigNumberish } swapAmount returns { SwapInfo } async getSwaps ( tokenIn , tokenOut , swapType , swapAmount , swapOptions ? ) { if ( ! this . poolCacher . finishedFetching ) return cloneDeep ( EMPTY_SWAPINFO ) ; const options = { ... this . defaultSwapOptions , ... swapOptions , } ; const pools = this . poolCacher . getPools ( ) ; const filteredPools = filterPoolsByType ( pools , options . poolTypeFilter ) ; const wrappedInfo = await getWrappedInfo ( this . provider , swapType , tokenIn , tokenOut , this . config , BigNumber . from ( swapAmount ) ) ; let swapInfo ; if ( isLidoStableSwap ( this . config . chainId , tokenIn , tokenOut ) ) { swapInfo = await getLidoStaticSwaps ( filteredPools , this . config . chainId , wrappedInfo . tokenIn . addressForSwaps , wrappedInfo . tokenOut . addressForSwaps , swapType , wrappedInfo . swapAmountForSwaps , this . provider ) ; } else { swapInfo = await this . processSwaps ( wrappedInfo . tokenIn . addressForSwaps , wrappedInfo . tokenOut . addressForSwaps , swapType , wrappedInfo . swapAmountForSwaps , filteredPools , options ) ; } if ( swapInfo . returnAmount . isZero ( ) ) return swapInfo ; swapInfo = setWrappedInfo ( swapInfo , swapType , wrappedInfo , this . config ) ; return swapInfo ; } param { string } outputToken param { number } outputTokenDecimals param { BigNumber } gasPrice param { BigNumber } swapGas returns { BigNumber } async getCostOfSwapInToken ( outputToken , outputTokenDecimals , gasPrice , swapGas ? ) { if ( gasPrice . isZero ( ) ) return Zero ; return this . swapCostCalculator . convertGasCostToToken ( outputToken , outputTokenDecimals , gasPrice , swapGas ) ; } private async processSwaps ( tokenIn , tokenOut , swapType , swapAmount , pools , swapOptions ) { if ( pools . length === 0 ) return cloneDeep ( EMPTY_SWAPINFO ) ; const paths = this . routeProposer . getCandidatePaths ( tokenIn , tokenOut , swapType , pools , swapOptions ) ; if ( paths . length == 0 ) return cloneDeep ( EMPTY_SWAPINFO ) ; let tokenInDecimals ; let tokenOutDecimals ; paths [ 0 ] . swaps . forEach ( ( swap ) => { if ( isSameAddress ( swap . tokenIn , tokenIn ) ) { tokenInDecimals = swap . tokenInDecimals ; } if ( isSameAddress ( swap . tokenOut , tokenOut ) ) { tokenOutDecimals = swap . tokenOutDecimals ; } } ) ; const costOutputToken = await this . getCostOfSwapInToken ( swapType === SwapTypes . SwapExactIn ? tokenOut : tokenIn , swapType === SwapTypes . SwapExactIn ? tokenOutDecimals : tokenInDecimals , swapOptions . gasPrice , swapOptions . swapGas ) ; const [ swaps , total , marketSp , totalConsideringFees ] = this . getBestPaths ( paths , swapAmount , swapType , tokenInDecimals , tokenOutDecimals , costOutputToken , swapOptions . maxPools ) ; const swapInfo = formatSwaps ( swaps , swapType , swapAmount , tokenIn , tokenOut , total , totalConsideringFees , marketSp ) ; return swapInfo ; } /**
     * Find optimal routes for trade from given candidate paths
     */ private getBestPaths ( paths , swapAmount , swapType , tokenInDecimals , tokenOutDecimals , costOutputToken , maxPools ) { const [ inputDecimals , outputDecimals ] = swapType === SwapTypes . SwapExactIn ? [ tokenInDecimals , tokenOutDecimals ] : [ tokenOutDecimals , tokenInDecimals ] ; const [ swaps , total , marketSp , totalConsideringFees ] = getBestPaths ( paths , swapType , swapAmount , inputDecimals , outputDecimals , maxPools , costOutputToken ) ; return [ swaps , parseFixed ( total . dp ( outputDecimals , OldBigNumber . ROUND_FLOOR ) . toString ( ) , outputDecimals ) , marketSp . toString ( ) , parseFixed ( totalConsideringFees . dp ( outputDecimals , OldBigNumber . ROUND_FLOOR ) . toString ( ) , outputDecimals ) , ] ; } }