diff --git a/src/components/layout/SwapRoutes.tsx b/src/components/layout/SwapRoutes.tsx index 740f0d24a..879d08bec 100644 --- a/src/components/layout/SwapRoutes.tsx +++ b/src/components/layout/SwapRoutes.tsx @@ -15,7 +15,11 @@ import { toRealSymbol } from '../../utils/token'; import { EstimateSwapView } from 'src/services/swap'; -import { getPoolAllocationPercents, percent } from '../../utils/numbers'; +import { + getPoolAllocationPercents, + percent, + getRouteAllocationPercents, +} from '../../utils/numbers'; import { Pool } from '../../services/pool'; import { FaAngleUp, FaAngleDown } from '../reactIcons'; import { Card } from '../card/Card'; @@ -72,8 +76,14 @@ import { displayNumberToAppropriateDecimals } from '../../services/commonV3'; import { numberWithCommas } from '../../pages/Orderly/utiles'; import { get_pool_name, openUrl } from '../../services/commonV3'; import getConfigV2 from '../../services/configV2'; + import { REF_FI_BEST_MARKET_ROUTE } from '../../state/swap'; import { PolygonRight } from '../../pages/Orderly/components/Common/Icons'; +import { + IEstimateSwapServerView, + IServerRoute, + getTokensOfRoute, +} from '../../services/smartRouterFromServer'; const configV2 = getConfigV2(); export const GetPriceImpact = ( value: string, @@ -717,16 +727,23 @@ export const getDexAction = (market: SwapMarket) => { export const SwapRoute = ({ route, - p, market, + routeServer, }: { - route: EstimateSwapView[]; - p: string; - tokenIn: TokenMetadata; - tokenOut: TokenMetadata; + route?: EstimateSwapView[]; market: SwapMarket; + routeServer?: IServerRoute; }) => { - const tokens = route[0].tokens; + const [tokens, setTokens] = useState([]); + useEffect(() => { + if (route) { + setTokens(route[0].tokens); + } else if (routeServer) { + getTokensOfRoute(routeServer).then((res) => { + setTokens(res); + }); + } + }, [route, routeServer]); const { swapType } = useContext(SwapProContext); @@ -771,6 +788,7 @@ export const SwapRoute = ({ tokens.map((t, i) => { return (
7 ? 'xsm:overflow-hidden' @@ -802,9 +820,11 @@ export const SwapRoute = ({ export const SwapRouteMoreThan2 = ({ market, trade, + throughPools, }: { market: SwapMarket; trade: ExchangeEstimate; + throughPools: number; }) => { const intl = useIntl(); @@ -846,7 +866,7 @@ export const SwapRouteMoreThan2 = ({ id: 'steps_in_the_route_zh', })} - {trade.estimates.length} + {throughPools}
- + {toRealSymbol(token.symbol)}
@@ -1742,7 +1762,7 @@ export const RightBracket = ({ size }: { size: number }) => {
@@ -1754,7 +1774,7 @@ export const LeftBracket = ({ size }: { size: number }) => {
@@ -1770,6 +1790,47 @@ export const TradeRoute = ({ tokenIn: TokenMetadata; tokenOut: TokenMetadata; }) => { + const [estimatesServerUI, setEstimatesServerUI] = + useState(); + const { estimates, estimatesServer } = trade || {}; + const identicalRoutes = estimates + ? separateRoutes(estimates, estimates[estimates.length - 1].outputToken) + : []; + + const pools = identicalRoutes.map((r) => r[0]).map((hub) => hub.pool); + useEffect(() => { + if (estimatesServer) { + getTokensDataOfEstimatesServer(); + } else { + setEstimatesServerUI(undefined); + } + }, [JSON.stringify(estimatesServer || {})]); + async function getTokensDataOfEstimatesServer() { + const pending = estimatesServer.routes.map((route) => + getTokensOfRoute(route) + ); + const tokens_of_routes = await Promise.all(pending); + estimatesServer.routes.map((route, index) => { + route.tokens = tokens_of_routes[index]; + }); + setEstimatesServerUI(estimatesServer); + } + const percents = useMemo(() => { + try { + return getPoolAllocationPercents(pools); + } catch (error) { + if (identicalRoutes.length === 0) return ['100']; + else return identicalRoutes.map((r) => r[0].percent); + } + }, [identicalRoutes, pools]); + const percentsServer = useMemo(() => { + const routes = estimatesServer?.routes || []; + try { + return getRouteAllocationPercents(routes); + } catch (error) { + return ['100']; + } + }, [(estimatesServer?.routes || []).length]); if (!tokenIn || !tokenOut) return null; if (!trade || !trade?.availableRoute || tokenIn?.id === tokenOut?.id) { @@ -1801,70 +1862,102 @@ export const TradeRoute = ({
); } - - const { estimates } = trade; - - const { market } = trade; - - const identicalRoutes = separateRoutes( - estimates, - estimates[estimates.length - 1].outputToken - ); - - const pools = identicalRoutes.map((r) => r[0]).map((hub) => hub.pool); - - const percents = useMemo(() => { - try { - return getPoolAllocationPercents(pools); - } catch (error) { - if (identicalRoutes.length === 0) return ['100']; - else return identicalRoutes.map((r) => r[0].percent); - } - }, [identicalRoutes, pools]); - + const routeLength = + identicalRoutes.length || trade.estimatesServer?.routes?.length; return (
- -
- {identicalRoutes.map((route, j) => { - return ( -
- {percents[j]}% + + {/* from script routes */} + {trade.estimates ? ( +
+ {identicalRoutes.map((route, j) => { + return (
-
- {route[0].tokens - .slice(1, route[0].tokens.length) - .map((t, i) => { + > + {percents[j]}% +
+
+ {route[0].tokens + .slice(1, route[0].tokens.length) + .map((t, i) => { + return ( + <> + + {t.id !== + route[0].tokens[route[0].tokens.length - 1]?.id && ( +
+ +
+ )} + + ); + })} +
+ + +
+ ); + })} +
+ ) : null} + + {/* from server routes */} + {estimatesServerUI ? ( +
+ {estimatesServerUI.routes.map((route, j) => { + const { pools, tokens } = route; + return ( +
+ + {percentsServer[j]}% + +
+
+ {tokens?.slice(1, tokens.length).map((t, i) => { return ( <> - {t.id !== - route[0].tokens[route[0].tokens.length - 1]?.id && ( + {t.id !== tokens[tokens.length - 1]?.id && (
@@ -1872,14 +1965,16 @@ export const TradeRoute = ({ ); })} +
+ +
+ ); + })} +
+ ) : null} - -
- ); - })} -
- +
@@ -1900,7 +1995,7 @@ export const TradeRouteModal = ( /> } {...props} - customWidth={isMobile() ? '95vw' : '700px'} + customWidth={isMobile() ? '95vw' : '800px'} >
r[0]).map((hub) => hub.pool); - - const percents = useMemo(() => { - try { - return getPoolAllocationPercents(pools); - } catch (error) { - if (identicalRoutes.length === 0) return ['100']; - else return identicalRoutes.map((r) => r[0].percent); - } - }, [identicalRoutes, pools]); - const [showRouteDetail, setShowRouteDetail] = useState(false); - + const { estimates, tokenIn, tokenOut, market, estimatesServer } = trade; + const { swapType } = useContext(SwapProContext); + if (!estimates && !estimatesServer) return null; + const identicalRoutes = estimates + ? separateRoutes(estimates, estimates[estimates.length - 1].outputToken) + : []; + const estimatesServerRoutes = estimatesServer?.routes || []; + let throughPools = 0; + if (estimatesServer) { + throughPools = estimatesServer.routes + .reduce((acc, cur) => { + return acc.plus(cur.pools.length); + }, Big(0)) + .toNumber(); + } else if (estimates) { + throughPools = estimates.length; + } + const isMoreThan2 = + identicalRoutes?.length > 1 || estimatesServer?.routes?.length > 1; return (
{swapType === SWAP_TYPE.LITE ? ( @@ -351,20 +347,19 @@ export function AutoRouter({ trade }: { trade: ExchangeEstimate }) { } }} > - {trade.estimates.length > 2 ? ( - - ) : ( - identicalRoutes.map((route, index) => ( - - )) - )} + {isMoreThan2 ? ( + + ) : null} + {!isMoreThan2 && identicalRoutes.length > 0 ? ( + + ) : null} + {!isMoreThan2 && estimatesServerRoutes.length > 0 ? ( + + ) : null}
= ({ children }) => { if (!selector) { return; } - const subscription = selector.store.observable .pipe( map((state) => state.accounts), @@ -242,6 +242,15 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { return () => subscription.unsubscribe(); }, [selector, accountId]); + useEffect(() => { + const selectedWalletId = selector?.store?.getState()?.selectedWalletId; + if (accountId && selectedWalletId) { + addUserWallet({ + account_id: accountId, + wallet_address: selectedWalletId, + }); + } + }, [selector?.store?.getState()?.selectedWalletId, accountId]); const getAllKeys = async (accountId: string) => { const account = await near.account(accountId); diff --git a/src/pages/SwapPage.tsx b/src/pages/SwapPage.tsx index 6bc777951..e51c4629b 100644 --- a/src/pages/SwapPage.tsx +++ b/src/pages/SwapPage.tsx @@ -36,6 +36,7 @@ import OrderlyAirDropPop from '../components/orderlyAirdrop/index'; import OrderlyAirDropPopMobile from '../components/orderlyAirdropMobile/index'; import { useOrderlyGuidePopStore } from '../stores/orderlyGuidePop'; import { isMobile } from 'src/utils/device'; +import { IEstimateSwapServerView } from '~src/services/smartRouterFromServer'; export const SWAP_MODE_KEY = 'SWAP_MODE_VALUE'; @@ -71,6 +72,7 @@ export type SwapContractType = export interface ExchangeEstimate { estimates?: EstimateSwapView[]; + estimatesServer?: IEstimateSwapServerView; fee?: number; priceImpact?: string; minAmountOut?: string; diff --git a/src/services/config.ts b/src/services/config.ts index af9ba52e8..aeefe0e12 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -149,8 +149,8 @@ export default function getConfig( explorerUrl: 'https://nearblocks.io', pikespeakUrl: 'https://pikespeak.ai', nearExplorerUrl: 'https://explorer.near.org/', - indexerUrl: 'https://api.ref.finance', - // indexerUrl: 'https://apiself.cclp.finance', + // indexerUrl: 'https://api.ref.finance', + indexerUrl: 'https://mainnet-indexer.ref-finance.com', sodakiApiUrl: 'https://api.stats.ref.finance/api', newSodakiApiUrl: 'https://api.data-service.ref.finance/api', txIdApiUrl: 'https://api3.nearblocks.io', diff --git a/src/services/indexer.ts b/src/services/indexer.ts index 770efa612..a5293eb87 100644 --- a/src/services/indexer.ts +++ b/src/services/indexer.ts @@ -1174,3 +1174,15 @@ export const getTokens = async () => { return tokens; }); }; +export const addUserWallet = async (params) => { + return await fetch(config.indexerUrl + '/add-user-wallet', { + method: 'POST', + headers: { + 'Content-type': 'application/json; charset=UTF-8', + ...getAuthenticationHeaders('/add-user-wallet'), + }, + body: JSON.stringify(params), + }).catch(async (res) => { + console.log('add user wallet failed', res); + }); +}; diff --git a/src/services/pool.ts b/src/services/pool.ts index 8a853e363..07a81f9c2 100644 --- a/src/services/pool.ts +++ b/src/services/pool.ts @@ -462,6 +462,83 @@ export const getPoolsByTokens = async ({ }); return { filteredPools, pool_protocol }; }; +export const getAllPoolsByTokens = async (): Promise<{ + filteredPools: Pool[]; +}> => { + let pools; + + const cachePools = async (pools: any) => { + await db.cachePoolsByTokens( + pools.filter(filterBlackListPools).filter((p: any) => isNotStablePool(p)) + ); + }; + try { + const cachedPoolProtocol = sessionStorage.getItem(REF_FI_POOL_PROTOCOL); + + if (cachedPoolProtocol === 'rpc') { + pools = await db.getAllPoolsTokens(); + + if (!pools || pools.length === 0) { + pools = await fetchPoolsRPC(); + await cachePools(pools); + } + } else { + const poolsRaw = await db.queryTopPools(); + if (poolsRaw && poolsRaw?.length > 0) { + pools = poolsRaw.map((p) => { + const parsedP = parsePool({ + ...p, + share: p.shares_total_supply, + id: Number(p.id), + tvl: Number(p.tvl), + }); + + return { + ...parsedP, + Dex: 'ref', + }; + }); + } else { + const poolsRaw = await fetchPoolsIndexer(); + + await db.cacheTopPools(poolsRaw); + + pools = poolsRaw.map((p: any) => { + return { + ...parsePool(p), + Dex: 'ref', + }; + }); + + await cachePools(pools); + } + } + } catch (error) { + const { pools: poolsRaw, protocol } = await fetchTopPools(); + + if (protocol === 'indexer') { + await db.cacheTopPools(poolsRaw); + pools = poolsRaw.map((p: any) => { + return { + ...parsePool(p), + Dex: 'ref', + }; + }); + sessionStorage.setItem(REF_FI_POOL_PROTOCOL, 'indexer'); + } else { + pools = poolsRaw; + sessionStorage.setItem(REF_FI_POOL_PROTOCOL, 'rpc'); + } + await cachePools(pools); + await cacheAllDCLPools(); + } + const filteredPools = pools + .filter(filterBlackListPools) + .filter((pool: any) => { + return isNotStablePool(pool); + }); + return { filteredPools }; +}; export const getPoolsByTokensAurora = async ({ tokenInId, @@ -542,12 +619,11 @@ export const getPoolsByTokensAurora = async ({ return filtered_pools; }; -export const getRefPoolsByToken1ORToken2 = async ( - tokenId1: string, - tokenId2: string -) => { - return await db.queryPoolsByTokens2(tokenId1, tokenId2); - //return await db.poolsTokens; +export const getRefPoolsByToken1ORToken2 = async () => { + return await db.queryPoolsByTokens2(); +}; +export const getRefPoolsByToken1ORToken2Parsed = async () => { + return await db.getAllPoolsTokens(); }; export const getPool = async (id: number): Promise => { diff --git a/src/services/smartRouterFromServer.ts b/src/services/smartRouterFromServer.ts new file mode 100644 index 000000000..307119b9d --- /dev/null +++ b/src/services/smartRouterFromServer.ts @@ -0,0 +1,226 @@ +import Big from 'big.js'; +import { + toNonDivisibleNumber, + scientificNotationToString, + calculateMarketPrice, +} from '../utils/numbers'; +import { TokenMetadata, ftGetTokenMetadata } from './ft-contract'; +import { getAllPoolsByTokens, getAllStablePoolsFromCache, Pool } from './pool'; +import { isStablePool } from '../services/near'; +export interface IEstimateSwapServerView { + amount_in: string; + amount_out: string; + contract_in: string; + contract_out: string; + routes: IServerRoute[]; + contract?: string; +} +export interface IServerRoute { + amount_in: string; + min_amount_out: string; + pools: IServerPool[]; + tokens?: TokenMetadata[]; +} +export interface IServerPool { + amount_in: string; + min_amount_out: string; + pool_id: string | number; + token_in: string; + token_out: string; +} + +export const estimateSwapFromServer = async ({ + tokenIn, + tokenOut, + amountIn, + slippage, + supportLedger, +}) => { + const timeoutDuration = 5000; + const controller = new AbortController(); + const env = process.env.REACT_APP_NEAR_ENV; + const timeOutId = setTimeout(() => { + controller.abort(); + }, timeoutDuration); + const domain = + env === 'pub-testnet' + ? 'smartroutertest.refburrow.top' + : 'smartrouter.ref.finance'; + const resultFromServer = await fetch( + `https://${domain}/findPath?amountIn=${amountIn}&tokenIn=${ + tokenIn.id + }&tokenOut=${tokenOut.id}&pathDeep=${supportLedger ? 1 : 3}&slippage=${ + Number(slippage) / 100 + }`, + { + signal: controller.signal, + } + ) + .then((res) => { + return res.json(); + }) + .finally(() => { + clearTimeout(timeOutId); + }); + return resultFromServer; +}; + +export async function getAvgFeeFromServer({ + estimatesFromServer, + setAvgFee, + tokenInAmount, + tokenIn, + poolsMap, +}: { + tokenInAmount: string; + tokenIn: TokenMetadata; + estimatesFromServer: IEstimateSwapServerView; + setAvgFee: (fee: number) => void; + poolsMap: Record; +}) { + let avgFee: number = 0; + const { routes } = estimatesFromServer; + routes.forEach((route) => { + const { amount_in, pools } = route; + const allocation = new Big(amount_in).div( + new Big(toNonDivisibleNumber(tokenIn.decimals, tokenInAmount)) + ); + const routeFee = pools.reduce((acc, cur) => { + return acc.plus(poolsMap[cur.pool_id]?.fee || 0); + }, new Big(0)); + avgFee += allocation.mul(routeFee).toNumber(); + }); + setAvgFee(avgFee); +} +export async function getUsedPools(routes: IServerRoute[]) { + const { topPools, stablePools } = await getAllPoolsFromCache(); + const pools: Record = {}; + routes.forEach((route) => { + route.pools.forEach((cur) => { + let p; + p = topPools.find((p) => +p.id === +cur.pool_id); + if (!p) { + p = stablePools.find((p) => +p.id === +cur.pool_id); + } + if (p) { + pools[p.id] = p; + } + }); + }); + return pools; +} +export async function getUsedTokens(routes: IServerRoute[]) { + const pending = routes.map((route) => getTokensOfRoute(route)); + const tokensList = await Promise.all(pending); + const list = tokensList.flat(); + const tokens: Record = list.reduce((acc, cur) => { + acc[cur.id] = cur; + return acc; + }, {}); + return tokens; +} +export async function getTokensOfRoute(route: IServerRoute) { + const tokenIds = route.pools.reduce((acc, cur, index) => { + if (index == 0) { + acc.push(cur.token_in, cur.token_out); + } else { + acc.push(cur.token_out); + } + return acc; + }, []); + const pending = tokenIds.map((tokenId) => ftGetTokenMetadata(tokenId)); + const tokens = await Promise.all(pending); + return tokens as TokenMetadata[]; +} +export async function getAllPoolsFromCache() { + const { filteredPools: topPools } = await getAllPoolsByTokens(); + const { allStablePools } = await getAllStablePoolsFromCache(); + const topPoolsMap = topPools.reduce((acc, p) => { + acc[p.id] = p; + return acc; + }, {}); + const stablePoolsMap = allStablePools.reduce((acc, p) => { + acc[p.id] = p; + return acc; + }, {}); + return { + topPools, + stablePools: allStablePools, + poolsMap: { ...topPoolsMap, ...stablePoolsMap }, + }; +} +export async function getPriceImpactFromServer({ + estimatesFromServer, + tokenIn, + tokenOut, + tokenInAmount, + tokenOutAmount, + tokenPriceList, + setPriceImpactValue, + poolsMap, + tokensMap, +}: { + estimatesFromServer: IEstimateSwapServerView; + tokenInAmount: string; + tokenIn: TokenMetadata; + tokenOut: TokenMetadata; + tokenOutAmount: string; + tokenPriceList: any; + setPriceImpactValue: (impact: string) => void; + poolsMap: Record; + tokensMap: Record; +}) { + try { + const newPrice = new Big(tokenInAmount || '0').div( + new Big(tokenOutAmount || '1') + ); + const { routes } = estimatesFromServer; + const priceIn = tokenPriceList[tokenIn.id]?.price; + const priceOut = tokenPriceList[tokenOut.id]?.price; + const priceImpactForRoutes = routes.map((route) => { + let oldPrice: Big; + if (!!priceIn && !!priceOut) { + oldPrice = new Big(priceOut).div(new Big(priceIn)); + + return newPrice.lt(oldPrice) + ? '0' + : newPrice.minus(oldPrice).div(newPrice).times(100).abs().toFixed(); + } + const pools = route.pools.map((pool) => poolsMap[pool.pool_id]); + oldPrice = pools.reduce((acc, pool, i) => { + const curRate = isStablePool(pool.id) + ? new Big(pool.rates[route.pools[i].token_out]).div( + new Big(pool.rates[route.pools[i].token_in]) + ) + : new Big( + scientificNotationToString( + calculateMarketPrice( + pool, + tokensMap[route.pools[i].token_in], + tokensMap[route.pools[i].token_out] + ).toString() + ) + ); + + return acc.mul(curRate); + }, new Big(1)); + return newPrice.lt(oldPrice) + ? '0' + : newPrice.minus(oldPrice).div(newPrice).times(100).abs().toFixed(); + }); + const rawRes = priceImpactForRoutes.reduce( + (pre, cur, i) => { + return pre.plus( + new Big(routes[i].amount_in) + .div(new Big(toNonDivisibleNumber(tokenIn.decimals, tokenInAmount))) + .mul(cur) + ); + }, + + new Big(0) + ); + setPriceImpactValue(scientificNotationToString(rawRes.toString())); + } catch (error) { + setPriceImpactValue('0'); + } +} diff --git a/src/services/swap.ts b/src/services/swap.ts index aeddae963..cb83939af 100644 --- a/src/services/swap.ts +++ b/src/services/swap.ts @@ -48,7 +48,7 @@ import { StablePool, } from './pool'; -// import { stableSmart } from './smartRouteLogic'; +import { cacheAllDCLPools } from './swapV3'; import { createSmartRouteLogicWorker, transformWorkerResult, @@ -60,6 +60,13 @@ import { nearWithdrawTransaction, WRAP_NEAR_CONTRACT_ID, } from './wrap-near'; +import { + IEstimateSwapServerView, + estimateSwapFromServer, + getUsedPools, + getUsedTokens, +} from './smartRouterFromServer'; +import { REF_DCL_POOL_CACHE_KEY } from '../state/swap'; export const REF_FI_SWAP_SIGNAL = 'REF_FI_SWAP_SIGNAL_KEY'; const { NO_REQUIRED_REGISTRATION_TOKEN_IDS } = getConfigV2(); @@ -90,6 +97,7 @@ interface EstimateSwapOptions { setSwapsToDoTri?: (todos: EstimateSwapView[]) => void; setSwapsToDoRef?: (todos: EstimateSwapView[]) => void; proGetCachePool?: boolean; + slippage?: number; } export interface ReservesMap { @@ -316,46 +324,9 @@ export const estimateSwapFlow = async ({ //@ts-ignore if (tokenFlow?.data === null || tokenFlow === null) throwNoPoolError(); - - // const res = await Promise.all( - // tokenFlow - // .map((flow) => { - // return flow.pool_ids.map(async (pool_id, i) => { - // const pool = isStablePool(pool_id) - // ? (await getStablePoolFromCache(pool_id.toString()))[0] - // : await db - // .queryTopPoolsByIds({ - // poolIds: [pool_id], - // }) - // .then((pools) => pools?.[0]); - - // return { - // estimate: - // i === flow.pool_ids.length - 1 - // ? new Big(flow.amount).toFixed(tokenOut.decimals) - // : '', - // inputToken: flow.all_tokens[i], - // outputToken: flow.all_tokens[i + 1], - // tokens: await Promise.all( - // flow.all_tokens.map((t) => ftGetTokenMetadata(t)) - // ), - // percent: flow.swap_ratio.toString(), - // partialAmountIn: - // i === 0 - // ? new Big(parsedAmountIn) - // .mul(new Big(flow.swap_ratio)) - // .div(100) - // .toFixed(0, 0) - // : '', - // pool: pool, - // } as EstimateSwapView; - // }); - // }) - // .flat() - // ); - return { estimates: [] }; }; +const SHUTDOWN_SRVER = false; export const estimateSwap = async ({ tokenIn, tokenOut, @@ -365,6 +336,95 @@ export const estimateSwap = async ({ loadingTrigger, supportLedger, proGetCachePool, + slippage, +}: EstimateSwapOptions): Promise<{ + estimates?: EstimateSwapView[]; + estimatesFromServer?: IEstimateSwapServerView; + tag: string; + source: 'script' | 'server'; + poolsMap?: Record; + tokensMap?: Record; +}> => { + if (!SHUTDOWN_SRVER) { + const resultFromServer = await estimateSwapFromServer({ + tokenIn, + tokenOut, + amountIn: toNonDivisibleNumber(tokenIn.decimals, amountIn), + slippage, + supportLedger, + }).catch(() => ({})); + if ( + !( + resultFromServer?.result_code !== 0 || + !resultFromServer?.result_data?.routes?.length + ) + ) { + const routes = resultFromServer.result_data?.routes; + let poolsMap = {}; + let tokensMap = {}; + try { + if (!localStorage.getItem(REF_DCL_POOL_CACHE_KEY)) { + await cacheAllDCLPools(); + } + } catch (error) {} + try { + poolsMap = await getUsedPools(routes); + } catch (error) {} + try { + tokensMap = await getUsedTokens(routes); + } catch (error) {} + return { + estimatesFromServer: resultFromServer.result_data, + tag: `${tokenIn.id}-${toNonDivisibleNumber( + tokenIn.decimals, + amountIn + )}-${tokenOut.id}`, + source: 'server', + poolsMap, + tokensMap, + }; + } else { + const resultFromScript = await estimateSwapFromScript({ + tokenIn, + tokenOut, + amountIn, + intl, + setLoadingData, + loadingTrigger, + supportLedger, + proGetCachePool, + }); + return { + ...resultFromScript, + source: 'script', + }; + } + } else { + const resultFromScript = await estimateSwapFromScript({ + tokenIn, + tokenOut, + amountIn, + intl, + setLoadingData, + loadingTrigger, + supportLedger, + proGetCachePool, + }); + return { + ...resultFromScript, + source: 'script', + }; + } +}; +export const estimateSwapFromScript = async ({ + tokenIn, + tokenOut, + amountIn, + intl, + setLoadingData, + loadingTrigger, + supportLedger, + proGetCachePool, }: EstimateSwapOptions): Promise<{ estimates: EstimateSwapView[]; tag: string; @@ -422,7 +482,7 @@ export const estimateSwap = async ({ return { estimates: supportLedgerRes, tag }; } - const orpools = await getRefPoolsByToken1ORToken2(tokenIn.id, tokenOut.id); + const orpools = await getRefPoolsByToken1ORToken2(); let res; let smartRouteV2OutputEstimate; @@ -1032,12 +1092,13 @@ export async function getHybridStableSmart( interface SwapOptions { useNearBalance?: boolean; - swapsToDo: EstimateSwapView[]; + swapsToDo?: EstimateSwapView[]; tokenIn: TokenMetadata; tokenOut: TokenMetadata; amountIn: string; - slippageTolerance: number; + slippageTolerance?: number; swapMarket?: SwapMarket; + swapsToDoServer?: IEstimateSwapServerView; } export const swap = async ({ @@ -1088,15 +1149,118 @@ export const instantSwap = async ({ }); } }; +export const swapFromServer = async ({ + tokenIn, + tokenOut, + amountIn, + swapsToDoServer, +}: SwapOptions) => { + const transactions: Transaction[] = []; + const tokenOutActions: RefFiFunctionCallOptions[] = []; + const { contract_out, routes } = swapsToDoServer; + const registerToken = async (token: TokenMetadata) => { + const tokenRegistered = await ftGetStorageBalance(token.id).catch(() => { + throw new Error(`${token.id} doesn't exist.`); + }); + if (tokenRegistered === null) { + if (NO_REQUIRED_REGISTRATION_TOKEN_IDS.includes(token.id)) { + const r = await native_usdc_has_upgrated(token.id); + if (r) { + tokenOutActions.push({ + methodName: 'storage_deposit', + args: { + registration_only: true, + account_id: getCurrentWallet()?.wallet?.getAccountId(), + }, + gas: '30000000000000', + amount: STORAGE_TO_REGISTER_WITH_MFT, + }); + } else { + tokenOutActions.push({ + methodName: 'register_account', + args: { + account_id: getCurrentWallet()?.wallet?.getAccountId(), + }, + gas: '10000000000000', + }); + } + } else { + tokenOutActions.push({ + methodName: 'storage_deposit', + args: { + registration_only: true, + account_id: getCurrentWallet()?.wallet?.getAccountId(), + }, + gas: '30000000000000', + amount: STORAGE_TO_REGISTER_WITH_MFT, + }); + } + transactions.push({ + receiverId: token.id, + functionCalls: tokenOutActions, + }); + } + }; + + if (tokenOut.id !== contract_out) { + return window.location.reload(); + } + + //making sure all actions get included. + await registerToken(tokenOut); + const actionsList = []; + routes.forEach((route) => { + route.pools.forEach((pool) => { + if (+pool.amount_in == 0) { + delete pool.amount_in; + } + pool.pool_id = Number(pool.pool_id); + actionsList.push(pool); + }); + }); + transactions.push({ + receiverId: tokenIn.id, + functionCalls: [ + { + methodName: 'ft_transfer_call', + args: { + receiver_id: REF_FI_CONTRACT_ID, + amount: toNonDivisibleNumber(tokenIn.decimals, amountIn), + msg: JSON.stringify({ + force: 0, + actions: actionsList, + ...(tokenOut.symbol == 'NEAR' ? { skip_unwrap_near: false } : {}), + }), + }, + gas: '180000000000000', + amount: ONE_YOCTO_NEAR, + }, + ], + }); + + if (tokenIn.id === WRAP_NEAR_CONTRACT_ID && tokenIn?.symbol == 'NEAR') { + transactions.unshift(nearDepositTransaction(amountIn)); + } + if (tokenIn.id === WRAP_NEAR_CONTRACT_ID) { + const registered = await ftGetStorageBalance(WRAP_NEAR_CONTRACT_ID); + if (registered === null) { + transactions.unshift({ + receiverId: WRAP_NEAR_CONTRACT_ID, + functionCalls: [registerAccountOnToken()], + }); + } + } + + return executeMultipleTransactions(transactions); +}; export const nearInstantSwap = async ({ tokenIn, tokenOut, amountIn, swapsToDo, slippageTolerance, -}: // minAmountOut, -SwapOptions) => { +}: SwapOptions) => { const transactions: Transaction[] = []; const tokenOutActions: RefFiFunctionCallOptions[] = []; @@ -1228,31 +1392,6 @@ SwapOptions) => { if (tokenIn.id === WRAP_NEAR_CONTRACT_ID && tokenIn?.symbol == 'NEAR') { transactions.unshift(nearDepositTransaction(amountIn)); } - // if (tokenOut.id === WRAP_NEAR_CONTRACT_ID) { - // const outEstimate = new Big(0); - // const routes = separateRoutes(swapsToDo, tokenOut.id); - - // const bigEstimate = routes.reduce((acc, cur) => { - // const curEstimate = round( - // 24, - // toNonDivisibleNumber( - // 24, - // percentLess(slippageTolerance, cur[cur.length - 1].estimate) - // ) - // ); - // return acc.plus(curEstimate); - // }, outEstimate); - - // const minAmountOut = toReadableNumber( - // 24, - // scientificNotationToString(bigEstimate.toString()) - // ); - - // if (tokenOut.symbol == 'NEAR') { - // transactions.push(nearWithdrawTransaction(minAmountOut)); - // } - // } - if (tokenIn.id === WRAP_NEAR_CONTRACT_ID) { const registered = await ftGetStorageBalance(WRAP_NEAR_CONTRACT_ID); if (registered === null) { diff --git a/src/state/pool.ts b/src/state/pool.ts index 9b212f9cb..5593a4d23 100644 --- a/src/state/pool.ts +++ b/src/state/pool.ts @@ -525,49 +525,6 @@ export const useMorePoolIds = ({ return ids; }; -// export const usePoolsMorePoolIds = () => { -// // top pool id to more pool ids:Array -// const [poolsMorePoolIds, setMorePoolIds] = useState>( -// {} -// ); - -// const getAllPoolsTokens = async () => { -// return (await getAllPoolsIndexer()).filter( -// (p: Pool) => p.pool_kind && p.pool_kind === 'SIMPLE_POOL' -// ); -// }; - -// useEffect(() => { -// getAllPoolsTokens().then((res) => { -// const poolsMorePoolIds = res.map((p: any) => { -// const id1 = p.tokenIds[0]; -// const id2 = p.tokenIds[1]; - -// return res -// .filter( -// (resP: any) => -// resP.tokenIds.includes(id1) && resP.tokenIds.includes(id2) -// ) -// .map((a: any) => a.id.toString()); -// }); - -// const parsedIds = poolsMorePoolIds.reduce( -// (acc: any, cur: any, i: number) => { -// return { -// ...acc, -// [res[i].id.toString()]: cur, -// }; -// }, -// {} -// ); - -// setMorePoolIds(parsedIds); -// }); -// }, []); - -// return poolsMorePoolIds; -// }; - export const useMorePools = ({ tokenIds, order, diff --git a/src/state/swap.tsx b/src/state/swap.tsx index a270a97a6..b98cd6f23 100644 --- a/src/state/swap.tsx +++ b/src/state/swap.tsx @@ -46,7 +46,7 @@ import { PoolMode, REF_FI_SWAP_SIGNAL, } from '../services/swap'; -import { EstimateSwapView, swap } from '../services/swap'; +import { EstimateSwapView, swap, swapFromServer } from '../services/swap'; import { get_pool, PoolInfo, @@ -72,6 +72,11 @@ import { toRealSymbol } from '../utils/token'; import { getCurrentWallet, WalletContext } from '../utils/wallets-integration'; import { useIndexerStatus } from './pool'; import { useTokenPriceList } from './token'; +import { + IEstimateSwapServerView, + getAvgFeeFromServer, + getPriceImpactFromServer, +} from '../services/smartRouterFromServer'; const ONLY_ZEROS = /^0*\.?0*$/; export const REF_DCL_POOL_CACHE_KEY = 'REF_DCL_POOL_CACHE_VALUE'; @@ -368,25 +373,38 @@ export const estimateValidator = ( swapTodos: EstimateSwapView[], tokenIn: TokenMetadata, parsedAmountIn: string, - tokenOut: TokenMetadata + tokenOut: TokenMetadata, + swapsToDoServer: IEstimateSwapServerView ) => { - if (swapTodos && swapTodos?.[0]?.pool === null) return true; - - const tokenInId = swapTodos[0]?.inputToken; - const tokenOutId = swapTodos[swapTodos.length - 1]?.outputToken; - - if ( - tokenInId !== tokenIn.id || - tokenOutId !== tokenOut.id || - !BigNumber.sum(...swapTodos.map((st) => st.partialAmountIn || 0)).isEqualTo( - parsedAmountIn - ) || - (!!localStorage.getItem(SUPPORT_LEDGER_KEY) && swapTodos?.length > 1) - ) { - return false; + if (swapTodos?.[0]?.pool === null || !swapsToDoServer) return true; + if (swapTodos) { + const tokenInId = swapTodos[0]?.inputToken; + const tokenOutId = swapTodos[swapTodos.length - 1]?.outputToken; + if ( + tokenInId !== tokenIn.id || + tokenOutId !== tokenOut.id || + !BigNumber.sum( + ...swapTodos.map((st) => st.partialAmountIn || 0) + ).isEqualTo(parsedAmountIn) || + (!!localStorage.getItem(SUPPORT_LEDGER_KEY) && swapTodos?.length > 1) + ) { + return false; + } + } + if (swapsToDoServer) { + const { contract_in, contract_out, amount_in } = swapsToDoServer; + if ( + contract_in !== tokenIn.id || + contract_out !== tokenOut.id || + !BigNumber(amount_in).isEqualTo(parsedAmountIn) + ) { + return false; + } } + return true; }; +// TODO 1 export const useSwap = ({ tokenIn, tokenInAmount, @@ -406,13 +424,15 @@ export const useSwap = ({ const [tokenOutAmount, setTokenOutAmount] = useState(''); const [swapError, setSwapError] = useState(); const [swapsToDo, setSwapsToDo] = useState(); + const [swapsToDoServer, setSwapsToDoServer] = + useState(); const [quoteDone, setQuoteDone] = useState(false); const tokenPriceList = useTokenPriceList(loadingTrigger); const { enableTri } = useContext(SwapProContext); - const [forceEstimate, setForceEstimate] = useState(false); + const [tag, setTag] = useState(); const [priceImpactValue, setPriceImpactValue] = useState('0'); @@ -459,29 +479,61 @@ export const useSwap = ({ setAvgFee(avgFee); }; - const getEstimate = async () => { setCanSwap(false); setQuoteDone(false); + setEstimating(true); - if (tokenIn && tokenOut && tokenIn.id !== tokenOut.id) { - if (!tokenInAmount || ONLY_ZEROS.test(tokenInAmount)) { - setTokenOutAmount('0'); - setSwapsToDo(null); - - return; - } - - setEstimating(true); - estimateSwap({ - tokenIn, - tokenOut, - amountIn: tokenInAmount, - intl, - supportLedger, - loadingTrigger, - }) - .then(async ({ estimates: estimatesRes }) => { + estimateSwap({ + tokenIn, + tokenOut, + amountIn: tokenInAmount, + intl, + supportLedger, + loadingTrigger, + slippage: slippageTolerance, + }) + .then(async (estimateResult) => { + if (estimateResult.source == 'server') { + const { estimatesFromServer, poolsMap, tokensMap } = estimateResult; + const { amount_out } = estimatesFromServer; + const expectedOut = toReadableNumber(tokenOut.decimals, amount_out); + setSwapError(null); + setSwapsToDo(null); + setTokenOutAmount(scientificNotationToString(expectedOut)); + setEstimateInOut([tokenInAmount, expectedOut]); + setCanSwap(true); + setQuoteDone(true); + setSwapsToDoServer(estimatesFromServer); + getAvgFeeFromServer({ + estimatesFromServer, + setAvgFee, + tokenInAmount, + tokenIn, + poolsMap, + }); + const tokenPriceListForCal = !!tokenPriceList?.NEAR + ? tokenPriceList + : (await getTokenPriceListFromCache()).reduce( + (acc, cur) => ({ + ...acc, + [cur.id]: cur, + }), + {} + ); + getPriceImpactFromServer({ + estimatesFromServer, + tokenIn, + tokenOut, + tokenInAmount, + tokenOutAmount: scientificNotationToString(expectedOut.toString()), + tokenPriceList: tokenPriceListForCal, + setPriceImpactValue, + poolsMap, + tokensMap, + }); + } else if (estimateResult.source == 'script') { + const { estimates: estimatesRes } = estimateResult; const estimates = estimatesRes.map((e) => ({ ...e, partialAmountIn: e.pool.partialAmountIn, @@ -498,6 +550,7 @@ export const useSwap = ({ if (tokenInAmount && !ONLY_ZEROS.test(tokenInAmount)) { setAverageFee(estimates); setSwapError(null); + setSwapsToDoServer(null); const expectedOut = estimates.reduce( (acc, cur) => @@ -539,103 +592,53 @@ export const useSwap = ({ } setPool(estimates[0].pool); - }) - .catch((err) => { - // if (!loadingTrigger) { - setCanSwap(false); - setTokenOutAmount(''); - setSwapError(err); - setQuoteDone(true); - - // } - }) - .finally(() => { - setForceEstimate && setForceEstimate(false); - setLoadingTrigger && setLoadingTrigger(false); - setEstimating && setEstimating(false); - setShowSwapLoading && setShowSwapLoading(false); - }); - } else if ( - tokenIn && - tokenOut && - !tokenInAmount && - ONLY_ZEROS.test(tokenInAmount) && - tokenIn.id !== tokenOut.id - ) { - setTokenOutAmount('0'); - } + } + }) + .catch((err) => { + setCanSwap(false); + setTokenOutAmount(''); + setSwapError(err); + setQuoteDone(true); + }) + .finally(() => { + setLoadingTrigger && setLoadingTrigger(false); + setEstimating && setEstimating(false); + setShowSwapLoading && setShowSwapLoading(false); + setTag(`${tokenIn?.id}|${tokenOut?.id}|${tokenInAmount}`); + }); }; useEffect(() => { - const valRes = - !swapError && - swapsToDo && - tokenIn && - tokenOut && - estimateValidator( - swapsToDo, - tokenIn, - toNonDivisibleNumber( - tokenIn?.decimals === null || tokenIn?.decimals === undefined - ? 24 - : tokenIn.decimals, - tokenInAmount - ), - tokenOut - ); - if (!tokenDeflationRateData) return; - if (estimating && swapsToDo && !forceEstimate) return; - - if (valRes && !loadingTrigger && !forceEstimate) { - return; + if ( + tokenIn?.id && + tokenOut?.id && + tokenIn.id !== tokenOut.id && + Number(tokenInAmount || 0) > 0 + ) { + getEstimate(); + } else if (ONLY_ZEROS.test(tokenInAmount || '0')) { + setTokenOutAmount('0'); } - - getEstimate(); }, [ - loadingTrigger, - loadingPause, - tokenIn?.id, - tokenOut?.id, tokenInAmount, reEstimateTrigger, - enableTri, - forceEstimate, - tokenDeflationRateData?.rate, - ]); - - useEffect(() => { - const valRes = - swapsToDo && - tokenIn && - tokenOut && - estimateValidator( - swapsToDo, - tokenIn, - toNonDivisibleNumber( - tokenIn?.decimals === null || tokenIn?.decimals === undefined - ? 24 - : tokenIn.decimals, - tokenInAmount - ), - tokenOut - ); - if (!tokenDeflationRateData) return; - if (estimating && swapsToDo && !forceEstimate) return; - - if (((valRes && !loadingTrigger) || swapError) && !forceEstimate) return; - getEstimate(); - }, [estimating, tokenDeflationRateData?.rate]); - - useEffect(() => { - setForceEstimate(true); - }, [ tokenIn?.id, tokenOut?.id, - tokenIn?.symbol, - tokenOut?.symbol, supportLedger, + enableTri, + slippageTolerance, ]); - + useEffect(() => { + if ( + loadingTrigger && + tokenIn?.id && + tokenOut?.id && + tokenIn.id !== tokenOut.id && + Number(tokenInAmount || 0) > 0 + ) { + getEstimate(); + } + }, [loadingTrigger]); useEffect(() => { let id: any = null; if (!loadingTrigger && !loadingPause) { @@ -652,18 +655,28 @@ export const useSwap = ({ }, [count, loadingTrigger, loadingPause]); const makeSwap = () => { - swap({ - slippageTolerance, - swapsToDo, - tokenIn, - amountIn: Big(tokenInAmount) - .div(Big(1).minus(tokenDeflationRateData?.rate || 0)) - .toFixed(), - tokenOut, - swapMarket: 'ref', - }).catch(setSwapError); + if (swapsToDo) { + swap({ + slippageTolerance, + swapsToDo, + tokenIn, + amountIn: Big(tokenInAmount) + .div(Big(1).minus(tokenDeflationRateData?.rate || 0)) + .toFixed(), + tokenOut, + swapMarket: 'ref', + }).catch(setSwapError); + } else if (swapsToDoServer) { + swapFromServer({ + swapsToDoServer, + tokenIn, + amountIn: Big(tokenInAmount) + .div(Big(1).minus(tokenDeflationRateData?.rate || 0)) + .toFixed(), + tokenOut, + }).catch(setSwapError); + } }; - return { canSwap, tokenOutAmount, @@ -673,9 +686,8 @@ export const useSwap = ({ swapError, makeSwap, avgFee, - tokenInAmount: !swapsToDo - ? '1' - : toReadableNumber( + tokenInAmount: swapsToDo + ? toReadableNumber( tokenIn.decimals, swapsToDo .reduce( @@ -683,7 +695,11 @@ export const useSwap = ({ new Big(0) ) .toFixed() - ), + ) + : swapsToDoServer + ? toReadableNumber(tokenIn.decimals, swapsToDoServer.amount_in || '0') + : '1', + pools: swapsToDo?.map((estimate) => estimate.pool), swapsToDo, isParallelSwap: swapsToDo?.every((e) => e.status === PoolMode.PARALLEL), @@ -692,6 +708,8 @@ export const useSwap = ({ new Big(priceImpactValue).minus(new Big((avgFee || 0) / 100)).toString() ), estimateInOut, + swapsToDoServer, + tag, }; }; export const useSwapV3 = ({ @@ -782,14 +800,13 @@ export const useSwapV3 = ({ input_token: tokenIn, output_token: tokenOut, input_amount: tokenInAmount, - tag: `${tokenIn.id}|${fee}|${tokenInAmount}`, + tag: `${tokenIn.id}|${tokenOut.id}|${tokenInAmount}`, }).catch((e) => null); }; const bestFee = bestEstimate?.tag?.split('|')?.[1] ? Number(bestEstimate?.tag?.split('|')?.[1]) : null; - useEffect(() => { if (!bestFee || wrapOperation) return; @@ -805,7 +822,6 @@ export const useSwapV3 = ({ useEffect(() => { if (!tokenIn || !tokenOut || !tokenInAmount || wrapOperation) return; - setQuoteDone(false); const storedPools = localStorage.getItem(REF_DCL_POOL_CACHE_KEY); @@ -818,7 +834,6 @@ export const useSwapV3 = ({ const allDCLPools = JSON.parse( localStorage.getItem(REF_DCL_POOL_CACHE_KEY) ); - Promise.all( fees.map((fee) => getQuote(fee, tokenIn, tokenOut, allDCLPools)) ) @@ -840,9 +855,6 @@ export const useSwapV3 = ({ setTokenOutAmount( toReadableNumber(tokenOut?.decimals || 24, bestEstimate.amount) ); - - setTag(bestEstimate.tag); - return; } }) @@ -851,6 +863,7 @@ export const useSwapV3 = ({ setQuoteDone(true); setPoolReFetch(!poolReFetch); setLoadingTrigger && setLoadingTrigger(false); + setTag(`${tokenIn?.id}|${tokenOut?.id}|${tokenInAmount}`); }); }, [ tokenIn?.id, @@ -1513,6 +1526,8 @@ export const useRefSwap = ({ priceImpactValue, tokenInAmount: tokenInAmountV1, estimateInOut, + swapsToDoServer, + tag, } = useSwap({ tokenIn, tokenInAmount, @@ -1530,7 +1545,6 @@ export const useRefSwap = ({ tokenDeflationRateData, }); const [estimateInAmount, tokenOutAmount] = estimateInOut; - const { makeSwap: makeSwapV2, tokenOutAmount: tokenOutAmountV2, @@ -1542,7 +1556,7 @@ export const useRefSwap = ({ canSwap: canSwapV2, swapErrorV3: swapErrorV2, tokenInAmount: tokenInAmountV2, - tag: tagV2, + tag: tagV3, } = useSwapV3({ tokenIn, tokenOut, @@ -1554,11 +1568,23 @@ export const useRefSwap = ({ setLoadingTrigger, reEstimateTrigger, }); - - const quoteDoneRef = - quoteDoneV2 && - quoteDone && - Big(estimateInAmount || 0).eq(tokenInAmount || 0); + function validator() { + if (tag && tagV3) { + const [inId, outId, inAmount] = tag.split('|'); + const [dclInId, dclOutId, dclInAmount] = tagV3.split('|'); + return ( + inId == tokenIn?.id && + outId == tokenOut.id && + inAmount == tokenInAmount && + dclInId == tokenIn?.id && + dclOutId == tokenOut.id && + dclInAmount == tokenInAmount + ); + } + return false; + } + // TODO 2 + const quoteDoneRef = quoteDoneV2 && quoteDone && validator(); if (!quoteDoneRef) return { quoteDone: false, @@ -1576,11 +1602,15 @@ export const useRefSwap = ({ ? 'v2' : 'v1'; if (bestSwap === 'v1') { + if (swapsToDoServer) { + swapsToDoServer.contract = 'Ref_Classic'; + } return { quoteDone: true, canSwap, makeSwap: makeSwapV1, estimates: swapsToDo?.map((s) => ({ ...s, contract: 'Ref_Classic' })), + estimatesServer: swapsToDoServer ?? null, tokenOutAmount: !tokenOutAmount || swapError ? '' @@ -1947,6 +1977,7 @@ export const useRefSwapPro = ({ selectMarket, swapType, } = useContext(SwapProContext); + // TODO 3 const resRef = useRefSwap({ tokenIn, tokenInAmount, @@ -2030,17 +2061,18 @@ export const useRefSwapPro = ({ }); resValid = - resValid && - (!resAurora?.availableRoute || - resAurora.tokenOutAmount === - toPrecision( - resAurora.tokenOutAmount || '0', - Math.min(tokenOut.decimals, 8) - )) && - (!resRef?.availableRoute || - resRef.estimates?.[0]?.tokens?.[ - resRef.estimates?.[0]?.tokens.length - 1 - ]?.id === localStorage.getItem('REF_FI_SWAP_OUT')); + (resValid && + (!resAurora?.availableRoute || + resAurora.tokenOutAmount === + toPrecision( + resAurora.tokenOutAmount || '0', + Math.min(tokenOut.decimals, 8) + )) && + (!resRef?.availableRoute || + resRef.estimates?.[0]?.tokens?.[ + resRef.estimates?.[0]?.tokens.length - 1 + ]?.id === localStorage.getItem('REF_FI_SWAP_OUT'))) || + (resRef.estimatesServer && !resRef.estimates); if (!resValid) { setReEstimateTrigger(!reEstimateTrigger); diff --git a/src/store/RefDatabase.ts b/src/store/RefDatabase.ts index 75f7b6f31..7c1358d0c 100644 --- a/src/store/RefDatabase.ts +++ b/src/store/RefDatabase.ts @@ -423,7 +423,7 @@ class RefDatabase extends Dexie { return result; } - async queryPoolsByTokens2(tokenInId: string, tokenOutId: string) { + async queryPoolsByTokens2() { //Queries for any pools that contain either tokenInId OR tokenOutId OR both. const normalItems = await this.poolsTokens.toArray(); diff --git a/src/utils/numbers.ts b/src/utils/numbers.ts index 3f0d0ad41..e4063f5f2 100644 --- a/src/utils/numbers.ts +++ b/src/utils/numbers.ts @@ -10,6 +10,7 @@ import Big from 'big.js'; import _, { sortBy } from 'lodash'; import { getStablePoolDecimal } from '../pages/stable/StableSwapEntry'; import { WRAP_NEAR_CONTRACT_ID } from '../services/wrap-near'; +import { IServerRoute } from '~src/services/smartRouterFromServer'; const BPS_CONVERSION = 10000; @@ -660,6 +661,58 @@ export function getPoolAllocationPercents(pools: Pool[]) { return []; } } +export function getRouteAllocationPercents(routes: IServerRoute[]) { + if (routes.length === 1) return ['100']; + + if (routes) { + const partialAmounts = routes.map((route) => { + return math.bignumber(route.amount_in); + }); + + const ps: string[] = new Array(partialAmounts.length).fill('0'); + + const sum = + partialAmounts.length === 1 + ? partialAmounts[0] + : math.sum(...partialAmounts); + + const sortedAmount = sortBy(partialAmounts, (p) => Number(p)); + + const minIndexes: number[] = []; + + for (let k = 0; k < sortedAmount.length - 1; k++) { + let minIndex = -1; + + for (let j = 0; j < partialAmounts.length; j++) { + if (partialAmounts[j].eq(sortedAmount[k]) && !minIndexes.includes(j)) { + minIndex = j; + minIndexes.push(j); + break; + } + } + const res = math + .round(percent(partialAmounts[minIndex].toString(), sum)) + .toString(); + + if (Number(res) === 0) { + ps[minIndex] = '1'; + } else { + ps[minIndex] = res; + } + } + + const finalPIndex = ps.indexOf('0'); + + ps[finalPIndex] = subtraction( + '100', + ps.length === 1 ? Number(ps[0]) : math.sum(...ps.map((p) => Number(p))) + ).toString(); + + return ps; + } else { + return []; + } +} export function getAllocationsLeastOne(arr: string[]) { if (arr.length === 0) return [];