import { EthLikeWeb3Public } from 'src/app/core/services/blockchain/blockchain-adapters/eth-like/web3-public/eth-like-web3-public' ; import { ROUTER_LIQUIDITY_POOLS_WITH_MODE , ROUTER_TOKENS_NET_MODE } from '@features/instant-trade/services/instant-trade-service/providers/ethereum/uni-swap-v3-service/utils/quoter-controller/constants/router-liquidity-pools' ; import { FeeAmount , LiquidityPool } from '@features/instant-trade/services/instant-trade-service/providers/ethereum/uni-swap-v3-service/utils/quoter-controller/models/liquidity-pool' ; import { EMPTY_ADDRESS } from '@shared/constants/blockchain/empty-address' ; import { FACTORY_CONTRACT_ABI , FACTORY_CONTRACT_ADDRESS } from '@features/instant-trade/services/instant-trade-service/providers/ethereum/uni-swap-v3-service/utils/quoter-controller/constants/factory-contract-data' ; import { MethodData } from '@shared/models/blockchain/method-data' ; import { PCacheable } from 'ts-cacheable' ; import BigNumber from 'bignumber.js' ; import { compareAddresses } from 'src/app/shared/utils/utils' ; import { SymbolToken } from '@shared/models/tokens/symbol-token' ; import { ContractData } from '@shared/models/blockchain/contract-data' ; import { UniSwapV3Route } from '@features/instant-trade/services/instant-trade-service/providers/ethereum/uni-swap-v3-service/models/uni-swap-v3-instant-trade' ; interface RecGraphVisitorOptions { routesLiquidityPools : LiquidityPool [ ] ; fromAmountAbsolute : string ; fromTokenAddress : string ; toTokenAddress : string ; } /**
 * Works with requests, related to Uniswap v3 liquidity pools.
 */ export class UniSwapV3QuoterController { private routerTokens : Record < string , SymbolToken > ; private routerLiquidityPools : LiquidityPool [ ] ; private readonly feeAmounts : FeeAmount [ ] ; param pools param initialTokenAddress return public static getEncodedPoolsPath ( pools , initialTokenAddress ) { let contractPath = initialTokenAddress . slice ( 2 ) . toLowerCase ( ) ; let lastTokenAddress = initialTokenAddress ; pools . forEach ( pool => { contractPath += pool . fee . toString ( 16 ) . padStart ( 6 , '0' ) ; const newToken = compareAddresses ( pool . token0 . address , lastTokenAddress ) ? pool . token1 : pool . token0 ; contractPath += newToken . address . slice ( 2 ) . toLowerCase ( ) ; lastTokenAddress = newToken . address ; } ) ; return `0x${ contractPath }` ; } param poolsPath param fromAmountAbsolute param fromTokenAddress param toTokenAddress private static getQuoterMethodData ( poolsPath , fromAmountAbsolute , fromTokenAddress , toTokenAddress ) { if ( poolsPath . length === 1 ) { return { poolsPath , methodData : { methodName : 'quoteExactInputSingle' , methodArguments : [ fromTokenAddress , toTokenAddress , poolsPath [ 0 ] . fee , fromAmountAbsolute , 0 ] } } ; } return { poolsPath , methodData : { methodName : 'quoteExactInput' , methodArguments : [ UniSwapV3QuoterController . getEncodedPoolsPath ( poolsPath , fromTokenAddress ) , fromAmountAbsolute ] } } ; } constructor ( private readonly web3Public , private readonly quoterContract ) { this . feeAmounts = [ 500 , 3000 , 10000 ] ; this . routerTokens = ROUTER_TOKENS_NET_MODE . mainnet ; this . routerLiquidityPools = ROUTER_LIQUIDITY_POOLS_WITH_MODE . mainnet ; } public setTestingMode ( ) { this . routerTokens = ROUTER_TOKENS_NET_MODE . testnet ; this . routerLiquidityPools = ROUTER_LIQUIDITY_POOLS_WITH_MODE . testnet ; } param firstTokenAddress param secondTokenAddress param firstTokenSymbol param secondTokenSymbol return @ PCacheable ( { maxAge : 1000 * 60 * 10 , maxCacheCount : 10 } ) private async getAllLiquidityPools ( firstTokenAddress , secondTokenAddress , firstTokenSymbol , secondTokenSymbol ) { const firstToken = { address : firstTokenAddress , symbol : firstTokenSymbol } ; const secondToken = { address : secondTokenAddress , symbol : secondTokenSymbol } ; let getPoolMethodArguments = [ ] ; getPoolMethodArguments . push ( ... Object . values ( this . routerTokens ) . filter ( routerToken => ! compareAddresses ( firstTokenAddress , routerToken . address ) && ! compareAddresses ( secondTokenAddress , routerToken . address ) ) . map ( routerToken => this . feeAmounts . map ( fee => [ { tokenA : firstToken , tokenB : routerToken , fee } , { tokenA : secondToken , tokenB : routerToken , fee } ] ) . flat ( ) ) . flat ( ) ) ; getPoolMethodArguments . push ( ... this . feeAmounts . map ( fee => ( { tokenA : firstToken , tokenB : secondToken , fee } ) ) ) ; getPoolMethodArguments = getPoolMethodArguments . filter ( methodArguments => ! this . routerLiquidityPools . find ( pool => pool . isPoolWithTokens ( methodArguments . tokenA . address , methodArguments . tokenB . address ) && pool . fee === methodArguments . fee ) ) ; const poolsAddresses = ( await this . web3Public . multicallContractMethods < { 0 : string ; } > ( FACTORY_CONTRACT_ADDRESS , FACTORY_CONTRACT_ABI , getPoolMethodArguments . map ( methodArguments => ( { methodName : 'getPool' , methodArguments : [ methodArguments . tokenA . address , methodArguments . tokenB . address , methodArguments . fee ] } ) ) ) ) . map ( result => result . output [ 0 ] ) ; return poolsAddresses . map ( ( poolAddress , index ) => { if ( poolAddress !== EMPTY_ADDRESS ) { return new LiquidityPool ( poolAddress , getPoolMethodArguments [ index ] . tokenA , getPoolMethodArguments [ index ] . tokenB , getPoolMethodArguments [ index ] . fee ) ; } return null ; } ) . filter ( pool => pool !== null ) . concat ( this . routerLiquidityPools ) ; } param fromAmountAbsolute param fromToken param toToken param routeMaxTransitPools public async getAllRoutes ( fromAmountAbsolute , fromToken , toToken , routeMaxTransitPools ) { const routesLiquidityPools = await this . getAllLiquidityPools ( fromToken . address , toToken . address , fromToken . symbol , toToken . symbol ) ; const options = { routesLiquidityPools , fromTokenAddress : fromToken . address , fromAmountAbsolute , toTokenAddress : toToken . address } ; const quoterMethodsData = [ ... Array ( routeMaxTransitPools + 1 ) ] . map ( ( _ , index ) => this . getQuoterMethodsData ( options , [ ] , fromToken . address , index ) ) . flat ( ) ; const results = await this . web3Public . multicallContractMethods < { 0 : string ; } > ( this . quoterContract . address , this . quoterContract . abi , quoterMethodsData . map ( quoterMethodData => quoterMethodData . methodData ) ) ; return results . map ( ( result , index ) => { if ( result . success ) { return { outputAbsoluteAmount : new BigNumber ( result . output [ 0 ] ) , poolsPath : quoterMethodsData [ index ] . poolsPath , initialTokenAddress : fromToken . address } ; } return null ; } ) . filter ( route => route !== null ) ; } param options param path param lastTokenAddress param maxTransitPools private private getQuoterMethodsData ( options , path , lastTokenAddress , maxTransitPools ) { const { routesLiquidityPools , fromAmountAbsolute , fromTokenAddress , toTokenAddress } = options ; if ( path . length === maxTransitPools ) { const pools = routesLiquidityPools . filter ( pool => pool . isPoolWithTokens ( lastTokenAddress , toTokenAddress ) ) ; return pools . map ( pool => UniSwapV3QuoterController . getQuoterMethodData ( path . concat ( pool ) , fromAmountAbsolute , fromTokenAddress , toTokenAddress ) ) ; } return routesLiquidityPools . filter ( pool => ! path . includes ( pool ) ) . map ( pool => { const methodsData = [ ] ; if ( compareAddresses ( pool . token0 . address , lastTokenAddress ) ) { const extendedPath = path . concat ( pool ) ; methodsData . push ( ... this . getQuoterMethodsData ( options , extendedPath , pool . token1 . address , maxTransitPools ) ) ; } if ( compareAddresses ( pool . token1 . address , lastTokenAddress ) ) { const extendedPath = path . concat ( pool ) ; methodsData . push ( ... this . getQuoterMethodsData ( options , extendedPath , pool . token0 . address , maxTransitPools ) ) ; } return methodsData ; } ) . flat ( ) ; } }