import cloneDeep from 'lodash.clonedeep' ; import { SubgraphPoolBase , PoolDictionary , SwapPairType , NewPath , Swap , PoolBase , PoolFilter , PoolTypes , PoolPairBase , SorConfig , } from '../types' ; import { MetaStablePool } from '../pools/metaStablePool/metaStablePool' ; import { ZERO } from '../utils/bignumber' ; import { parseNewPool } from '../pools' ; import { Zero } from '@ethersproject/constants' ; export const filterPoolsByType = ( pools , poolTypeFilter ) : SubgraphPoolBase [ ] => { if ( poolTypeFilter === PoolFilter . All ) return pools ; return pools . filter ( ( p ) => p . poolType === poolTypeFilter ) ; } ; export function filterPoolsOfInterest ( allPools , tokenIn , tokenOut , maxPools ) { const poolsFilteredDictionary = { } ; let tokenInPairedTokens = new Set ( ) ; let tokenOutPairedTokens = new Set ( ) ; Object . keys ( allPools ) . forEach ( ( id ) => { const pool = allPools [ id ] ; const tokenListSet = new Set ( pool . tokensList ) ; const containsTokenIn = tokenListSet . has ( tokenIn . toLowerCase ( ) ) ; const containsTokenOut = tokenListSet . has ( tokenOut . toLowerCase ( ) ) ; if ( containsTokenIn && containsTokenOut ) { pool . setTypeForSwap ( SwapPairType . Direct ) ; poolsFilteredDictionary [ pool . id ] = pool ; return ; } if ( maxPools > 1 ) { if ( containsTokenIn && ! containsTokenOut ) { tokenInPairedTokens = new Set ( [ ... tokenInPairedTokens , ... tokenListSet , ] ) ; pool . setTypeForSwap ( SwapPairType . HopIn ) ; poolsFilteredDictionary [ pool . id ] = pool ; } else if ( ! containsTokenIn && containsTokenOut ) { tokenOutPairedTokens = new Set ( [ ... tokenOutPairedTokens , ... tokenListSet , ] ) ; pool . setTypeForSwap ( SwapPairType . HopOut ) ; poolsFilteredDictionary [ pool . id ] = pool ; } } } ) ; const hopTokensSet = [ ... tokenInPairedTokens ] . filter ( ( x ) => tokenOutPairedTokens . has ( x ) ) ; const hopTokens = [ ... hopTokensSet ] ; return [ poolsFilteredDictionary , hopTokens ] ; } export function filterHopPools ( tokenIn , tokenOut , hopTokens , poolsOfInterest ) { const filteredPoolsOfInterest = { } ; const paths = [ ] ; let firstPoolLoop = true ; if ( hopTokens . length === 0 ) { for ( const id in poolsOfInterest ) { if ( poolsOfInterest [ id ] . swapPairType !== SwapPairType . Direct ) { continue ; } const path = createPath ( [ tokenIn , tokenOut ] , [ poolsOfInterest [ id ] ] ) ; paths . push ( path ) ; filteredPoolsOfInterest [ id ] = poolsOfInterest [ id ] ; } } for ( let i = 0 ; i < hopTokens . length ; i ++ ) { let highestNormalizedLiquidityFirst = ZERO ; let highestNormalizedLiquidityFirstPoolId ; let highestNormalizedLiquiditySecond = ZERO ; let highestNormalizedLiquiditySecondPoolId ; for ( const id in poolsOfInterest ) { const pool = poolsOfInterest [ id ] ; if ( pool . swapPairType === SwapPairType . Direct ) { if ( firstPoolLoop ) { const path = createPath ( [ tokenIn , tokenOut ] , [ pool ] ) ; paths . push ( path ) ; filteredPoolsOfInterest [ id ] = pool ; } continue ; } const tokenListSet = new Set ( pool . tokensList ) ; if ( ! tokenListSet . has ( hopTokens [ i ] ) ) continue ; if ( pool . swapPairType === SwapPairType . HopIn ) { const poolPairData = pool . parsePoolPairData ( tokenIn , hopTokens [ i ] ) ; const normalizedLiquidity = pool . getNormalizedLiquidity ( poolPairData ) ; if ( normalizedLiquidity . isGreaterThanOrEqualTo ( highestNormalizedLiquidityFirst ) ) { highestNormalizedLiquidityFirst = normalizedLiquidity ; highestNormalizedLiquidityFirstPoolId = id ; } } else if ( pool . swapPairType === SwapPairType . HopOut ) { const poolPairData = pool . parsePoolPairData ( hopTokens [ i ] , tokenOut ) ; const normalizedLiquidity = pool . getNormalizedLiquidity ( poolPairData ) ; if ( normalizedLiquidity . isGreaterThanOrEqualTo ( highestNormalizedLiquiditySecond ) ) { highestNormalizedLiquiditySecond = normalizedLiquidity ; highestNormalizedLiquiditySecondPoolId = id ; } } else { continue ; } } firstPoolLoop = false ; if ( highestNormalizedLiquidityFirstPoolId && highestNormalizedLiquiditySecondPoolId ) { filteredPoolsOfInterest [ highestNormalizedLiquidityFirstPoolId ] = poolsOfInterest [ highestNormalizedLiquidityFirstPoolId ] ; filteredPoolsOfInterest [ highestNormalizedLiquiditySecondPoolId ] = poolsOfInterest [ highestNormalizedLiquiditySecondPoolId ] ; const path = createPath ( [ tokenIn , hopTokens [ i ] , tokenOut ] , [ poolsOfInterest [ highestNormalizedLiquidityFirstPoolId ] , poolsOfInterest [ highestNormalizedLiquiditySecondPoolId ] , ] ) ; paths . push ( path ) ; } } return [ filteredPoolsOfInterest , paths ] ; } export function getLinearStaBal3Paths ( tokenIn , tokenOut , poolsAllDict , poolsFilteredDict , config ) { const staBal3PoolInfo = config . staBal3Pool ; if ( ! staBal3PoolInfo ) return [ ] ; const staBal3Pool = poolsAllDict [ staBal3PoolInfo . id ] as MetaStablePool ; if ( ! staBal3Pool ) return [ ] ; if ( tokenIn === staBal3PoolInfo . address || tokenOut === staBal3PoolInfo . address ) return [ ] ; const linearStaBal3Pools = getLinearStaBal3Pools ( poolsAllDict , staBal3Pool ) ; const linearPoolIn = getPoolWithMainToken ( linearStaBal3Pools , tokenIn ) ; const linearPoolOut = getPoolWithMainToken ( linearStaBal3Pools , tokenOut ) ; const pathsUsingLinear = [ ] ; if ( ! linearPoolIn && ! linearPoolOut ) return [ ] ; else if ( linearPoolIn && linearPoolOut ) { if ( linearPoolIn . id === linearPoolOut . id ) return [ ] ; const linearPathway = createPath ( [ tokenIn , linearPoolIn . address , linearPoolOut . address , tokenOut ] , [ linearPoolIn , staBal3Pool , linearPoolOut ] ) ; pathsUsingLinear . push ( linearPathway ) ; return pathsUsingLinear ; } else if ( linearPoolIn && ! linearPoolOut ) { const linearPathway = createPath ( [ tokenIn , linearPoolIn . address , staBal3Pool . address ] , [ linearPoolIn , staBal3Pool ] ) ; const staBal3TokenOutPath = getMostLiquidPath ( [ staBal3Pool . address , tokenOut ] , 0 , [ ] , poolsFilteredDict , SwapPairType . HopOut ) ; if ( staBal3TokenOutPath . swaps ) { const shortPath = composePaths ( [ linearPathway , staBal3TokenOutPath , ] ) ; pathsUsingLinear . push ( shortPath ) ; } const wethStaBal3Info = config . wethStaBal3 ; if ( ! wethStaBal3Info ) return pathsUsingLinear ; const wethStaBal3Pool = poolsAllDict [ wethStaBal3Info . id ] ; if ( ! wethStaBal3Pool ) return pathsUsingLinear ; const wethTokenOutPath = getMostLiquidPath ( [ staBal3Pool . address , config . weth , tokenOut ] , 1 , [ wethStaBal3Pool ] , poolsFilteredDict , SwapPairType . HopOut ) ; if ( wethTokenOutPath . swaps ) { const longPath = composePaths ( [ linearPathway , wethTokenOutPath ] ) ; pathsUsingLinear . push ( longPath ) ; } return pathsUsingLinear ; } else { const linearPathway = createPath ( [ staBal3Pool . address , linearPoolOut . address , tokenOut ] , [ staBal3Pool , linearPoolOut ] ) ; const tokenInStaBal3Path = getMostLiquidPath ( [ tokenIn , staBal3Pool . address ] , 0 , [ ] , poolsFilteredDict , SwapPairType . HopIn ) ; if ( tokenInStaBal3Path . swaps ) { const shortPath = composePaths ( [ tokenInStaBal3Path , linearPathway ] ) ; pathsUsingLinear . push ( shortPath ) ; } const wethStaBal3Info = config . wethStaBal3 ; if ( ! wethStaBal3Info ) return pathsUsingLinear ; const wethStaBal3Pool = poolsAllDict [ wethStaBal3Info . id ] ; if ( ! wethStaBal3Pool ) return pathsUsingLinear ; const tokenInWethPath = getMostLiquidPath ( [ tokenIn , config . weth , staBal3Pool . address ] , 0 , [ wethStaBal3Pool ] , poolsFilteredDict , SwapPairType . HopIn ) ; if ( tokenInWethPath . swaps ) { const longPath = composePaths ( [ tokenInWethPath , linearPathway ] ) ; pathsUsingLinear . push ( longPath ) ; } return pathsUsingLinear ; } } function getLinearStaBal3Pools ( pools , staBal3Pool ) { const linearStaBal3Pools = { } ; for ( const id in pools ) { if ( pools [ id ] . poolType === PoolTypes . Linear ) { if ( staBal3Pool . tokensList . includes ( pools [ id ] . address ) ) linearStaBal3Pools [ id ] = pools [ id ] ; } } return linearStaBal3Pools ; } function getPoolWithMainToken ( pools , token ) { let pool ; for ( const id in pools ) { const mainIndex = pools [ id ] . mainIndex ; if ( mainIndex !== undefined ) { if ( pools [ id ] . tokensList [ mainIndex ] === token . toLowerCase ( ) ) return pools [ id ] ; } } return pool ; } function getMostLiquidPath ( tokens , tokenInIndex , pathPools , pools , swapPairType ) { const tokenIn = tokens [ tokenInIndex ] ; const tokenOut = tokens [ tokenInIndex + 1 ] ; const mostLiquidPool = getHighestLiquidityPool ( tokenIn . toLowerCase ( ) , tokenOut . toLowerCase ( ) , swapPairType , pools ) ; if ( mostLiquidPool === null ) return { } as NewPath ; pathPools . splice ( tokenInIndex , 0 , pools [ mostLiquidPool ] ) ; const path = createPath ( tokens , pathPools ) ; return path ; } export function createPath ( tokens , pools ) { let tI , tO ; const swaps = [ ] ; const poolPairData = [ ] ; let id = '' ; for ( let i = 0 ; i < pools . length ; i ++ ) { tI = tokens [ i ] ; tO = tokens [ i + 1 ] ; const poolPair = pools [ i ] . parsePoolPairData ( tI , tO ) ; poolPairData . push ( poolPair ) ; id = id + poolPair . id ; const swap = { pool : pools [ i ] . id , tokenIn : tI , tokenOut : tO , tokenInDecimals : poolPair . decimalsIn , tokenOutDecimals : poolPair . decimalsOut , } ; swaps . push ( swap ) ; } const path = { id , swaps , limitAmount : Zero , poolPairData , pools , } ; return path ; } export function getHighestLiquidityPool ( tokenIn , tokenOut , swapPairType , poolsOfInterest ) { let highestNormalizedLiquidity = ZERO ; let highestNormalizedLiquidityPoolId = null ; for ( const id in poolsOfInterest ) { const pool = poolsOfInterest [ id ] ; if ( swapPairType != pool . swapPairType ) continue ; const tokenListSet = new Set ( pool . tokensList ) ; if ( ! tokenListSet . has ( tokenIn . toLowerCase ( ) ) || ! tokenListSet . has ( tokenOut . toLowerCase ( ) ) ) continue ; const poolPairData = pool . parsePoolPairData ( tokenIn , tokenOut ) ; const normalizedLiquidity = pool . getNormalizedLiquidity ( poolPairData ) ; if ( normalizedLiquidity . isGreaterThanOrEqualTo ( highestNormalizedLiquidity ) ) { highestNormalizedLiquidity = normalizedLiquidity ; highestNormalizedLiquidityPoolId = id ; } } return highestNormalizedLiquidityPoolId ; } function composePaths ( paths ) { let id = '' ; let swaps = [ ] ; let poolPairData = [ ] ; let pools = [ ] ; for ( const path of paths ) { id += path . id ; swaps = swaps . concat ( path . swaps ) ; poolPairData = poolPairData . concat ( path . poolPairData ) ; pools = pools . concat ( path . pools ) ; } const path = { id : id , swaps : swaps , poolPairData : poolPairData , limitAmount : Zero , pools : pools , } ; return path ; } export function getPathsUsingStaBalPool ( tokenIn , tokenOut , poolsAll , poolsFiltered , config ) { const usdcConnectingPoolInfo = config . usdcConnectingPool ; if ( ! usdcConnectingPoolInfo ) return [ ] ; const usdcConnectingPool = poolsAll [ usdcConnectingPoolInfo . id ] ; const hopTokenStaBal = config . staBal3Pool ?. address ; if ( ! usdcConnectingPool || ! hopTokenStaBal ) return [ ] ; const metastablePoolIdIn = getHighestLiquidityPool ( tokenIn , hopTokenStaBal , SwapPairType . HopIn , poolsFiltered ) ; const metastablePoolIdOut = getHighestLiquidityPool ( hopTokenStaBal , tokenOut , SwapPairType . HopOut , poolsFiltered ) ; if ( metastablePoolIdIn && ! metastablePoolIdOut ) { const metaStablePoolIn = poolsFiltered [ metastablePoolIdIn ] ; const staBalPath = createPath ( [ tokenIn , hopTokenStaBal , usdcConnectingPoolInfo . usdc ] , [ metaStablePoolIn , usdcConnectingPool ] ) ; const mostLiquidLastPool = getHighestLiquidityPool ( usdcConnectingPoolInfo . usdc , tokenOut , SwapPairType . HopOut , poolsFiltered ) ; if ( mostLiquidLastPool === null ) return [ ] ; const lastPool = poolsFiltered [ mostLiquidLastPool ] ; const pathEnd = createPath ( [ usdcConnectingPoolInfo . usdc , tokenOut ] , [ lastPool ] ) ; return [ composePaths ( [ staBalPath , pathEnd ] ) ] ; } if ( ! metastablePoolIdIn && metastablePoolIdOut ) { const mostLiquidFirstPool = getHighestLiquidityPool ( tokenIn , usdcConnectingPoolInfo . usdc , SwapPairType . HopIn , poolsFiltered ) ; if ( mostLiquidFirstPool === null ) return [ ] ; const metaStablePoolIn = poolsFiltered [ metastablePoolIdOut ] ; const firstPool = poolsFiltered [ mostLiquidFirstPool ] ; const staBalPath = createPath ( [ usdcConnectingPoolInfo . usdc , hopTokenStaBal , tokenOut ] , [ usdcConnectingPool , metaStablePoolIn ] ) ; const pathStart = createPath ( [ tokenIn , usdcConnectingPoolInfo . usdc ] , [ firstPool ] ) ; return [ composePaths ( [ pathStart , staBalPath ] ) ] ; } return [ ] ; } export function parseToPoolsDict ( pools , timestamp ) { return Object . fromEntries ( cloneDeep ( pools ) . filter ( ( pool ) => pool . tokensList . length > 0 && pool . tokens [ 0 ] . balance !== '0' ) . map ( ( pool ) => [ pool . id , parseNewPool ( pool , timestamp ) ] ) . filter ( ( [ , pool ] ) => pool !== undefined ) ) ; }