import { SOR } from '@balancer-labs/sor' ; import { JsonRpcProvider } from '@ethersproject/providers' ; import { ChainId , getRPCConstants , getTraderConstants , isSameAddress } from '@masknet/web3-shared-evm' ; import { ZERO } from '@masknet/web3-shared-base' ; import BigNumber from 'bignumber.js' ; import { first , memoize } from 'lodash-unified' ; import { currentChainIdSettings } from '../../../Wallet/settings' ; import { BALANCER_MAX_NO_POOLS , BALANCER_SOR_GAS_PRICE , BALANCER_SWAP_TYPE } from '../../constants' ; import { getFutureTimestamps } from '../../helpers/blocks' ; import type { Route } from '../../types' ; import { fetchBlockNumbersByTimestamps } from '../blocks' ; import { fetchLBP_PoolsByTokenAddress , fetchLBP_PoolTokenPrices , fetchLBP_PoolTokens } from '../LBP' ; const createSOR_ = memoize ( ( chainId ) => { const { RPC } = getRPCConstants ( chainId ) ; const providerURL = first ( RPC ) ; if ( ! providerURL ) throw new Error ( 'Unknown chain id.' ) ; return new SOR ( new JsonRpcProvider ( providerURL ) , BALANCER_SOR_GAS_PRICE , BALANCER_MAX_NO_POOLS , chainId , '' ) ; } , ( chainId ) => String ( chainId ) ) ; function createSOR ( chainId ) { const sor = createSOR_ ( chainId ) ; sor . poolsUrl = `${ getTraderConstants ( chainId ) . BALANCER_POOLS_URL }?timestamp=${ Date . now ( ) }` ; return sor ; } export async function updatePools ( force = false , chainId ? ) { const currentChainId = chainId ?? currentChainIdSettings . value ; const sor = createSOR ( currentChainId ) ; if ( ! sor . isAllFetched || force ) { sor . poolsUrl = `${ getTraderConstants ( currentChainId ) . BALANCER_POOLS_URL }?timestamp=${ Date . now ( ) }` ; await sor . fetchPools ( ) ; } } export async function getSwaps ( tokenIn , tokenOut , swapType , amount , targetChainId ? ) { const chainId = targetChainId ?? currentChainIdSettings . value ; const sor = createSOR ( chainId ) ; await sor . setCostOutputToken ( tokenOut ) ; updatePools ( ) ; const [ swaps , tradeAmount , spotPrice ] = await sor . getSwaps ( tokenIn , tokenOut , swapType , new BigNumber ( amount ) ) ; const totalSwapAmount = swaps . reduce ( ( total , rawHops ) => total . plus ( first ( rawHops ) ?. swapAmount || '0' ) , ZERO ) ; const pools = sor . onChainCache . pools ; const routes = swaps . map ( ( rawHops ) => { const swapAmount = new BigNumber ( first ( rawHops ) ?. swapAmount || '0' ) ; const share = swapAmount . div ( totalSwapAmount ) . toNumber ( ) ; const hops = rawHops . map ( ( rawHop ) => { const { swapAmount } = rawHop ; const tokenIn = rawHop . tokenIn ; const tokenOut = rawHop . tokenOut ; const rawPool = pools . find ( ( pool ) => pool . id === rawHop . pool ) ; if ( ! rawPool ) return { } ; const totalWeight = new BigNumber ( rawPool . totalWeight ) ; const pool = { address : rawPool . id , tokens : rawPool . tokens . map ( ( token ) => { const address = token . address ; const weight = new BigNumber ( token . denormWeight ) ; const share = weight . div ( totalWeight ) . toNumber ( ) ; return { address , share , } ; } ) . sort ( ( a , b ) => { if ( isSameAddress ( a . address , tokenIn ) || isSameAddress ( b . address , tokenOut ) ) return - 1 ; if ( isSameAddress ( a . address , tokenOut ) || isSameAddress ( b . address , tokenIn ) ) return 1 ; return a . share - b . share ; } ) . filter ( ( token , index , tokens ) => { return index < 2 || index > tokens . length - 3 ; } ) , } ; return { pool , tokenIn , tokenOut , swapAmount , } ; } ) ; return { share , hops , } ; } ) as Route [ ] ; return { swaps : [ swaps , tradeAmount , spotPrice ] as const , routes , } ; } export async function fetchTokenPrices ( address , duration , size ) { const pools = await fetchLBP_PoolsByTokenAddress ( address ) ; const pool = first ( pools ) ; if ( ! pool ) return [ ] ; const timestamps = getFutureTimestamps ( pool . createTime , duration , size ) ; const blockNumbers = await fetchBlockNumbersByTimestamps ( timestamps ) ; if ( ! blockNumbers . length ) return [ ] ; const prices = await fetchLBP_PoolTokenPrices ( pool . id , address , blockNumbers . map ( ( x ) => x . blockNumber ) ) ; return prices . map ( ( x , i ) => ( { timestamp : timestamps [ i ] , blockNumber : x . blockNumber , price : Number . parseFloat ( x . price ) , } ) ) ; } export async function fetchPoolTokens ( address , duration , size ) { const pools = await fetchLBP_PoolsByTokenAddress ( address ) ; const pool = first ( pools ) ; if ( ! pool ) return [ ] ; const timestamps = getFutureTimestamps ( pool . createTime , duration , size ) ; const blockNumbers = await fetchBlockNumbersByTimestamps ( timestamps ) ; if ( ! blockNumbers . length ) return [ ] ; const poolTokens = await fetchLBP_PoolTokens ( pool . id , blockNumbers . map ( ( x ) => x . blockNumber ) ) ; return [ ] ; }