diff --git a/craco.config.cjs b/craco.config.cjs index 5cee5b1ab..fbe070ad0 100644 --- a/craco.config.cjs +++ b/craco.config.cjs @@ -4,6 +4,8 @@ const MiniCssExtractPlugin = require('mini-css-extract-plugin'); const isProduction = process.env.NODE_ENV === 'production'; const { ProvidePlugin } = require('webpack'); +const { RetryChunkLoadPlugin } = require('webpack-retry-chunk-load-plugin'); + // Linting and type checking are only necessary as part of development and testing. // Omit them from production builds, as they slow down the feedback loop. const shouldLintOrTypeCheck = !isProduction; @@ -47,6 +49,10 @@ module.exports = { filename: 'static/css/[name].[contenthash:8].css', chunkFilename: 'static/css/[name].[contenthash:8].chunk.css', }), + new RetryChunkLoadPlugin({ + retryDelay: 2000, + maxRetries: 3, + }), ], remove: ['CaseSensitivePathsPlugin', 'IgnorePlugin'], }, @@ -88,6 +94,13 @@ module.exports = { webpackConfig.ignoreWarnings = [/Failed to parse source map/]; webpackConfig.module.rules = [ + { + test: /\.m?js$/, + resolve: { + fullySpecified: false, // disable the behaviour + }, + include: [/node_modules/] + }, { test: /\.(js|mjs|jsx|ts|tsx)$/, use: { diff --git a/package.json b/package.json index e853a49ac..513ab3941 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,8 @@ "typescript": "^4.9.5", "url": "^0.11.0", "url-loader": "4.1.1", - "webpack": "5.89.0" + "webpack": "5.89.0", + "webpack-retry-chunk-load-plugin": "3.1.1" }, "dependencies": { "@aurora-is-near/engine": "think-in-universe/aurora.js#6db862e", @@ -90,18 +91,18 @@ "@craco/craco": "7.1.0", "@ledgerhq/devices": "^7.0.0", "@metamask/detect-provider": "^2.0.0", - "@near-wallet-selector/core": "^7.0.0", - "@near-wallet-selector/here-wallet": "^8.1.0", - "@near-wallet-selector/ledger": "^7.0.0", - "@near-wallet-selector/math-wallet": "^7.0.2", - "@near-wallet-selector/meteor-wallet": "^7.9.0", - "@near-wallet-selector/modal-ui": "^7.0.2", - "@near-wallet-selector/my-near-wallet": "^7.0.0", - "@near-wallet-selector/near-wallet": "^7.0.0", - "@near-wallet-selector/neth": "^7.0.2", - "@near-wallet-selector/nightly": "^7.0.2", - "@near-wallet-selector/sender": "^7.0.0", - "@near-wallet-selector/wallet-connect": "^7.0.2", + "@near-wallet-selector/core": "^8.5.4", + "@near-wallet-selector/here-wallet": "^8.5.4", + "@near-wallet-selector/ledger": "^8.5.4", + "@near-wallet-selector/math-wallet": "^8.5.4", + "@near-wallet-selector/meteor-wallet": "^8.5.4", + "@near-wallet-selector/modal-ui": "^8.5.4", + "@near-wallet-selector/my-near-wallet": "^8.5.4", + "@near-wallet-selector/near-wallet": "^8.5.4", + "@near-wallet-selector/neth": "^7.9.1", + "@near-wallet-selector/nightly": "^8.5.4", + "@near-wallet-selector/sender": "^8.5.4", + "@near-wallet-selector/wallet-connect": "^8.5.4", "@react-icons/all-files": "https://github.com/react-icons/react-icons/releases/download/v4.7.1/react-icons-all-files-4.7.1.tgz", "@transak/transak-sdk": "1.2.2", "@types/animejs": "^3.1.4", @@ -167,7 +168,7 @@ "recharts": "^2.1.6", "reconnecting-websocket": "^4.4.0", "respinner": "^3.0.8", - "rxjs": "^7.5.5", + "rxjs": "7.8.1", "socket.io-client": "^4.5.4", "swiper": "^6.8.4", "web3-eth-abi": "1.8.2" diff --git a/src/components/button/Button.tsx b/src/components/button/Button.tsx index ca826d503..dc5a33d7b 100644 --- a/src/components/button/Button.tsx +++ b/src/components/button/Button.tsx @@ -154,8 +154,7 @@ export function ConnectToNearBtn() { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); - // setShowWalletSelector(true); + // setButtonLoading(true); modal.show(); }} > @@ -211,7 +210,7 @@ export function ConnectToNearBtnGradient({ onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); + // setButtonLoading(true); modal.show(); }} > @@ -259,7 +258,7 @@ export function ConnectToNearBtnGradientMoible({ onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); + // setButtonLoading(true); modal.show(); }} > @@ -534,8 +533,8 @@ export function OprationButton(props: any) { onClick={onClick} disabled={disabled} className={`flex items-center justify-center w-full h-full ${ - btnClassName ? btnClassName : '' - }`} + disabled ? 'cursor-not-allowed' : '' + } ${btnClassName ? btnClassName : ''}`} > {props.children} @@ -562,8 +561,7 @@ export function ConnectToNearButton(props: any) { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); - // setShowWalletSelector(true); + // setButtonLoading(true); modal.show(); }} > @@ -880,7 +878,7 @@ export function GreenConnectToNearBtn(props: any) { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); + // setButtonLoading(true); modal.show(); }} > @@ -928,8 +926,7 @@ export function BlacklightConnectToNearBtn(props: any) { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); - // setShowWalletSelector(true); + // setButtonLoading(true); modal.show(); }} > @@ -988,7 +985,7 @@ export function ConnectToNearBtnVotingMobile() { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); + // setButtonLoading(true); modal.show(); }} > @@ -1066,7 +1063,7 @@ export function ConnectToNearBtnSwap() { onClick={(e) => { e.preventDefault(); e.stopPropagation(); - setButtonLoading(true); + // setButtonLoading(true); modal.show(); }} onMouseEnter={() => { diff --git a/src/components/d3Chart/DclChart.tsx b/src/components/d3Chart/DclChart.tsx new file mode 100644 index 000000000..c2f9e8476 --- /dev/null +++ b/src/components/d3Chart/DclChart.tsx @@ -0,0 +1,1910 @@ +import React, { useState, useEffect } from 'react'; +import { isMobile } from '../../utils/device'; +import { ftGetTokenMetadata } from '../../services/ft-contract'; +import { + get_pool, + PoolInfo, + list_liquidities, + get_pool_marketdepth, + get_liquidity, +} from '../../services/swapV3'; +import { + getPriceByPoint, + getPointByPrice, + POINTLEFTRANGE, + POINTRIGHTRANGE, + divide_liquidities_into_bins_user, + UserLiquidityInfo, + getBinPointByPoint, + get_x_y_amount_by_condition, + get_account_24_apr, + divide_liquidities_into_bins_pool, + reverse_price, + get_total_earned_fee, +} from '../../services/commonV3'; +import { getDclPoolPoints, getDCLAccountFee } from '../../services/indexer'; +import { sortBy, debounce } from 'lodash'; +import { + IChartData, + IChartItemConfig, + IChartConfig, + IBinDetail, + IPoolChartConfig, + IUserLiquiditiesDetail, + IDCLAccountFee, + IRMTYPE, +} from './interfaces'; +import { + formatNumber, + formatPercentage, + formatWithCommas_usd, + formatWithCommas_number, + formatPriceWithCommas, +} from './utils'; +import { + get_custom_config_for_chart, + get_default_config_for_chart, +} from './config'; +import Big from 'big.js'; +import * as d3 from 'd3'; +import { useWalletSelector } from '../../context/WalletSelectorContext'; +import { getBoostTokenPrices } from '../../services/farm'; +import { formatWithCommas } from 'src/utils/numbers'; +import { ILiquidityInfoPool, IOrderInfoPool } from '../../services/commonV3'; +import { BlueCircleLoading } from '../../components/layout/Loading'; +import { get_unClaimed_fee_data } from '../../pages/poolsV3/components/detail/DetailFun'; +import { AddIcon, SubIcon } from './Icon'; +import { isValid, createRandomString } from './utils'; +export default function DclChart({ + pool_id, + leftPoint, + rightPoint, + targetPoint, + setLeftPoint, + setRightPoint, + setTargetPoint, + config, + chartType, + removeParams, + newlyAddedLiquidities, + reverse, + radius, +}: { + pool_id: string; + leftPoint?: number; + rightPoint?: number; + targetPoint?: number; + setLeftPoint?: Function; + setRightPoint?: Function; + setTargetPoint?: Function; + config?: IPoolChartConfig; + chartType?: 'POOL' | 'USER'; + removeParams?: { + fromLeft?: boolean; + fromRight?: boolean; + point?: number; + all?: boolean; + }; + newlyAddedLiquidities?: UserLiquidityInfo[]; + reverse?: boolean; + radius?: number; +}) { + const [pool, setPool] = useState(); + const [price_range, set_price_range] = useState(); + const [chartDataList, setChartDataList] = useState(); + const [binDetail, setBinDetail] = useState(); + const [dragLeftPoint, setDragLeftPoint] = useState(); + const [dragRightPoint, setDragRightPoint] = useState(); + const [zoom, setZoom] = useState(); + const [randomId] = useState('.' + createRandomString()); + const [drawChartDone, setDrawChartDone] = useState(false); + const [user_liquidities, set_user_liquidities] = useState< + UserLiquidityInfo[] + >([]); + const [user_liquidities_detail, set_user_liquidities_detail] = + useState(); + const [tokenPriceList, setTokenPriceList] = useState>(); + const [chartDataListDone, setChartDataListDone] = useState(false); + const [dclPoolPoints, setDclPoolPoints] = useState(); + const [dclPoolPointsDone, setDclPoolPointsDone] = useState(false); + /** constant start */ + const appearanceConfig: IPoolChartConfig = config || {}; + let [timerObj, setTimerObj] = useState({ + timer: '', + }); + const dragBarWidth = 28; + const radiusDragBarWidth = 20; + const percentBoxWidth = 44; + const min_bar_height = 2; + const svgWidth = +(appearanceConfig.svgWidth || 520); + const svgHeight = +(appearanceConfig.svgHeight || 250); + const axisHeight = appearanceConfig.axisHidden + ? appearanceConfig.controlHidden + ? 0 + : 5 + : 21; + const wholeBarHeight = svgHeight - axisHeight; + const disFromHoverBoxToPointer = +( + appearanceConfig.disFromPercentBoxToDragBar || 20 + ); + const disFromPercentBoxToDragBar = +( + appearanceConfig.disFromPercentBoxToDragBar || 2 + ); + const svgPaddingX = +(appearanceConfig.svgPaddingX || 10); + + // for default left price and right price + const defaultPercent = +(appearanceConfig.defaultPercent || 10); + const whole_bars_background_padding = +( + appearanceConfig.whole_bars_background_padding || 20 + ); + /** constant end */ + const { accountId } = useWalletSelector(); + const is_mobile = isMobile(); + useEffect(() => { + // get all token prices + getBoostTokenPrices().then((result) => { + setTokenPriceList(result); + }); + }, []); + // init + useEffect(() => { + clearTimeout(timerObj.timer); + if (pool_id) { + timerObj.timer = setTimeout(() => { + get_pool_detail(pool_id); + }, 500); + } + }, [pool_id]); + // init get data from back end + useEffect(() => { + if (pool) { + get_chart_data(); + } + }, [pool, accountId]); + useEffect(() => { + if (dclPoolPointsDone && chartDataListDone) { + const combineList = combine_data(dclPoolPoints, chartDataList); + setChartDataList(combineList); + } + }, [dclPoolPointsDone, chartDataListDone]); + useEffect(() => { + if (chartDataList) { + init_price_range(); + } + }, [reverse]); + // draw chart + useEffect(() => { + if ( + (chartType !== 'USER' && price_range && chartDataList) || + (chartType == 'USER' && chartDataList?.length) + ) { + drawChart(); + setDrawChartDone(true); + } + }, [price_range, chartDataList, config?.svgWidth]); + // generate user chart + useEffect(() => { + if (pool && accountId && newlyAddedLiquidities && chartType == 'USER') { + const new_list = get_latest_user_chart_data(); + setChartDataList(new_list); + } + }, [newlyAddedLiquidities, user_liquidities]); + useEffect(() => { + if ( + isValid(dragLeftPoint) && + !appearanceConfig.controlHidden && + drawChartDone + ) { + const scale = scaleAxis(); + const newPoint = dragLeftPoint; + let newPrice; + if (reverse) { + newPrice = reverse_price(get_price_by_point(newPoint)); + } else { + newPrice = get_price_by_point(newPoint); + } + const movePercent = diffPrices(newPrice); + const x = scale(+newPrice) - dragBarWidth / 2; + d3.select(`${randomId} .drag-left`).attr( + 'transform', + `translate(${x}, -${axisHeight})` + ); + d3.select(`${randomId} .percentLeft`) + .attr( + 'transform', + `translate(${ + x - + (percentBoxWidth - dragBarWidth / 2 + disFromPercentBoxToDragBar) + }, 0)` + ) + .select('text') + .text(movePercent + '%') + .attr('fill', 'white'); + const rightX = Number( + d3 + .select(`${randomId} .drag-right`) + .attr('transform') + .split(',')[0] + .slice(10) + ); + const W = rightX - x - dragBarWidth / 2; + d3.select(`${randomId} .overlap rect`) + .attr('transform', `translate(${x + dragBarWidth / 2}, 0)`) + .attr('width', W); + } + }, [dragLeftPoint, price_range, drawChartDone]); + useEffect(() => { + if (isValid(leftPoint)) { + setDragLeftPoint(leftPoint); + } + }, [leftPoint]); + useEffect(() => { + if ( + isValid(dragRightPoint) && + !appearanceConfig.controlHidden && + drawChartDone + ) { + const scale = scaleAxis(); + const newPoint = dragRightPoint; + let newPrice = reverse_price(get_price_by_point(newPoint)); + if (reverse) { + newPrice = reverse_price(get_price_by_point(newPoint)); + } else { + newPrice = get_price_by_point(newPoint); + } + const movePercent = diffPrices(newPrice); + let x = scale(+newPrice); + // transform attr can not receive too big number + if (Big(x).gt(9999999999)) { + x = 9999999999; + } + d3.select(`${randomId} .drag-right`).attr( + 'transform', + `translate(${x}, -${axisHeight})` + ); + d3.select(`${randomId} .percentRight`) + .attr( + 'transform', + `translate(${x + (disFromPercentBoxToDragBar + 2)}, 0)` + ) + .select('text') + .text(movePercent + '%') + .attr('fill', 'white'); + + const leftX = Number( + d3 + .select(`${randomId} .drag-left`) + .attr('transform') + .split(',')[0] + .slice(10) + ); + const W = x - leftX - dragBarWidth / 2; + d3.select(`${randomId} .overlap rect`).attr('width', W); + } + }, [dragRightPoint, price_range, drawChartDone]); + useEffect(() => { + if (isValid(rightPoint)) { + setDragRightPoint(rightPoint); + } + }, [rightPoint]); + useEffect(() => { + if (config?.radiusMode && isValid(targetPoint) && drawChartDone) { + // hide drag bar and show target price bar + draw_radius_mode_bar(); + d3.select(`${randomId} .leftBar`).attr('style', 'display:none'); + d3.select(`${randomId} .rightBar`).attr('style', 'display:none'); + } else { + d3.select(`${randomId} .leftBar`).attr('style', ''); + d3.select(`${randomId} .rightBar`).attr('style', ''); + d3.select(`${randomId} .radiusBar`).attr('style', 'display:none'); + } + }, [config?.radiusMode, drawChartDone, targetPoint, pool_id, price_range]); + + // bind drag event + useEffect(() => { + if (!config.controlHidden && pool_id && drawChartDone) { + bind_radius_mode_bar_event(); + } + }, [config.controlHidden, drawChartDone, price_range, pool_id, radius]); + + // draw remove area for user + useEffect(() => { + if (removeParams && drawChartDone) { + const scale = scaleAxis(); + const scaleBar = scaleAxisY(); + draw_background_bars_for_select_area({ scale, scaleBar }); + } + }, [ + removeParams?.all, + removeParams?.fromLeft, + removeParams?.fromRight, + removeParams?.point, + drawChartDone, + ]); + // to get user detail when hover on chart + useEffect(() => { + if ( + user_liquidities.length && + pool && + chartType == 'USER' && + tokenPriceList + ) { + get_user_liquidities_detail(); + } + }, [user_liquidities, pool, chartType, tokenPriceList]); + async function get_user_liquidities_detail() { + const { token_x_metadata, token_y_metadata, pool_id } = pool; + const dcl_fee_result: IDCLAccountFee | any = await getDCLAccountFee({ + pool_id, + account_id: accountId, + }); + const price_x = tokenPriceList[token_x_metadata.id]?.price || 0; + const price_y = tokenPriceList[token_y_metadata.id]?.price || 0; + + const points: number[] = []; + let total_x_amount = Big(0); + let total_y_amount = Big(0); + let total_value = Big(0); + let total_fee_earned = Big(0); + let apr_24 = ''; + if (dcl_fee_result) { + // total unClaimed fee + const [ + unClaimed_tvl_fee, + unClaimed_amount_x_fee, + unClaimed_amount_y_fee, + ] = get_unClaimed_fee_data(user_liquidities, pool, tokenPriceList); + const dclAccountFee: IDCLAccountFee = dcl_fee_result; + const { total_earned_fee } = dclAccountFee; + // total earned fee + const { total_fee_earned_money } = get_total_earned_fee({ + total_earned_fee, + token_x_metadata, + token_y_metadata, + unClaimed_amount_x_fee, + unClaimed_amount_y_fee, + tokenPriceList, + }); + total_fee_earned = Big(total_fee_earned_money); + // 24h profit + apr_24 = formatPercentage( + get_account_24_apr( + unClaimed_tvl_fee, + dcl_fee_result, + pool, + tokenPriceList + ) + ); + } + user_liquidities.forEach((l: UserLiquidityInfo) => { + const { left_point, right_point, amount } = l; + points.push(left_point, right_point); + const { total_x, total_y } = get_x_y_amount_by_condition({ + left_point, + right_point, + amount, + tokenX: token_x_metadata, + tokenY: token_y_metadata, + poolDetail: pool, + }); + total_x_amount = total_x_amount.plus(total_x); + total_y_amount = total_y_amount.plus(total_y); + }); + const total_x_value = Big(price_x).mul(total_x_amount); + const total_y_value = Big(price_y).mul(total_y_amount); + total_value = total_x_value.plus(total_y_value); + points.sort((b: number, a: number) => { + return b - a; + }); + const min_point = points[0]; + const max_point = points[points.length - 1]; + let min_price, max_price; + if (reverse) { + min_price = reverse_price(get_price_by_point(max_point)); + max_price = reverse_price(get_price_by_point(min_point)); + } else { + min_price = get_price_by_point(min_point); + max_price = get_price_by_point(max_point); + } + set_user_liquidities_detail({ + total_value: formatWithCommas_usd(total_value.toFixed()), + min_price: formatPriceWithCommas(min_price), + max_price: formatPriceWithCommas(max_price), + total_x_amount: formatNumber(total_x_amount.toFixed()), + total_y_amount: formatNumber(total_y_amount.toFixed()), + apr_24, + total_earned_fee: formatWithCommas_usd(total_fee_earned.toFixed()), + }); + } + function get_latest_user_chart_data() { + const { token_x_metadata, token_y_metadata } = pool; + const { bin: bin_final } = getConfig(); + const nfts = user_liquidities.concat(newlyAddedLiquidities || []); + const list = divide_liquidities_into_bins_user({ + liquidities: nfts, + slot_number_in_a_bin: bin_final, + tokenX: token_x_metadata, + tokenY: token_y_metadata, + poolDetail: pool, + }); + return list; + } + async function get_pool_detail(pool_id: string) { + const p: PoolInfo = await get_pool(pool_id); + const { token_x, token_y } = p; + p.token_x_metadata = await ftGetTokenMetadata(token_x); + p.token_y_metadata = await ftGetTokenMetadata(token_y); + setPool(p); + } + async function get_chart_data() { + get_dcl_pool_points(); + const list = await get_data_from_back_end(); + setChartDataList(list); + init_price_range(); + setChartDataListDone(true); + } + function init_price_range() { + const { range } = getConfig(); + if (chartType !== 'USER') { + const [price_l_default, price_r_default] = + get_price_range_by_percent(range); + set_price_range([+price_l_default, +price_r_default]); + setZoom(range); + } else { + const { sortP } = get_price_and_liquidity_range(); + const range = [+sortP[0], +sortP[sortP.length - 1]]; + set_price_range(range); + } + } + function get_dcl_pool_points() { + const { pool_id } = pool; + const { bin: bin_final, rangeGear } = getConfig(); + const [price_l, price_r] = get_price_range_by_percent(rangeGear[0], true); + const point_l = get_point_by_price(price_l); + const point_r = get_point_by_price(price_r); + getDclPoolPoints(pool_id, bin_final, point_l, point_r).then( + (pointsData_apr) => { + setDclPoolPoints(pointsData_apr?.point_data); + setDclPoolPointsDone(true); + } + ); + } + async function get_data_from_back_end() { + setChartDataListDone(false); + const { token_x_metadata, token_y_metadata, pool_id } = pool; + const { bin: bin_final, rangeGear } = getConfig(); + let list: any[] = []; + if (chartType == 'USER') { + if (accountId) { + const liquidities = await list_liquidities(); + const nfts = liquidities.filter((l: UserLiquidityInfo) => { + return l.pool_id == pool_id; + }); + const liquiditiesPromise = nfts.map((liquidity: UserLiquidityInfo) => { + return get_liquidity(liquidity.lpt_id); + }); + const nft_details = await Promise.all(liquiditiesPromise); + set_user_liquidities(nft_details); + list = divide_liquidities_into_bins_user({ + liquidities: nfts, + slot_number_in_a_bin: bin_final, + tokenX: token_x_metadata, + tokenY: token_y_metadata, + poolDetail: pool, + }); + } + } else { + const marketdepthData = await get_pool_marketdepth(pool_id); + const { liquidities, orders } = marketdepthData; + let liquidities_array: ILiquidityInfoPool[] = Object.values(liquidities); + // to find the bin which left_point is current_point and right_point is current_point, if the bin number is two,then need to merge. + let merged_current_point_liquidity: ILiquidityInfoPool; + const contain_current_point_liquidities: ILiquidityInfoPool[] = []; + const exclude_current_point_liquidities_array = liquidities_array.filter( + (l: ILiquidityInfoPool) => { + const { left_point, right_point } = l; + if ( + right_point == pool.current_point || + left_point == pool.current_point + ) { + contain_current_point_liquidities.push(l); + return false; + } + return true; + } + ); + if (contain_current_point_liquidities.length == 2) { + contain_current_point_liquidities.sort( + (b: ILiquidityInfoPool, a: ILiquidityInfoPool) => { + return b.left_point - a.left_point; + } + ); + merged_current_point_liquidity = { + left_point: contain_current_point_liquidities[0].left_point, + right_point: contain_current_point_liquidities[1].right_point, + amount_l: contain_current_point_liquidities[0].amount_l, + }; + exclude_current_point_liquidities_array.push( + merged_current_point_liquidity + ); + liquidities_array = exclude_current_point_liquidities_array; + } + const orders_array: IOrderInfoPool[] = Object.values(orders); + const pointsData_l = divide_liquidities_into_bins_pool({ + liquidities: liquidities_array, + orders: orders_array, + slot_number_in_a_bin: bin_final, + tokenX: token_x_metadata, + tokenY: token_y_metadata, + poolDetail: pool, + }); + list = combine_data([], pointsData_l); + } + return list; + } + function combine_data( + pointsData_apr: IChartData[], + pointsData_l: IChartData[] + ) { + const pointsData: IChartData[] = []; + const pointsData_apr_map = pointsData_apr?.reduce((acc, cur) => { + return { + ...acc, + [cur.point]: cur, + }; + }, {}); + const pointsData_l_map = pointsData_l?.reduce((acc, cur) => { + return { + ...acc, + [cur.point]: cur, + }; + }, {}); + if (pointsData_l_map) { + Object.keys(pointsData_l_map).forEach((point_l: string) => { + const { + liquidity, + token_x, + token_y, + order_liquidity, + order_x, + order_y, + point, + pool_id, + } = pointsData_l_map[point_l]; + + const { fee, total_liquidity } = pointsData_apr_map?.[point_l] || {}; + + pointsData.push({ + fee: fee || '0', + total_liquidity: total_liquidity || '0', + pool_id, + point, + liquidity, + token_x, + token_y, + order_liquidity, + order_x, + order_y, + }); + pointsData.sort((b: IChartData, a: IChartData) => { + return b.point - a.point; + }); + }); + } + return pointsData; + } + function getChartDataListInRange() { + const [price_range_point_min, price_range_point_max] = + get_point_range_by_price_range(); + const bin_width = getConfig().bin * pool.point_delta; + const chartDataListInRange = chartDataList.filter((d: IChartData) => { + const { point } = d; + const point_right = point + bin_width; + // return point >= price_range_point_min && point_right <= price_range_point_max; + return ( + point_right > price_range_point_min && point < price_range_point_max + ); + }); + return chartDataListInRange; + } + function get_point_range_by_price_range() { + let price_range_point_min; + let price_range_point_max; + const price_range_min = price_range[0].toString(); + const price_range_max = price_range[price_range.length - 1].toString(); + if (reverse) { + price_range_point_min = get_point_by_price( + reverse_price(price_range_max) + ); + price_range_point_max = get_point_by_price( + reverse_price(price_range_min) + ); + } else { + price_range_point_min = get_point_by_price(price_range_min); + price_range_point_max = get_point_by_price(price_range_max); + } + return [price_range_point_min, price_range_point_max]; + } + + function get_price_range_by_percent( + percent: number, + forward?: boolean + ): [string, string] { + const p_l_r = percent / 100; + const price = get_current_price(forward); + const price_l_temp = Big(1 - p_l_r).mul(price); + const price_l = price_l_temp.lt(0) ? '0' : price_l_temp.toFixed(); + const price_r = Big(1 + p_l_r) + .mul(price) + .toFixed(); + + return [price_l, price_r]; + } + function drawChart() { + const data: IChartData[] = process_back_end_data_in_range(); + const scale = scaleAxis(); + const scaleBar = scaleAxisY(); + // down bars + draw_down_bars({ data, scale, scaleBar }); + // up bars + if (chartType !== 'USER') { + draw_up_bars({ data, scale, scaleBar }); + } + // create axis + if (appearanceConfig.axisHidden) { + d3.select(`${randomId} .axis`).remove(); + } else { + draw_axis({ scale }); + } + // draw background bars + if (appearanceConfig.hoverBoxHidden) { + d3.select(`${randomId} .bars_background`).remove(); + d3.select(`${randomId} .overBox`).remove(); + d3.select(`${randomId} .whole_bars_background`).remove(); + d3.select(`${randomId} .wholeOverBox`).remove(); + } else { + draw_background_bars({ data, scale, scaleBar }); + } + // remove select area + if (chartType == 'USER' && removeParams) { + draw_background_bars_for_select_area({ scale, scaleBar }); + } else { + d3.select(`${randomId} .remove_bars_background`).remove(); + } + + // draw current line + if ( + appearanceConfig.currentBarHidden || + (chartType == 'USER' && !is_in_range()) + ) { + d3.select(`${randomId} .currentLine`).remove(); + } else { + draw_current_bar({ scale }); + } + if (appearanceConfig.controlHidden) { + remove_control(); + } else { + // init + // drag left + draw_drag_left({ scale }); + // drag right + draw_drag_right({ scale }); + } + } + function is_in_range() { + const { sortP } = get_price_and_liquidity_range(); + const current_price = get_current_price(); + return ( + Big(current_price).gte(sortP[0]) && + Big(current_price).lte(sortP[sortP.length - 1]) + ); + } + function process_back_end_data_in_range() { + const { bin: bin_final } = getConfig(); + const { point_delta } = pool; + const list = + chartType == 'USER' ? chartDataList : getChartDataListInRange(); + const data: IChartData[] = list?.map((o: IChartData) => { + const { point } = o; + let price_l, price_r, point_l, point_r; + if (reverse) { + point_l = +point + point_delta * bin_final; + price_l = reverse_price(get_price_by_point(point_l)); + point_r = point; + price_r = reverse_price(get_price_by_point(+point_r)); + } else { + point_l = point; + price_l = get_price_by_point(+point_l); + point_r = +point + point_delta * bin_final; + price_r = get_price_by_point(point_r); + } + return { + ...o, + liquidity: Big(o.liquidity || 0).toFixed(), + order_liquidity: Big(o.order_liquidity || 0).toFixed(), + token_x: Big(o.token_x || 0).toFixed(), + token_y: Big(o.token_y || 0).toFixed(), + order_x: Big(o.order_x || 0).toFixed(), + order_y: Big(o.order_y || 0).toFixed(), + total_liquidity: Big(o.total_liquidity || 0).toFixed(), + fee: Big(o.fee || 0).toFixed(), + + price_l, + price_r, + point_l, + point_r, + }; + }); + return data || []; + } + function hoverBox(e: any, d: IChartData) { + if (is_mobile) { + d3.select(`${randomId} .overBox`).attr('style', `display:block`); + } else { + d3.select(`${randomId} .overBox`).attr( + 'style', + `visibility:visible;transform:translate(${ + e.offsetX + disFromHoverBoxToPointer + }px, ${e.offsetY / 2}px)` + ); + } + + const { + point_l, + token_x, + token_y, + order_x, + order_y, + fee, + total_liquidity, + } = d; + const { colors } = getConfig(); + + const total_token_x = Big(token_x).plus(order_x); + const total_token_y = Big(token_y).plus(order_y); + const price_by_token_x = get_price_by_point(+point_l); + const price_by_token_y = reverse_price(price_by_token_x); + const apr = Big(total_liquidity).gt(0) + ? Big(fee).div(total_liquidity).mul(365).mul(100).toFixed() + : '0'; + setBinDetail({ + feeApr: formatPercentage(apr), + colors, + ...(total_token_x.gt(0) + ? { + token_x_amount: formatWithCommas_number(total_token_x.toFixed()), + token_x_amount_in_liquidity: formatWithCommas_number(token_x), + token_x_amount_in_order: formatWithCommas_number(order_x), + } + : {}), + ...(total_token_y.gt(0) + ? { + token_y_amount: formatWithCommas_number(total_token_y.toFixed()), + token_y_amount_in_liquidity: formatWithCommas_number(token_y), + token_y_amount_in_order: formatWithCommas_number(order_y), + } + : {}), + price_by_token_x: formatPriceWithCommas(price_by_token_x), + price_by_token_y: formatPriceWithCommas(price_by_token_y), + }); + } + function LeaveBox(e: any, d: IChartData) { + if (is_mobile) { + d3.select(`${randomId} .overBox`).attr('style', `display:none`); + } else { + d3.select(`${randomId} .overBox`).attr( + 'style', + `visibility:hidden;transform:translate(${ + e.offsetX + disFromHoverBoxToPointer + }px, ${e.offsetY / 2}px)` + ); + } + } + function hoverUserBox(e: any) { + if (is_mobile) { + d3.select(`${randomId} .wholeOverBox`).attr('style', `display:block;`); + } else { + d3.select(`${randomId} .wholeOverBox`).attr( + 'style', + `visibility:visible;transform:translate(${ + e.offsetX + disFromHoverBoxToPointer + }px, ${e.offsetY / 2}px)` + ); + } + } + function LeaveUserBox(e: any) { + if (is_mobile) { + d3.select(`${randomId} .wholeOverBox`).attr('style', `display:none;`); + } else { + d3.select(`${randomId} .wholeOverBox`).attr( + 'style', + `visibility:hidden;transform:translate(${ + e.offsetX + disFromHoverBoxToPointer + }px, ${e.offsetY / 2}px)` + ); + } + } + function remove_control() { + d3.select(`${randomId} .control`).remove(); + d3.select(`${randomId} .overlap`).remove(); + d3.select(`${randomId} .rightBar`).remove(); + d3.select(`${randomId} .leftBar`).remove(); + } + function draw_axis({ scale }: { scale: any }) { + const axis: any = d3.axisBottom(scale).tickSize(0).tickPadding(10); + if (appearanceConfig.ticks || chartType == 'USER') { + axis.ticks(appearanceConfig.ticks || 5).tickFormat(function (d: any) { + const dBig = new Big(d); + if (dBig.gte(10000)) { + return formatWithCommas(dBig.toFixed(0)); + } else { + return formatWithCommas(dBig.toFixed()); + } + }); + } + d3.select(`${randomId} svg .axis`) + .call(axis) + .selectAll('text') + .attr('fill', '#7E8A93'); + d3.select(`${randomId} svg .axis`) + .attr('transform', `translate(0, ${svgHeight - axisHeight})`) + .select('.domain') + .attr('stroke', 'transparent'); + } + function draw_down_bars({ + data, + scale, + scaleBar, + }: { + data: IChartData[]; + scale: Function; + scaleBar: Function; + }) { + const { current_point } = pool; + const { colors } = getConfig(); + d3.select(`${randomId} .bars_liquidity`) + .selectAll('rect') + .data(data) + .join('rect') + .transition() + .attr('width', function (d) { + return ( + scale(Big(d.price_r).toNumber()) - scale(Big(d.price_l).toNumber()) + ); + }) + .attr('height', function (d) { + return get_final_bar_height(scaleBar(+d.liquidity)); + }) + .attr('x', function (d) { + return scale(Big(d.price_l).toNumber()); + }) + .attr('y', function (d) { + return wholeBarHeight - get_final_bar_height(scaleBar(+d.liquidity)); + }) + .attr('rx', 2) + .attr('fill', function (d) { + if (reverse) { + return +d.point_r >= current_point ? colors[1] : colors[0]; + } else { + return +d.point_l >= current_point ? colors[1] : colors[0]; + } + }); + } + function draw_up_bars({ + data, + scale, + scaleBar, + }: { + data: IChartData[]; + scale: Function; + scaleBar: Function; + }) { + const { colors } = getConfig(); + const { current_point } = pool; + d3.select(`${randomId} .bars_order`) + .selectAll('rect') + .data(data) + .join('rect') + .transition() + .attr('width', function (d) { + return ( + scale(Big(d.price_r).toNumber()) - scale(Big(d.price_l).toNumber()) + ); + }) + .attr('height', function (d) { + return get_final_bar_height(scaleBar(+d.order_liquidity)); + }) + .attr('x', function (d) { + return scale(Big(d.price_l).toNumber()); + }) + .attr('y', function (d) { + return ( + wholeBarHeight - + get_final_bar_height(scaleBar(+d.liquidity)) - + get_final_bar_height(scaleBar(+d.order_liquidity)) + ); + }) + .attr('rx', 2) + .attr('fill', function (d) { + if (reverse) { + return +d.point_r >= current_point ? colors[1] : colors[0]; + } else { + return +d.point_l >= current_point ? colors[1] : colors[0]; + } + }) + .attr('opacity', '0.7'); + } + function get_final_bar_height(h: number) { + if (Big(h || 0).lt(min_bar_height) && Big(h || 0).gt(0)) + return min_bar_height; + return h; + } + function draw_background_bars({ + data, + scale, + scaleBar, + }: { + data: IChartData[]; + scale: Function; + scaleBar: Function; + }) { + if (chartType == 'USER') { + draw_background_bars_user({ + scale, + scaleBar, + }); + } else { + draw_background_bars_pool({ + data, + scale, + }); + } + } + function draw_background_bars_pool({ + data, + scale, + }: { + data: IChartData[]; + scale: Function; + }) { + d3.select(`${randomId} .bars_background`) + .selectAll('rect') + .data(data) + .join('rect') + .on('mousemove', function (e, d) { + d3.select(this).attr('fill', 'rgba(255,255,255,0.1)'); + hoverBox(e, d); + }) + .on('mouseleave', function (e, d) { + d3.select(this).attr('fill', 'transparent'); + LeaveBox(e, d); + }) + .transition() + .attr('width', function (d) { + return ( + scale(Big(d.price_r).toNumber()) - scale(Big(d.price_l).toNumber()) + ); + }) + .attr('height', function (d) { + return wholeBarHeight; + }) + .attr('x', function (d) { + return scale(Big(d.price_l).toNumber()); + }) + .attr('y', function (d) { + return 0; + }) + .attr('rx', 2) + .attr('fill', 'transparent'); + } + function draw_background_bars_user({ + scale, + scaleBar, + }: { + scale: Function; + scaleBar: Function; + }) { + const { sortP, sortY } = get_price_and_liquidity_range(); + d3.select(`${randomId} .whole_bars_background`) + .on('mousemove', function (e) { + d3.select(this).attr('fill', 'rgba(255,255,255,0.1)'); + hoverUserBox(e); + }) + .on('mouseleave', function (e) { + d3.select(this).attr('fill', 'transparent'); + LeaveUserBox(e); + }) + .transition() + .attr('width', function () { + return ( + scale(sortP[sortP.length - 1]) - + scale(sortP[0]) + + whole_bars_background_padding * 2 + ); + }) + .attr('height', function () { + return ( + scaleBar(sortY[sortY.length - 1]) + whole_bars_background_padding + ); + }) + .attr('x', function () { + return scale(sortP[0]) - whole_bars_background_padding; + }) + .attr('y', function () { + return ( + wholeBarHeight - + scaleBar(sortY[sortY.length - 1]) - + whole_bars_background_padding + ); + }) + .attr('rx', 4) + .attr('fill', 'transparent'); + } + function draw_background_bars_for_select_area({ + scale, + scaleBar, + }: { + scale: Function; + scaleBar: Function; + }) { + const { sortP, sortY } = get_price_and_liquidity_range(); + const min_bin_price = sortP[0]; + const max_bin_price = sortP[sortP.length - 1]; + const { fromLeft, fromRight, all, point } = removeParams; + let remove_to_price: string; + if (reverse) { + remove_to_price = reverse_price(get_price_by_point(point)); + } else { + remove_to_price = get_price_by_point(point); + } + + d3.select(`${randomId} .remove_bars_background`) + .attr('width', function () { + if (all) { + return scale(max_bin_price) - scale(min_bin_price); + } else if (fromLeft) { + return scale(remove_to_price) - scale(min_bin_price); + } else if (fromRight) { + return scale(max_bin_price) - scale(remove_to_price); + } + }) + .attr('height', function () { + return ( + scaleBar(sortY[sortY.length - 1]) + whole_bars_background_padding + ); + }) + .attr('x', function () { + if (fromRight) { + return scale(remove_to_price); + } else { + return scale(min_bin_price); + } + }) + .attr('y', function () { + return ( + wholeBarHeight - + scaleBar(sortY[sortY.length - 1]) - + whole_bars_background_padding + ); + }) + .attr('rx', 4) + .attr('fill', 'rgba(255,255,255,0.1)'); + } + function draw_current_bar({ scale }: { scale: Function }) { + const x = scale(+get_current_price()) + svgPaddingX; + d3.select(`${randomId} .currentLine`).attr( + 'style', + `transform:translate(${x}px, -${axisHeight}px)` + ); + if (is_mobile) { + if (x > 290) { + const tx = x > 320 ? '-95%' : x > 300 ? '-80%' : '-70%'; + d3.select(`${randomId} .currentLineDetail`).attr( + 'style', + `transform:translateX(${tx})` + ); + } else if (x < 50) { + const tx = x < 10 ? '-10%' : x < 20 ? '-20%' : '-30%'; + d3.select(`${randomId} .currentLineDetail`).attr( + 'style', + `transform:translateX(${tx})` + ); + } + } + } + function draw_drag_left({ scale }: { scale: any }) { + const price = get_current_price(); + let price_l; + if (dragLeftPoint) { + if (reverse) { + price_l = reverse_price(get_price_by_point(dragLeftPoint)); + } else { + price_l = get_price_by_point(dragLeftPoint); + } + } else { + const price_l_temp = Big(1 - defaultPercent / 100) + .mul(price) + .toFixed(); + if (reverse) { + const newLeftPoint = get_nearby_bin_right_point( + get_point_by_price(reverse_price(price_l_temp)) + ); + price_l = reverse_price(get_price_by_point(newLeftPoint)); + setDragLeftPoint(newLeftPoint); + } else { + const newLeftPoint = get_nearby_bin_left_point( + get_point_by_price(price_l_temp) + ); + price_l = get_price_by_point(newLeftPoint); + setDragLeftPoint(newLeftPoint); + } + } + + const x = scale(price_l) - dragBarWidth / 2; + d3.select(`${randomId} .drag-left`).attr( + 'transform', + `translate(${x}, -${axisHeight})` + ); + d3.select(`${randomId} .percentLeft`) + .attr( + 'transform', + `translate(${ + x - (percentBoxWidth - dragBarWidth / 2 + disFromPercentBoxToDragBar) + }, 0)` + ) + .select('text') + .text(`${diffPrices(price_l)}%`) + .attr('fill', 'white'); + const dragLeft = d3.drag().on('drag', function (e) { + const rightX = Number( + d3 + .select(`${randomId} .drag-right`) + .attr('transform') + .split(',')[0] + .slice(10) + ); + if (rightX < e.x || e.x < dragBarWidth / 2) return; + let p; + if (reverse) { + p = reverse_price(scale.invert(e.x)); + } else { + p = scale.invert(e.x); + } + let newLeftPoint; + if (reverse) { + newLeftPoint = get_nearby_bin_right_point(get_point_by_price(p)); + } else { + newLeftPoint = get_nearby_bin_left_point(get_point_by_price(p)); + } + setDragLeftPoint(newLeftPoint); + setLeftPoint && setLeftPoint(newLeftPoint); + }); + d3.select(`${randomId} .drag-left`).call(dragLeft); + } + function draw_drag_right({ scale }: { scale: any }) { + const price = get_current_price(); + let price_r; + if (dragRightPoint) { + if (reverse) { + price_r = reverse_price(get_price_by_point(dragRightPoint)); + } else { + price_r = get_price_by_point(dragRightPoint); + } + } else { + const price_r_temp = Big(1 + defaultPercent / 100) + .mul(price) + .toFixed(); + if (reverse) { + const newRightPoint = get_nearby_bin_left_point( + get_point_by_price(reverse_price(price_r_temp)) + ); + price_r = reverse_price(get_price_by_point(newRightPoint)); + setDragRightPoint(newRightPoint); + } else { + const newRightPoint = get_nearby_bin_right_point( + get_point_by_price(price_r_temp) + ); + price_r = get_price_by_point(newRightPoint); + setDragRightPoint(newRightPoint); + } + } + const x = scale(price_r); + d3.select(`${randomId} .drag-right`).attr( + 'transform', + `translate(${x}, -${axisHeight})` + ); + d3.select(`${randomId} .percentRight`) + .attr('transform', `translate(${x + disFromPercentBoxToDragBar + 2}, 0)`) + .select('text') + .text(`${diffPrices(price_r)}%`) + .attr('fill', 'white'); + const dragRight = d3.drag().on('drag', (e) => { + const leftX = Number( + d3 + .select(`${randomId} .drag-left`) + .attr('transform') + .split(',')[0] + .slice(10) + ); + const limitx = svgWidth - (svgPaddingX * 2 + dragBarWidth); + if (leftX > e.x - dragBarWidth / 2 || e.x > limitx) return; + let p; + if (reverse) { + p = reverse_price(scale.invert(e.x)); + } else { + p = scale.invert(e.x); + } + let newRightPoint; + if (reverse) { + newRightPoint = get_nearby_bin_left_point(get_point_by_price(p)); + } else { + newRightPoint = get_nearby_bin_right_point(get_point_by_price(p)); + } + setDragRightPoint(newRightPoint); + setRightPoint && setRightPoint(newRightPoint); + }); + d3.select(`${randomId} .drag-right`).call(dragRight); + } + function draw_radius_mode_bar() { + const scale: any = scaleAxis(); + let price = get_price_by_point(targetPoint); + if (reverse) { + price = reverse_price(price); + } + const x = scale(price) - 8; + d3.select(`${randomId} .radiusBar`) + .attr('transform', `translate(${x}, -${axisHeight})`) + .attr('style', 'display:block;cursor:ew-resize'); + } + function bind_radius_mode_bar_event() { + const scale: any = scaleAxis(); + const dragTarget = d3.drag().on('drag', (e) => { + let p; + const drag_price = scale.invert(e.x); + if (reverse) { + p = reverse_price(drag_price); + } else { + p = drag_price; + } + // if target price is out of range then limit dragging + if ( + Big(drag_price).lte(price_range[0]) || + Big(drag_price).gte(price_range[price_range.length - 1]) + ) + return; + + const newTargetPoint = get_point_by_price(p); + let left_point, right_point; + const BIN_WIDTH = getConfig().bin * pool.point_delta; + if (reverse) { + left_point = Math.min( + get_bin_point_by_point(newTargetPoint + BIN_WIDTH * radius), + POINTRIGHTRANGE + ); + right_point = Math.max( + left_point - BIN_WIDTH * radius * 2, + POINTLEFTRANGE + ); + } else { + right_point = Math.min( + get_bin_point_by_point(newTargetPoint + BIN_WIDTH * radius), + POINTRIGHTRANGE + ); + left_point = Math.max( + right_point - BIN_WIDTH * radius * 2, + POINTLEFTRANGE + ); + } + + const left_price = get_price_by_point(left_point); + const right_price = get_price_by_point(right_point); + + // if left or right price is out of range then limit dragging + if (Big(left_price).lte(0) || Big(right_price).lte(0)) return; + + setTargetPoint && setTargetPoint(newTargetPoint); + setDragLeftPoint(left_point); + setDragRightPoint(right_point); + setLeftPoint && setLeftPoint(left_point); + setRightPoint && setRightPoint(right_point); + }); + d3.select(`${randomId} .radiusBar`).call(dragTarget); + } + function get_current_price(forward?: boolean) { + const current_price = get_price_by_point(pool.current_point); + if (reverse && !forward) { + return reverse_price(current_price); + } else { + return current_price; + } + } + function get_point_by_price(price: string) { + const { point_delta, token_x_metadata, token_y_metadata } = pool; + const decimalRate_point = + Math.pow(10, token_y_metadata.decimals) / + Math.pow(10, token_x_metadata.decimals); + const point = getPointByPrice(point_delta, price, decimalRate_point); + return point; + } + function get_price_by_point(point: number) { + const { token_x_metadata, token_y_metadata } = pool; + const decimalRate_price = + Math.pow(10, token_x_metadata.decimals) / + Math.pow(10, token_y_metadata.decimals); + return getPriceByPoint(point, decimalRate_price); + } + function get_nearby_bin_right_point(p: number) { + const { point_delta } = pool; + const slots = getConfig().bin * point_delta; + const point_int_bin = Math.round(p / slots) * slots; + if (point_int_bin < POINTLEFTRANGE) { + return POINTLEFTRANGE; + } else if (point_int_bin > POINTRIGHTRANGE) { + return 800000; + } + return point_int_bin + slots; + } + function get_nearby_bin_left_point(p: number) { + const { point_delta } = pool; + const slots = getConfig().bin * point_delta; + const point_int_bin = Math.round(p / slots) * slots; + if (point_int_bin < POINTLEFTRANGE) { + return POINTLEFTRANGE; + } else if (point_int_bin > POINTRIGHTRANGE) { + return 800000; + } + return point_int_bin; + } + function get_bin_point_by_point(point: number, type?: IRMTYPE) { + const { point_delta } = pool; + const slot_num = getConfig().bin; + return getBinPointByPoint(point_delta, slot_num, point, type); + } + function scaleAxis() { + if (chartType == 'USER') { + return scaleAxis_User(); + } else { + return scaleAxis_Pool(); + } + } + function scaleAxis_User() { + const { sortP } = get_price_and_liquidity_range(); + const range = [+sortP[0], +sortP[sortP.length - 1]]; + return d3 + .scaleLinear() + .domain(range) + .range([0, svgWidth - svgPaddingX * 2]); + } + function scaleAxis_Pool() { + return d3 + .scaleLinear() + .domain(price_range) + .range([0, svgWidth - svgPaddingX * 2]); + } + function scaleAxisY() { + if (chartType == 'USER') { + return scaleAxisY_User(); + } else { + return scaleAxisY_Pool(); + } + } + function scaleAxisY_User() { + const { sortY: sortL } = get_price_and_liquidity_range(); + return d3 + .scaleLinear() + .domain([0, +sortL[sortL.length - 1]]) + .range([0, wholeBarHeight - whole_bars_background_padding]) + .clamp(true); + } + function scaleAxisY_Pool() { + const { sortY: sortL } = get_price_and_liquidity_range(); + return d3 + .scaleLinear() + .domain([0, +sortL[sortL.length - 1]]) + .range([0, wholeBarHeight]); + } + + function get_price_and_liquidity_range() { + const Y: number[] = []; + const X: number[] = []; + const list = process_back_end_data_in_range(); + list.forEach((o: IChartData) => { + const { liquidity, order_liquidity, price_l, price_r } = o; + Y.push(+liquidity); + X.push(+price_l, +price_r); + if (chartType !== 'USER') { + Y.push( + +order_liquidity, + Big(liquidity).plus(order_liquidity).plus(min_bar_height).toNumber() + ); + } + }); + // X.push(+get_current_price()); + const sortY = sortBy(Y); + const sortX = sortBy(X); + return { sortP: sortX, sortY }; + } + function diffPrices(newPrice: string, peferencePrice?: string) { + let movePercent; + const price = peferencePrice || get_current_price(); + if (+price > +newPrice) { + movePercent = -Big(1) + .minus(Big(newPrice).div(price)) + .mul(100) + .toFixed(0, 1); + } else { + movePercent = Big(newPrice).div(price).minus(1).mul(100).toFixed(0, 1); + } + return movePercent; + } + function getConfig(): IChartItemConfig { + const { bin, range, colors, rangeGear } = + get_default_config_for_chart() as IChartItemConfig; + const custom_config: IChartConfig = get_custom_config_for_chart(); + const percent_final = custom_config[pool_id]?.range || range; + const bin_final = custom_config[pool_id]?.bin || bin; + const colors_final = custom_config[pool_id]?.colors || colors; + const rangeGear_final = custom_config[pool_id]?.rangeGear || rangeGear; + return { + bin: bin_final, + range: percent_final, + colors: colors_final, + rangeGear: rangeGear_final, + }; + } + function get_current_price_by_token_x() { + if (pool) { + return formatPriceWithCommas(get_current_price()); + } + return '-'; + } + function get_current_price_by_token_y() { + if (pool) { + return formatPriceWithCommas(reverse_price(get_current_price())); + } + return '-'; + } + function zoomOut() { + const { rangeGear } = getConfig(); + const targetPercent = rangeGear.find((item) => item < zoom); + if (targetPercent) { + const [new_left_price, new_right_price] = + get_price_range_by_percent(targetPercent); + set_price_range([+new_left_price, +new_right_price]); + setZoom(targetPercent); + } + } + function zoomIn() { + const { rangeGear } = getConfig(); + const index = rangeGear.findIndex((item) => item == zoom); + let targetPercent; + if (index !== 0) { + targetPercent = rangeGear[index - 1]; + } + if (targetPercent) { + const [new_left_price, new_right_price] = + get_price_range_by_percent(targetPercent); + set_price_range([+new_left_price, +new_right_price]); + setZoom(targetPercent); + } + } + const rangeGear = getConfig().rangeGear; + const is_in_max_zoom = zoom == rangeGear[rangeGear.length - 1]; + const is_in_min_zoom = zoom == rangeGear[0]; + return ( + <> +
+ {/* control button area*/} +
+
+ +
+
+ +
+ {/*
+ +
*/} +
+ + + + + + + + {/* axis */} + + {/* drag left bar */} + + + + + + + + + + + + + + {/* drag right bar*/} + + + + + + + + + + + + + + {/* overlap area between drag left and drag right */} + + + + {/* show bar in radius mode */} + + + + + + + + + + {/* show hover box then hover on the bin */} +
+
+ APR(24h) + + {binDetail?.feeApr} + +
+
+ Price + + {reverse ? ( + <> + {binDetail?.price_by_token_y} {pool?.token_x_metadata?.symbol}{' '} + / {binDetail?.price_by_token_x}{' '} + {pool?.token_y_metadata?.symbol} + + ) : ( + <> + {binDetail?.price_by_token_x} {pool?.token_y_metadata?.symbol}{' '} + / {binDetail?.price_by_token_y}{' '} + {pool?.token_x_metadata?.symbol} + + )} + +
+ {binDetail?.token_x_amount ? ( + <> +
+ + {pool?.token_x_metadata?.symbol} Amount + + + {binDetail.token_x_amount} + +
+
+
+ + in Liquidity +
+ + {binDetail.token_x_amount_in_liquidity} + +
+
+
+ + in Limit Orders +
+ + {binDetail.token_x_amount_in_order} + +
+ + ) : null} + {binDetail?.token_y_amount ? ( + <> +
+ + {pool?.token_y_metadata?.symbol} Amount + + + {binDetail.token_y_amount} + +
+
+
+ + in Liquidity +
+ + {binDetail.token_y_amount_in_liquidity} + +
+
+
+ + in Limit Orders +
+ + {binDetail.token_y_amount_in_order} + +
+ + ) : null} +
+
+
+ Your Liquidity + + {user_liquidities_detail?.total_value || '-'} + +
+
+ Price Range + + {user_liquidities_detail?.min_price} -{' '} + {user_liquidities_detail?.max_price} + + {reverse + ? pool?.token_x_metadata?.symbol + + '/' + + pool?.token_y_metadata?.symbol + : pool?.token_y_metadata?.symbol + + '/' + + pool?.token_x_metadata?.symbol} + + +
+
+ Position + + {user_liquidities_detail?.total_x_amount}{' '} + {pool?.token_x_metadata.symbol} +{' '} + {user_liquidities_detail?.total_y_amount}{' '} + {pool?.token_y_metadata.symbol} + +
+
+ APR(24h) + + {user_liquidities_detail?.apr_24 || '-'} + +
+
+ Total Earned Fee + + {user_liquidities_detail?.total_earned_fee || '-'} + +
+
+ {/* current price area */} +
+
+
+
+ + {pool?.token_x_metadata?.symbol}:{' '} + + + + {pool?.token_y_metadata?.symbol} + +
+
+ + {pool?.token_y_metadata?.symbol}:{' '} + + + + {pool?.token_x_metadata?.symbol} + +
+
+
+
+ {!chartDataListDone && ( +
+ +
+ )} + {chartDataListDone && + !chartDataList?.length && + !appearanceConfig.smallChart && ( +
+ No data +
+ )} + + ); +} diff --git a/src/components/d3Chart/Icon.tsx b/src/components/d3Chart/Icon.tsx new file mode 100644 index 000000000..7d2ba592d --- /dev/null +++ b/src/components/d3Chart/Icon.tsx @@ -0,0 +1,93 @@ +import React from 'react'; + +export function AddIcon(props: any) { + return ( + + + + + ); +} +export function SubIcon(props: any) { + return ( + + + + ); +} +export function LeftArrowIcon(props: any) { + return ( + + + + ); +} +export function RightArrowIcon(props: any) { + return ( + + + + ); +} diff --git a/src/components/d3Chart/config.tsx b/src/components/d3Chart/config.tsx new file mode 100644 index 000000000..3060f40be --- /dev/null +++ b/src/components/d3Chart/config.tsx @@ -0,0 +1,108 @@ +import { IChartConfig, IChartItemConfig } from './interfaces'; +export function get_custom_config_for_chart(): IChartConfig { + const env: string = process.env.NEAR_ENV; + if (env == 'pub-testnet') { + return {}; + } else if (env == 'testnet') { + return { + 'phoenix-bonds.testnet|wrap.testnet|2000': { + bin: 2, + range: 20, + rangeGear: [100, 80, 60, 40, 20, 10], + colors: ['#A36949', '#717C84'], + }, + 'usdc.fakes.testnet|wrap.testnet|2000': { + bin: 10, + range: 100, + rangeGear: [180, 160, 140, 120, 100, 80, 60, 40, 20, 10], + colors: ['#707C84', '#2775CA'], + }, + 'aurora.fakes.testnet|usdc.fakes.testnet|2000': + // AURORA<>USDC.e@2000 + { + bin: 10, + range: 100, + rangeGear: [180, 160, 140, 120, 100, 80, 60, 40, 20, 10], + colors: ['#79BD84', '#2775CA'], + }, + 'eth.fakes.testnet|usdc.fakes.testnet|2000': + // ETH<>USDC.e@2000 + { + bin: 4, + range: 50, + rangeGear: [130, 110, 90, 70, 50, 30, 20, 10], + colors: ['#626CA3', '#2775CA'], + }, + }; + } else { + return { + 'phoenix-bonds.near|wrap.near|2000': + // pNEAR<>NEAR@20000 + { + bin: 2, + range: 20, + rangeGear: [100, 80, 60, 40, 20, 10], + colors: ['#A36949', '#717C84'], + }, + 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near|aurora|2000': + // ETH<>USDC.e@2000 + { + bin: 2, + range: 50, + rangeGear: [130, 110, 90, 70, 50, 30, 20, 10], + colors: ['#626CA3', '#2775CA'], + }, + 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near|aaaaaa20d9e0e2461697782ef11675f668207961.factory.bridge.near|2000': + // AURORA<>USDC.e@2000 + { + bin: 5, + range: 100, + rangeGear: [180, 160, 140, 120, 100, 80, 60, 40, 20, 10], + colors: ['#79BD84', '#2775CA'], + }, + 'a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48.factory.bridge.near|wrap.near|2000': + // NEAR<>USDC.e@2000 + { + bin: 5, + range: 100, + rangeGear: [180, 160, 140, 120, 100, 80, 60, 40, 20, 10], + colors: ['#707C84', '#2775CA'], + }, + }; + } +} +/** + * + * @returns + * bin: 一个bin里slot的数量 + * range: 当前价格的百分比,获取以当前价格为中心点的左右range 默认 + * rangeGear: range 的档位,用来实现缩放 + */ +export function get_default_config_for_chart(): IChartItemConfig { + const env: string = process.env.NEAR_ENV; + if (env == 'pub-testnet') { + return { + bin: 20, + range: 40, + rangeGear: [100, 80, 60, 40, 20, 10], + colors: ['#707C84', '#2775CA'], + }; + } else if (env == 'testnet') { + return { + bin: 10, + range: 40, + rangeGear: [100, 80, 60, 40, 20, 10], + colors: ['#707C84', '#2775CA'], + }; + } else { + return { + bin: 20, + range: 40, + rangeGear: [100, 80, 60, 40, 20, 10], + colors: ['#707C84', '#2775CA'], + }; + } +} + +export const RADIUS_DEFAULT_NUMBER = 3; +export const max_nft_divisional_per_side = 3; diff --git a/src/components/d3Chart/interfaces.tsx b/src/components/d3Chart/interfaces.tsx new file mode 100644 index 000000000..c2bdce438 --- /dev/null +++ b/src/components/d3Chart/interfaces.tsx @@ -0,0 +1,118 @@ +import { UserLiquidityInfo } from '../../services/commonV3'; +export interface IChartData { + pool_id: string; + point: number; + token_x: string; + token_y: string; + liquidity: string; + + price_l?: string; + price_r?: string; + point_l?: number; + point_r?: number; + + order_x?: string; + order_y?: string; + order_liquidity?: string; + fee?: string; + total_liquidity?: string; + sort_number?: number; +} + +export interface IChartItemConfig { + bin?: number; + range?: number; + colors?: string[]; + rangeGear?: number[]; +} +export interface IChartConfig { + [token_id: string]: IChartItemConfig; +} +export interface IBinDetail { + feeApr?: string; + token_x_amount?: string; + token_x_amount_in_liquidity?: string; + token_x_amount_in_order?: string; + token_y_amount?: string; + token_y_amount_in_liquidity?: string; + token_y_amount_in_order?: string; + price_by_token_x?: string; + price_by_token_y?: string; + colors: string[]; +} + +export interface IPoolChartConfig { + svgWidth?: string | number; + svgHeight?: string | number; + disFromHoverBoxToPointer?: string | number; + disFromPercentBoxToDragBar?: string | number; + svgPaddingX?: string | number; + defaultPercent?: string | number; + whole_bars_background_padding?: string | number; + controlHidden?: boolean; + axisHidden?: boolean; + currentBarHidden?: boolean; + hoverBoxHidden?: boolean; + radiusMode?: boolean; + ticks?: number; + smallChart?: boolean; +} + +export interface IUserLiquiditiesDetail { + total_value: string; + min_price: string; + max_price: string; + total_x_amount: string; + total_y_amount: string; + apr_24: string; + total_earned_fee: string; +} + +export interface IDCLAccountFee { + total_earned_fee: { + total_fee_x: string; + total_fee_y: string; + }; + apr: { + fee_data: { + fee_x: string; + fee_y: string; + }; + user_token: { + token_x: string; + token_y: string; + timestamp: number; + }; + change_log_data: IDclLogData[]; + }; +} + +export interface IDclLogData { + event_method?: string; + token_x: string; + token_y: string; + timestamp: string; +} +export interface IProcessedLogData { + total_value: string; + distance_from_24: string; +} +export type IRMTYPE = 'round' | 'floor' | 'ceil'; + +export interface IDclChartProps { + pool_id: string; + leftPoint?: number; + rightPoint?: number; + setLeftPoint?: Function; + setRightPoint?: Function; + config?: IPoolChartConfig; + chartType?: 'POOL' | 'USER'; + removeParams?: { + fromLeft?: boolean; + fromRight?: boolean; + point?: number; + all?: boolean; + }; + newlyAddedLiquidities?: UserLiquidityInfo[]; + reverse?: boolean; +} diff --git a/src/components/d3Chart/utils.ts b/src/components/d3Chart/utils.ts new file mode 100644 index 000000000..4084ef4a4 --- /dev/null +++ b/src/components/d3Chart/utils.ts @@ -0,0 +1,105 @@ +import Big from 'big.js'; +import { + formatWithCommas, + toInternationalCurrencySystem, +} from '../../utils/numbers'; +export const formatWithCommas_usd = (v: string | number) => { + if (isInvalid(v)) return '$-'; + const big = Big(v); + if (big.eq(0)) { + return '$0'; + } else if (big.lt(0.01)) { + return '<$0.01'; + } else if (big.lt(10000)) { + return '$' + formatWithCommas(big.toFixed(2, 1)); + } else { + return '$' + formatWithCommas(big.toFixed(0, 1)); + } +}; + +export const formatPercentage = (v: string | number) => { + if (isInvalid(v)) return '-%'; + const big = Big(v); + if (big.eq(0)) { + return '0%'; + } else if (big.lt(0.01)) { + return '<0.01%'; + } else { + return big.toFixed(2, 1) + '%'; + } +}; +export const formatNumber = (v: string | number, type?: 'down' | 'both') => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.01'; + } else { + return type == 'down' ? big.toFixed(2, 0) : big.toFixed(2, 1); + } +}; +export const formatWithCommas_number = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.01'; + } else { + return formatWithCommas(big.toFixed(2, 1)); + } +}; +export const formatPrice = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.0001)) { + return '<0.0001'; + } else { + return big.toFixed(4, 0); + } +}; +export const formatPriceWithCommas = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.0001)) { + return '<0.0001'; + } else { + const p = big.toFixed(4, 0); + const [whole, decimal] = p.split('.'); + return `${formatWithCommas(whole)}${decimal ? '.' + decimal : ''}`; + } +}; +export const formatToInternationalCurrencySystem$ = (v: string | number) => { + if (isInvalid(v)) return '$-'; + return '$' + toInternationalCurrencySystem(Big(v || 0).toFixed(), 2); +}; +export const isInvalid = function (v: any) { + if (v === '' || v === undefined || v === null || v === '-') return true; + return false; +}; + +export function isValid(n: number) { + if (n !== undefined && n !== null) return true; + return false; +} +export function createRandomChar(c = 'a-z') { + switch (c) { + case 'A-Z': + return String.fromCharCode(Math.trunc(Math.random() * 25) + 65); + case 'a-z': + return String.fromCharCode(Math.trunc(Math.random() * 25) + 97); + case '0-9': + default: + return String.fromCharCode(Math.trunc(Math.random() * 10) + 48); + } +} +export function createRandomString(length = 4) { + let str = ''; + for (let i = 0; i < length; i++) str += createRandomChar(); + return str; +} diff --git a/src/components/farm/FarmsDclDetail.tsx b/src/components/farm/FarmsDclDetail.tsx index d0adc0ceb..6e970f392 100644 --- a/src/components/farm/FarmsDclDetail.tsx +++ b/src/components/farm/FarmsDclDetail.tsx @@ -1,45 +1,40 @@ import React, { useEffect, - useRef, useState, useContext, useMemo, createContext, } from 'react'; import { FormattedMessage, useIntl } from 'react-intl'; -import { isMobile } from 'src/utils/device'; import { ArrowLeftIcon, UpArrowIcon, BoostRightArrowIcon, BoostOptIcon, DclFarmIcon, - NFTIdIcon, LinkArrowIcon, NewTag, CalcIcon, } from 'src/components/icon/FarmBoost'; import { RefreshIcon } from 'src/components/icon/swapV3'; -import { AddButtonIcon } from 'src/components/icon/V3'; -import { useHistory, useLocation } from 'react-router-dom'; +import { useHistory } from 'react-router-dom'; import getConfig from '../../services/config'; -import { LinkIcon, ArrowDownHollow } from 'src/components/icon'; import { FarmBoost, Seed, claimRewardBySeed_boost, BoostConfig, - stake_boost_nft, - unStake_boost_nft, + batch_stake_boost_nft, + batch_unStake_boost_nft, + IStakeInfo, + UserSeedInfo, } from 'src/services/farm'; import { WalletContext } from '../../utils/wallets-integration'; import { toPrecision, toReadableNumber, - toNonDivisibleNumber, toInternationalCurrencySystem, formatWithCommas, - calculateFairShare, } from '../../utils/numbers'; import BigNumber from 'bignumber.js'; import { @@ -48,7 +43,7 @@ import { OprationButton, ConnectToNearBtn, } from 'src/components/button/Button'; -import { getMftTokenId, toRealSymbol } from 'src/utils/token'; +import { toRealSymbol } from 'src/utils/token'; import ReactTooltip from 'react-tooltip'; import QuestionMark from 'src/components/farm/QuestionMark'; import { LOVE_TOKEN_DECIMAL } from '../../state/referendum'; @@ -62,21 +57,18 @@ import { TOKEN_LIST_FOR_RATE, get_matched_seeds_for_dcl_pool, displayNumberToAppropriateDecimals, - get_intersection_radio, - get_intersection_icon_by_radio, getEffectiveFarmList, sort_tokens_by_base, get_pool_name, openUrl, } from 'src/services/commonV3'; import { list_liquidities, dcl_mft_balance_of } from '../../services/swapV3'; -import { AddNewPoolV3 } from 'src/components/pool/AddNewPoolV3'; -import { ftGetTokenMetadata, TokenMetadata } from 'src/services/ft-contract'; +import { TokenMetadata } from 'src/services/ft-contract'; import CalcModelDcl from '../../components/farm/CalcModelDcl'; +import { formatWithCommas_usd, formatPercentage } from './utils'; import moment from 'moment'; -const ONLY_ZEROS = /^0*\.?0*$/; +import Big from 'big.js'; const { REF_VE_CONTRACT_ID, REF_UNI_V3_SWAP_CONTRACT_ID } = getConfig(); -const FarmContext = createContext(null); export default function FarmsDclDetail(props: { detailData: Seed; emptyDetailData: Function; @@ -96,7 +88,6 @@ export default function FarmsDclDetail(props: { boostConfig, user_data, user_data_loading, - dayVolumeMap, all_seeds, } = props; const [listLiquidities, setListLiquidities] = useState( @@ -112,8 +103,6 @@ export default function FarmsDclDetail(props: { useState([]); const [listLiquiditiesLoading, setListLiquiditiesLoading] = useState(true); const [rangeSort, setRangeSort] = useState(true); - const [addLiquidityModalVisible, setAddLiquidityModalVisible] = - useState(false); const [mft_balance_in_dcl_account, set_mft_balance_in_dcl_account] = useState('0'); const [claimLoading, setClaimLoading] = useState(false); @@ -121,10 +110,8 @@ export default function FarmsDclDetail(props: { const [betterSeed, setBetterSeed] = useState(); const [isNewSeed, setIsNewSeed] = useState(false); const [seedDclCalcVisible, setSeedDclCalcVisible] = useState(false); - const s = localStorage.getItem('unavailable_positions'); - const [show_unavailable_positions, set_show_unavailable_positions] = useState( - s == '1' ? true : false - ); + const [nft_stake_loading, set_nft_stake_loading] = useState(false); + const [nft_unStake_loading, set_nft_unStake_loading] = useState(false); const { user_seeds_map = {}, user_unclaimed_map = {}, @@ -167,6 +154,46 @@ export default function FarmsDclDetail(props: { const unclaimedRewardsData = useMemo(() => { return getTotalUnclaimedRewards(); }, [user_unclaimed_map[detailData.seed_id]]); + const [yp_percent, yp_farming_value, yp_unFarm_value] = useMemo(() => { + if (!listLiquiditiesLoading) { + const { farming_parts_value, can_farm_parts_value, un_farm_parts_value } = + caculate_values(); + if (can_farm_parts_value.gt(0)) { + const percent = farming_parts_value.div(can_farm_parts_value).mul(100); + return [ + formatPercentage(percent.toFixed()), + formatWithCommas_usd(farming_parts_value.toFixed()), + formatWithCommas_usd(un_farm_parts_value.toFixed()), + ]; + } else { + return ['0%', '$0', '$0']; + } + } else { + return ['0%', '$0', '$0']; + } + }, [ + listLiquiditiesLoading, + listLiquidities_inFarimg.length, + listLiquidities_unFarimg.length, + ]); + + const canStake = useMemo(() => { + if (!listLiquiditiesLoading) { + const { canStake } = get_stake_info(); + return canStake; + } + return false; + }, [ + listLiquiditiesLoading, + listLiquidities_inFarimg.length, + listLiquidities_unFarimg.length, + ]); + const canUnStake = useMemo(() => { + if (!listLiquiditiesLoading) { + return listLiquidities_inFarimg.length; + } + }, [listLiquiditiesLoading, listLiquidities_inFarimg.length]); + function getTotalUnclaimedRewards() { let totalPrice = 0; let resultTip = ''; @@ -309,20 +336,12 @@ export default function FarmsDclDetail(props: { } ); if (liquidities_minted_in_another_seed.length > 0) { - const liquidity_another = liquidities_minted_in_another_seed[0]; - const { mft_id } = liquidity_another; - const list_new = JSON.parse(JSON.stringify(list)); - const seed_id_another = - REF_UNI_V3_SWAP_CONTRACT_ID + '@' + mft_id.slice(1); - const { free_amount = '0', locked_amount = '0' } = - user_seeds_map[seed_id_another] || {}; - const user_seed_amount_another = new BigNumber(free_amount) - .plus(locked_amount) - .toFixed(); - const seed_another = all_seeds.find((seed: Seed) => { - return seed.seed_id == seed_id_another; - }); - if (seed_another) { + const another_seeds = get_another_seeds( + liquidities_minted_in_another_seed + ); + Object.values(another_seeds).forEach((another_seed_detail: any) => { + const list_new = JSON.parse(JSON.stringify(list)); + const [seed_another, user_seed_amount_another] = another_seed_detail; const [ temp_farming_another, temp_free_another, @@ -336,21 +355,21 @@ export default function FarmsDclDetail(props: { temp_farming_another.forEach((liquidity: UserLiquidityInfo) => { temp_farming_another_map[liquidity.lpt_id] = liquidity; }); - const { min_deposit } = detailData; + // const { min_deposit } = detailData; liquidities_minted_in_another_seed.forEach( (liquidity: UserLiquidityInfo) => { const liquidity_another: UserLiquidityInfo = temp_farming_another_map[liquidity.lpt_id]; - const v_liquidity = mint_liquidity(liquidity, detailData.seed_id); + // const v_liquidity = mint_liquidity(liquidity, detailData.seed_id); if (+liquidity_another?.part_farm_ratio > 0) { liquidity.status_in_other_seed = 'staked'; } - if (new BigNumber(v_liquidity).isLessThan(min_deposit)) { - liquidity.less_than_min_deposit = true; - } + // if (new BigNumber(v_liquidity).isLessThan(min_deposit)) { + // liquidity.less_than_min_deposit = true; + // } } ); - } + }); const temp_unavailable_new: UserLiquidityInfo[] = []; const frees_extra = temp_unavailable.filter( (liquidity: UserLiquidityInfo) => { @@ -364,7 +383,7 @@ export default function FarmsDclDetail(props: { if ( !( liquidity.status_in_other_seed == 'staked' || - liquidity.less_than_min_deposit || + // liquidity.less_than_min_deposit || !inRange || (!mft_id && amount_is_little) ) @@ -388,6 +407,26 @@ export default function FarmsDclDetail(props: { setListLiquiditiesLoading(false); } } + function get_another_seeds(minted_liquidities: UserLiquidityInfo[]) { + const target: any = {}; + minted_liquidities.forEach((liquidity_minted_in_another_seed) => { + const { mft_id } = liquidity_minted_in_another_seed; + const seed_id_another = + REF_UNI_V3_SWAP_CONTRACT_ID + '@' + mft_id.slice(1); + const { free_amount = '0', locked_amount = '0' } = + user_seeds_map[seed_id_another] || {}; + const user_seed_amount_another = new BigNumber(free_amount) + .plus(locked_amount) + .toFixed(); + const seed_another: Seed = all_seeds.find((seed: Seed) => { + return seed.seed_id == seed_id_another; + }); + if (seed_another) { + target[seed_another.seed_id] = [seed_another, user_seed_amount_another]; + } + }); + return target; + } function sortTokens(tokens: TokenMetadata[]) { tokens.sort((a: TokenMetadata, b: TokenMetadata) => { if (a.symbol === 'NEAR') return 1; @@ -912,7 +951,188 @@ export default function FarmsDclDetail(props: { const [tokenx, tokeny, fee] = detailData?.pool?.pool_id?.split('|') || ''; return +fee / 10000 + '%'; } + function get_liquidity_value( + liquidity: UserLiquidityInfo, + leftPoint?: number, + rightPoint?: number + ) { + const { amount } = liquidity; + const poolDetail = detailData.pool; + const { token_x, token_y } = poolDetail; + const v = get_total_value_by_liquidity_amount_dcl({ + left_point: leftPoint || liquidity.left_point, + right_point: rightPoint || liquidity.right_point, + poolDetail, + amount, + price_x_y: { + [token_x]: tokenPriceList[token_x]?.price || '0', + [token_y]: tokenPriceList[token_y]?.price || '0', + }, + metadata_x_y: { + [token_x]: tokens[0], + [token_y]: tokens[1], + }, + }); + return v; + } const radio = getBoostMutil(); + /** new start */ + function caculate_values() { + const can_farm_liquidities = listLiquidities_inFarimg.concat( + listLiquidities_unFarimg + ); + // 能farm部分的的流动性的总价值 + let can_farm_parts_value = Big(0); + can_farm_liquidities.forEach((l: UserLiquidityInfo) => { + const l_v = get_range_part_value(l); + can_farm_parts_value = can_farm_parts_value.plus(l_v || 0); + }); + + // unFarming 部分流动性的总价值(包含部分在farm中的那个NFT剩余的部分) + let un_farm_parts_value = Big(0); + listLiquidities_unFarimg.forEach((l: UserLiquidityInfo) => { + const l_v = get_range_part_value(l); + un_farm_parts_value = un_farm_parts_value.plus(l_v || 0); + }); + const [part_farm_liquidity] = listLiquidities_inFarimg.filter( + (l: UserLiquidityInfo) => { + return Big(l.unfarm_part_amount || 0).gt(0); + } + ); + if (part_farm_liquidity) { + const v = get_range_part_value(part_farm_liquidity); + const un_farm_part_value = Big(100) + .minus(part_farm_liquidity.part_farm_ratio) + .div(100) + .mul(v); + un_farm_parts_value = un_farm_parts_value.plus(un_farm_part_value); + } + + // farming 部分流动性的总价值 + const farming_parts_value = can_farm_parts_value.minus(un_farm_parts_value); + return { + can_farm_parts_value, + un_farm_parts_value, + farming_parts_value, + }; + } + function get_range_part_value(liquidity: UserLiquidityInfo) { + const [left_point, right_point] = get_valid_range( + liquidity, + detailData.seed_id + ); + const v = get_liquidity_value(liquidity, left_point, right_point); + return v; + } + function batchStakeNFT() { + set_nft_stake_loading(true); + const { liquidities, total_v_liquidity, withdraw_amount } = + get_stake_info(); + batch_stake_boost_nft({ + liquidities, + total_v_liquidity, + withdraw_amount, + seed_id: detailData.seed_id, + }); + } + function batchUnStakeNFT() { + set_nft_unStake_loading(true); + const unStake_info: IStakeInfo = get_unStake_info(); + batch_unStake_boost_nft(unStake_info); + } + function get_stake_info(): IStakeInfo { + const { seed_id, min_deposit } = detailData; + let total_v_liquidity = Big(0); + let withdraw_amount = Big(0); + const liquidities: UserLiquidityInfo[] = listLiquidities_unFarimg.concat( + [] + ); + listLiquidities_unFarimg.forEach((l: UserLiquidityInfo) => { + const v_liquidity = mint_liquidity(l, seed_id); + total_v_liquidity = total_v_liquidity.plus(v_liquidity); + }); + + const [part_farm_liquidity] = listLiquidities_inFarimg.filter( + (l: UserLiquidityInfo) => { + return Big(l.unfarm_part_amount || 0).gt(0); + } + ); + if (part_farm_liquidity) { + total_v_liquidity = total_v_liquidity.plus( + part_farm_liquidity.unfarm_part_amount + ); + liquidities.push(part_farm_liquidity); + } + if (total_v_liquidity.lt(min_deposit)) { + if (part_farm_liquidity) { + const v_liquidity = mint_liquidity(part_farm_liquidity, seed_id); + withdraw_amount = new Big(v_liquidity).minus( + part_farm_liquidity.unfarm_part_amount + ); + total_v_liquidity = total_v_liquidity.plus(withdraw_amount); + } + } + return { + liquidities, + total_v_liquidity: total_v_liquidity.toFixed(), + withdraw_amount: withdraw_amount.toFixed(), + canStake: total_v_liquidity.lt(min_deposit) || isEnded ? false : true, + }; + } + function get_unStake_info() { + const { free_amount = '0', locked_amount = '0' } = + user_seeds_map[detailData.seed_id] || {}; + const user_seed_amount = new BigNumber(free_amount) + .plus(locked_amount) + .toFixed(); + return { + liquidities: listLiquidities_inFarimg, + withdraw_amount: user_seed_amount, + seed_id: detailData.seed_id, + }; + } + // get unavailable text + function get_unavailable_text() { + let tip = ''; + const { seed_id, min_deposit } = detailData; + const { total_v_liquidity } = get_stake_info(); + for (let i = 0; i < listLiquidities_unavailable.length; i++) { + const liquidity = listLiquidities_unavailable[i]; + const [left_point, right_point] = get_valid_range(liquidity, seed_id); + const inrange = +right_point > +left_point; + if (!inrange) { + tip = intl.formatMessage({ id: 'your_price_range_tip' }); + break; + } else if (liquidity.status_in_other_seed == 'staked') { + tip = intl.formatMessage({ id: 'position_has_staked_tip' }); + } + } + + if (listLiquidities_unavailable.length && !tip) { + // too little to mint + tip = 'The Liquidity amount is too little to mint'; + } + if (!tip && Big(total_v_liquidity).gt(0)) { + // min deposit + const rate = new BigNumber(min_deposit).dividedBy(total_v_liquidity); + let rate_display = rate.toFixed(1); + if (rate.isGreaterThan(rate_display)) { + rate_display = new BigNumber(rate_display).plus(0.1).toFixed(); + } + // your liquidity + tip = + intl.formatMessage({ id: 'minimum_tip' }) + + ' ' + + `${rate_display}x` + + ' ' + + intl.formatMessage({ id: 'your_liquidity_3' }); + } + return tip; + } + /** new end */ + const isEmpty = !canStake && !canUnStake; + const stakeDisabled = !canStake || nft_stake_loading; + return (
@@ -1023,7 +1243,6 @@ export default function FarmsDclDetail(props: {
) : null} - {/* baseData for PC*/}
@@ -1192,342 +1411,123 @@ export default function FarmsDclDetail(props: {
{/* login area */} - {/* unClaimed Rewards for PC */} -
-
- -
- - -
-
-
-
- - {unclaimedRewardsData.worth} - - -
- { - if (!unclaimedRewardsData.showClaimButton) return; - e.stopPropagation(); - claimReward(); - }} - > - } - /> - -
-
- {/* unClaimed Rewards for Mobile */} + {/* add liquidity entry bar */} +
-
-
- -
- - -
-
- -
- - {unclaimedRewardsData.worth} - - -
-
- {unclaimedRewardsData.showClaimButton ? ( -
-
- - -
- { - e.stopPropagation(); - claimReward(); - }} - > - } - /> - -
- ) : null} - + {/* Your Farming Position */}
- {unclaimedRewardsData.list.map( - ( - { - token, - amount, - preAmount, - }: { token: TokenMetadata; amount: string; preAmount: string }, - index: number - ) => { - return ( -
-
- - - {toRealSymbol(token.symbol)} - -
-
- {preAmount ? ( - <> - {preAmount} - - - - {amount} - - ) : ( - {amount} - )} -
-
- ); - } - )} -
-
- {/* Your Positions */} -
-
-
{ - setAddLiquidityModalVisible(true); - }} - className={`flex items-center text-sm text-primaryText p-1.5 cursor-pointer border border-v3HoverDarkBgColor hover:text-white hover:bg-dclButtonBgColor border-dashed rounded-lg ${ - listLiquidities.length > 0 ? '' : 'hidden' - }`} - > - - -
-
- {listLiquidities.length == 0 && !listLiquiditiesLoading && !isEnded ? ( -
-
-
- - +
+
+ + Your Farming Position - { - setAddLiquidityModalVisible(true); - }} - color="#fff" - borderRadius="8px" - className={`flex-shrink-0 px-4 h-10 text-center text-sm text-white ml-2 xsm:w-full xsm:ml-0 xsm:mt-2.5`} + - - + {yp_farming_value} +
- ) : null} - - {listLiquidities_inFarimg.length > 0 ? ( - <> -
- -
- {listLiquidities_inFarimg.map((liquidity: UserLiquidityInfo) => { - return ( - - ); - })} - - ) : null} - - {listLiquidities_unFarimg.length > 0 ? ( - <> -
- +
+
+
+ + {yp_unFarm_value} + {' '} + available to stake
- {listLiquidities_unFarimg.map((liquidity: UserLiquidityInfo) => { - return ( - - ); - })}{' '} - - ) : null} + {/* {!isEnded && !canStake && ( +
+ {get_unavailable_text()} +
+ )} */} +
+
+ {!isEnded && ( + + ( + + )} + /> + + )} - {listLiquidities_unavailable.length > 0 ? ( - <> -
{ - const current_status = !show_unavailable_positions; - localStorage.setItem( - 'unavailable_positions', - current_status ? '1' : '0' - ); - set_show_unavailable_positions(current_status); - }} - > - - {show_unavailable_positions ? ( - - ) : ( - - )} - - -
-
- {listLiquidities_unavailable.map( - (liquidity: UserLiquidityInfo) => { - return ( - - ); - } - )} -
- - ) : null} - -
- {/* add liquidity modal */} - { - setAddLiquidityModalVisible(false); - }} - seed={detailData} - tokenPriceList={tokenPriceList} - style={{ - overlay: { - backdropFilter: 'blur(15px)', - WebkitBackdropFilter: 'blur(15px)', - }, - content: { - outline: 'none', - transform: 'translate(-50%, -50%)', - }, - }} - > - {/* caculator */} - {seedDclCalcVisible ? ( - { - e.stopPropagation(); + {canUnStake ? ( + + ( + + )} + /> + + ) : null} +
+
+
+ {/* unClaimed Rewards*/} + +
+ {/* caculator */} + {seedDclCalcVisible ? ( + { + e.stopPropagation(); setSeedDclCalcVisible(false); }} seed={detailData} @@ -1547,189 +1547,217 @@ export default function FarmsDclDetail(props: {
); } - -function LiquidityLine(props: { - liquidity: UserLiquidityInfo; - status: StatusType; +function AddLiquidityEntryBar(props: { + loading: boolean; + inFarimg: UserLiquidityInfo[]; + unFarimg: UserLiquidityInfo[]; + unavailable: UserLiquidityInfo[]; + detailData: Seed; + isEnded: boolean; +}) { + let tip: any; + const { loading, inFarimg, unFarimg, unavailable, detailData, isEnded } = + props; + if (!loading && inFarimg.length == 0 && unFarimg.length == 0) { + // if (unavailable.length == 0) { + // tip = ; + // } else { + // tip = + // 'The price range of your liquidity is out of reward range. Please add liquidity within reward range.'; + // } + tip = + "You don't have liquidity during the farm reward range, click 'Add Liquidity' to start farming."; + } + if (loading || !tip || isEnded) return null; + return ( +
+
+
+

{tip}

+ { + const poolId = detailData.pool.pool_id; + const params_str = get_pool_name(poolId); + openUrl(`/addLiquidityV2#${params_str}`); + }} + color="#fff" + minWidth="9rem" + className={`flex-shrink-0 px-1 h-8 lg:ml-5 text-center text-sm text-white focus:outline-none font-semibold xsm:w-full `} + > + + +
+
+ ); +} +function UserTotalUnClaimBlock(props: { + detailData: Seed; + tokenPriceList: any; + user_seeds_map: Record; + user_unclaimed_token_meta_map: Record; + user_unclaimed_map: Record; + radio?: string | number; }) { - const { liquidity, status } = props; const { detailData, tokenPriceList, - tokens, - mft_balance_in_dcl_account, - rate_need_to_reverse_display, - all_seeds, - isPending, - isEnded, - rangeSort, - } = useContext(FarmContext); - const [nft_stake_loading, set_nft_stake_loading] = useState(false); - const [nft_unStake_loading, set_nft_unStake_loading] = useState(false); - const [hover, setHover] = useState(false); - const [dclCalcVisible, setDclCalcVisible] = useState(false); - const liquidity_status_string: StatusType | string = useMemo(() => { - if (!(liquidity && detailData)) return ''; - const { unfarm_part_amount } = liquidity; - let farmStatus: StatusType = status; - if (status == 'farming' && +unfarm_part_amount > 0) { - farmStatus = 'partialfarming'; - } - return farmStatus; - }, [liquidity, detailData]); - const [ - liquidity_status_display, - liquidity_operation_display, - liquidity_partialfarming_stakebuttonstatus_display, - ]: any = useMemo(() => { - if (!(liquidity && detailData && liquidity_status_string)) - return [null, [], '']; - const part_farm_ratio = liquidity.part_farm_ratio; - let status; - let operation: string[] = []; - let stakeButtonStatus = ''; - if (liquidity_status_string == 'farming') { - status = ( - - - - ); - operation = ['unstake']; - } else if (liquidity_status_string == 'unavailable') { - status = ( - - - - ); - } else if (liquidity_status_string == 'unfarming') { - status = ( - - - - ); - operation = ['stake']; - } else { - const part_farm_ratio_big = new BigNumber(part_farm_ratio); - let percent = '-%'; - if (part_farm_ratio_big.isLessThan(1)) { - percent = '<1%'; - } else { - percent = `${part_farm_ratio_big.toFixed(0, 1)}%`; - } - status = ( - - {percent} Partial Farming - - ); - operation = ['stake', 'unstake']; - if ( - new BigNumber(liquidity.v_liquidity).isLessThan(detailData.min_deposit) - ) { - stakeButtonStatus = 'disable'; - } else { - stakeButtonStatus = 'enable'; - } - } - return [status, operation, stakeButtonStatus]; - }, [liquidity, detailData, liquidity_status_string]); - useEffect(() => { - get_each_token_apr_for_nft(); - }, [detailData, tokenPriceList, liquidity]); + user_seeds_map, + user_unclaimed_token_meta_map, + user_unclaimed_map, + radio, + } = props; + const [claimLoading, setClaimLoading] = useState(false); + const [showDetail, setShowDetail] = useState(false); + const { seed_id } = detailData; + const { globalState } = useContext(WalletContext); + const isSignedIn = globalState.isSignedIn; const intl = useIntl(); - - function get_liquidity_value(liquidity: UserLiquidityInfo) { - const { left_point, right_point, amount } = liquidity; - const poolDetail = detailData.pool; - const { token_x, token_y } = poolDetail; - const v = get_total_value_by_liquidity_amount_dcl({ - left_point, - right_point, - poolDetail, - amount, - price_x_y: { - [token_x]: tokenPriceList[token_x]?.price || '0', - [token_y]: tokenPriceList[token_y]?.price || '0', - }, - metadata_x_y: { - [token_x]: tokens[0], - [token_y]: tokens[1], - }, + function claimReward() { + if (claimLoading) return; + setClaimLoading(true); + claimRewardBySeed_boost(detailData.seed_id).catch((error) => { + setClaimLoading(false); }); - return v; } - function get_liquidity_value_display(liquidity: UserLiquidityInfo) { - const v = get_liquidity_value(liquidity); - const v_big = new BigNumber(v); - if (v_big.isLessThan(0.01)) { - return `<$0.01`; - } else { - return `$${formatWithCommas(toPrecision(v.toString(), 2))}`; - } - } - function get_your_range(liquidity: UserLiquidityInfo) { - const { left_point, right_point } = liquidity; - const [token_x_metadata, token_y_metadata] = - detailData.pool.tokens_meta_data; - const [fixRange, dcl_pool_id, seed_left_point, seed_right_point] = - detailData.seed_id.split('@')[1].split('&'); - let icon; - const radio = get_intersection_radio({ - left_point_liquidity: left_point, - right_point_liquidity: right_point, - left_point_seed: seed_left_point, - right_point_seed: seed_right_point, + function getTotalUnclaimedRewards() { + let totalPrice = 0; + let resultTip = ''; + const tempFarms = {}; + + detailData.farmList.forEach((farm: FarmBoost) => { + tempFarms[farm.terms.reward_token] = true; }); - const p = new BigNumber(radio); - const Icon = get_intersection_icon_by_radio(radio); - icon = ; - const decimalRate = - Math.pow(10, token_x_metadata.decimals) / - Math.pow(10, token_y_metadata.decimals); - let left_price = getPriceByPoint(+left_point, decimalRate); - let right_price = getPriceByPoint(+right_point, decimalRate); - if (!rangeSort) { - const temp = left_price; - left_price = new BigNumber(1).dividedBy(right_price).toFixed(); - right_price = new BigNumber(1).dividedBy(temp).toFixed(); - } - const display_left_price = left_price; - const display_right_price = right_price; - function rangeTip() { - // const tip = intl.formatMessage({ id: 'farmRewardsCopy' }); - const tip = - 'The intersection of your price range and the farm reward range.'; - let result: string = `
${tip}
`; - return result; - } - function displayP(p: BigNumber) { - if (p.isEqualTo(0)) { - return '0%'; - } else if (p.isLessThan(1)) { - return '1%'; + const isEnded = detailData.farmList[0].status == 'Ended'; + const unclaimed = user_unclaimed_map[seed_id] || {}; + const unClaimedTokenIds = Object.keys(unclaimed); + const tokenList: any[] = []; + unClaimedTokenIds?.forEach((tokenId: string) => { + const token: TokenMetadata = user_unclaimed_token_meta_map[tokenId]; + // total price + const { id, decimals, icon } = token; + const amount = toReadableNumber(decimals, unclaimed[id] || '0'); + const tokenPrice = tokenPriceList[id]?.price; + if (tokenPrice && tokenPrice != 'N/A') { + totalPrice += +amount * tokenPrice; + } + // rewards number + let displayNum = ''; + if (new BigNumber('0').isEqualTo(amount)) { + displayNum = '-'; + } else if (new BigNumber('0.001').isGreaterThan(amount)) { + displayNum = '<0.001'; } else { - return p?.toFixed(0, 1) + '%'; + displayNum = new BigNumber(amount).toFixed(3, 1); + } + // before boost number + let beforeNum = ''; + if (radio) { + const v = new BigNumber(amount).dividedBy(radio); + if (new BigNumber('0').isEqualTo(v)) { + beforeNum = '-'; + } else if (new BigNumber('0.001').isGreaterThan(v)) { + beforeNum = '<0.001'; + } else { + beforeNum = new BigNumber(v).toFixed(3, 1); + } } + const tempTokenData = { + token, + amount: displayNum, + preAmount: beforeNum, + }; + tokenList.push(tempTokenData); + const txt = intl.formatMessage({ id: 'ended_search' }); + const itemHtml = `
+ +
+ ${formatWithCommas(displayNum)} + ${ + !isEnded && !tempFarms[id] + ? `${txt}` + : '' + } +
+
`; + resultTip += itemHtml; + }); + if (totalPrice == 0) { + return { + worth: , + showClaimButton: false, + tip: resultTip, + list: tokenList, + }; + } else if (new BigNumber('0.01').isGreaterThan(totalPrice)) { + return { + worth: '<$0.01', + showClaimButton: true, + tip: resultTip, + list: tokenList, + }; + } else { + return { + worth: `$${toInternationalCurrencySystem(totalPrice.toString(), 2)}`, + showClaimButton: true, + tip: resultTip, + list: tokenList, + }; } - return ( -
- - {displayNumberToAppropriateDecimals(display_left_price)} ~{' '} - {displayNumberToAppropriateDecimals(display_right_price)} - + } + const unclaimedRewardsData = useMemo(() => { + return getTotalUnclaimedRewards(); + }, [user_unclaimed_map[seed_id]]); + function switchDetailButton() { + setShowDetail(!showDetail); + } + function valueOfRewardsTip() { + const tip = intl.formatMessage({ id: 'farmRewardsCopy' }); + let result: string = `
${tip}
`; + return result; + } + return ( +
+
+
+ +
+ + +
+
+
- - {icon} - + + {unclaimedRewardsData.worth}
- ); - } - function get_each_token_apr_for_nft() { - const { farmList, total_seed_amount, total_seed_power } = detailData; - // principal - const total_principal = get_liquidity_value(liquidity); - // lp percent - let percent: BigNumber = new BigNumber(0); - const mint_amount = mint_liquidity(liquidity, detailData.seed_id); - const part_farm_ratio = liquidity.part_farm_ratio; - if (+part_farm_ratio == 100) { - // full farming - if (+total_seed_power > 0) { - percent = new BigNumber(mint_amount).dividedBy(total_seed_power); - } - } else if (+part_farm_ratio > 0 && +part_farm_ratio < 100) { - // partial Farming - if (+total_seed_power > 0) { - const partial_amount = new BigNumber(mint_amount) - .multipliedBy(part_farm_ratio) - .dividedBy(100); - percent = partial_amount.dividedBy(total_seed_power); - } - } else { - // unFarming, unavailable - const temp_total = new BigNumber(total_seed_power || 0).plus(mint_amount); - if (temp_total.isGreaterThan(0)) { - percent = new BigNumber(mint_amount).dividedBy(temp_total); - } - } - // get apr per token - const farmList_parse = JSON.parse(JSON.stringify(farmList || [])); - farmList_parse.forEach((farm: FarmBoost) => { - const { token_meta_data } = farm; - const { daily_reward, reward_token } = farm.terms; - const quantity = toReadableNumber(token_meta_data.decimals, daily_reward); - const reward_token_price = Number( - tokenPriceList[reward_token]?.price || 0 - ); - const cur_token_rewards = new BigNumber(quantity) - .multipliedBy(reward_token_price) - .multipliedBy(365); - const user_can_get_token_rewards = new BigNumber( - cur_token_rewards - ).multipliedBy(percent); - if (+total_principal > 0) { - farm.yourNFTApr = new BigNumber(user_can_get_token_rewards) - .dividedBy(total_principal) - .toFixed(); - } else { - farm.yourNFTApr = '0'; - } - }); - liquidity.farmList = farmList_parse; - } - function get_your_total_apr() { - const farms = liquidity.farmList || []; - let apr = new BigNumber(0); - const allPendingFarms = isPending(); - farms.forEach(function (item: FarmBoost) { - const pendingFarm = item.status == 'Created' || item.status == 'Pending'; - if (allPendingFarms || !pendingFarm) { - apr = new BigNumber(apr).plus(item.yourNFTApr || 0); - } - }); - return apr.multipliedBy(100).toFixed(); - } - function display_your_apr() { - const total_apr = new BigNumber(get_your_total_apr()); - if (total_apr.isEqualTo('0')) { - return '0%'; - } else if (total_apr.isLessThan(0.01)) { - return `<0.01%`; - } else { - return `${toPrecision(total_apr.toFixed(), 2)}%`; - } - } - function get_your_apr_copy(liquidity: UserLiquidityInfo) { - const { farmList, total_seed_amount, total_seed_power } = detailData; - // principal - const total_principal = get_liquidity_value(liquidity); - // seed total rewards - let total_rewards = '0'; - farmList.forEach((farm: FarmBoost) => { - const { token_meta_data } = farm; - const { daily_reward, reward_token } = farm.terms; - const quantity = toReadableNumber(token_meta_data.decimals, daily_reward); - const reward_token_price = Number( - tokenPriceList[reward_token]?.price || 0 - ); - const cur_token_rewards = new BigNumber(quantity) - .multipliedBy(reward_token_price) - .multipliedBy(365); - total_rewards = cur_token_rewards.plus(total_rewards).toFixed(); - }); - // lp percent - let percent: BigNumber = new BigNumber(0); - const mint_amount = mint_liquidity(liquidity, detailData.seed_id); - const part_farm_ratio = liquidity.part_farm_ratio; - if (+part_farm_ratio == 100) { - // full farming - if (+total_seed_power > 0) { - percent = new BigNumber(mint_amount).dividedBy(total_seed_power); - } - } else if (+part_farm_ratio > 0 && +part_farm_ratio < 100) { - // partial Farming - if (+total_seed_power > 0) { - const partial_amount = new BigNumber(mint_amount) - .multipliedBy(part_farm_ratio) - .dividedBy(100); - percent = partial_amount.dividedBy(total_seed_power); - } - } else { - // unFarming, unavailable - const temp_total = new BigNumber(total_seed_power || 0).plus(mint_amount); - if (temp_total.isGreaterThan(0)) { - percent = new BigNumber(mint_amount).dividedBy(temp_total); - } - } - // profit - let profit; - if (percent) { - profit = percent.multipliedBy(total_rewards); - } - // your apr - if (profit) { - if (+total_principal == 0) { - return 0; - } - const your_apr = profit.dividedBy(total_principal).multipliedBy(100); - if (your_apr.isEqualTo('0')) { - return '0%'; - } else if (your_apr.isLessThan(0.01)) { - return `<0.01%`; - } else { - return `${toPrecision(your_apr.toFixed(), 2)}%`; - } - } else { - return '-'; - } - } - function getYourLiquidityAprTip() { - const { farmList } = liquidity; - if (!farmList) return ''; - const tempList = farmList; - const lastList: any[] = []; - const pending_farms: FarmBoost[] = []; - const no_pending_farms: FarmBoost[] = []; - const totalApr = get_your_total_apr(); - const txt = intl.formatMessage({ id: 'reward_apr' }); - tempList.forEach((farm: FarmBoost) => { - if (farm.status == 'Created') { - pending_farms.push(farm); - } else { - no_pending_farms.push(farm); - } - }); - if (pending_farms.length > 0) { - pending_farms.forEach((farm: FarmBoost) => { - lastList.push({ - rewardToken: farm.token_meta_data, - apr: new BigNumber(farm.yourNFTApr || 0) - .multipliedBy(100) - .toFixed() - .toString(), - startTime: farm.terms.start_at, - pending: true, - }); - }); - } - if (no_pending_farms.length > 0) { - const mergedFarms = mergeCommonRewardsFarms( - JSON.parse(JSON.stringify(no_pending_farms)) - ); - mergedFarms.forEach((farm: FarmBoost) => { - lastList.push({ - rewardToken: farm.token_meta_data, - apr: new BigNumber(farm.yourNFTApr || 0) - .multipliedBy(100) - .toFixed() - .toString(), - }); - }); - } - // show last display string - let result: string = ''; - result = ` -
- ${txt} - ${ - toPrecision(totalApr.toString(), 2) + '%' - } -
- `; - lastList.forEach((item: any) => { - const { rewardToken, apr, pending, startTime } = item; - const token = rewardToken; - let itemHtml = ''; - if (pending) { - const startDate = moment.unix(startTime).format('YYYY-MM-DD'); - const txt = intl.formatMessage({ id: 'start' }); - itemHtml = `
- -
- - - -
-
`; - } else { - itemHtml = `
- - -
`; - } - result += itemHtml; - }); - return result; - } - function mergeCommonRewardsFarms(farms: FarmBoost[]) { - const tempMap = {}; - farms.forEach((farm: FarmBoost) => { - const { reward_token, daily_reward } = farm.terms; - let preMergedfarms: FarmBoost = tempMap[reward_token]; - if (preMergedfarms) { - preMergedfarms.yourNFTApr = new BigNumber( - preMergedfarms.yourNFTApr || 0 - ) - .plus(farm.yourNFTApr) - .toFixed() - .toString(); - preMergedfarms.terms.daily_reward = new BigNumber( - preMergedfarms.terms.daily_reward - ) - .plus(daily_reward) - .toFixed(); - } else { - tempMap[reward_token] = farm; - } - }); - return Object.values(tempMap); - } - function stakeNFT(liquidity: UserLiquidityInfo) { - set_nft_stake_loading(true); - let finalAmount; - const { seed_id, min_deposit } = detailData; - const { mft_id, part_farm_ratio, unfarm_part_amount } = liquidity; - const v_liquidity = mint_liquidity(liquidity, seed_id); - let needUnstake = false; - let withdraw_amount = '0'; - if (!mft_id || new BigNumber(part_farm_ratio || 0).isEqualTo(0)) { - finalAmount = v_liquidity; - } else { - if (new BigNumber(unfarm_part_amount).isLessThan(min_deposit)) { - needUnstake = true; - withdraw_amount = new BigNumber(v_liquidity) - .minus(unfarm_part_amount) - .toFixed(); - } else { - finalAmount = unfarm_part_amount; - } - } - stake_boost_nft({ - liquidity, - seed_id, - stakeAmount: finalAmount, - withdraw_amount, - needUnstake, - }); - } - function unStakeNFT(liquidity: UserLiquidityInfo) { - set_nft_unStake_loading(true); - const amount = liquidity.v_liquidity; - const mft_balance_big = new BigNumber(mft_balance_in_dcl_account); - const amount_big = new BigNumber(amount); - let finalAmount = '0'; - if (mft_balance_big.isEqualTo('0')) { - finalAmount = amount; - } else if (mft_balance_big.isGreaterThanOrEqualTo(amount)) { - finalAmount = '0'; - } else { - finalAmount = amount_big.minus(mft_balance_in_dcl_account).toFixed(); - } - unStake_boost_nft({ - lpt_id: liquidity.lpt_id, - seed_id: detailData.seed_id, - withdraw_amount: finalAmount, - }); - } - function goYourLiquidityDetail(liquidity: UserLiquidityInfo) { - liquidity.lpt_id.split('#')[0]; - const pool_id = liquidity.lpt_id.split('#')[0]; - const lpt_index = liquidity.lpt_id.split('#')[1]; - openUrl(`/yoursLiquidityDetailV2/${get_pool_name(pool_id)}@${lpt_index}`); - } - function unavailableTip() { - const tip = unavailableText(); - const result: string = `
${tip}
`; - return result; - } - function unavailableText() { - let tip = ''; - const { seed_id, min_deposit } = detailData; - const [left_point, right_point] = get_valid_range(liquidity, seed_id); - const inrange = +right_point > +left_point; - if (!inrange) { - tip = intl.formatMessage({ id: 'your_price_range_tip' }); - } else if (liquidity.status_in_other_seed == 'staked') { - tip = intl.formatMessage({ id: 'position_has_staked_tip' }); - } else { - const v_liquidity = mint_liquidity(liquidity, seed_id); - const rate = new BigNumber(min_deposit).dividedBy(v_liquidity); - let rate_display = rate.toFixed(1); - if (rate.isGreaterThan(rate_display)) { - rate_display = new BigNumber(rate_display).plus(0.1).toFixed(); - } - // your liquidity - tip = - intl.formatMessage({ id: 'minimum_tip' }) + - ' ' + - `${rate_display}x` + - ' ' + - intl.formatMessage({ id: 'your_liquidity_3' }); - } - return tip; - } - function unavailableDiv() { - let tip; - const { seed_id, min_deposit } = detailData; - const [left_point, right_point] = get_valid_range(liquidity, seed_id); - const inrange = +right_point > +left_point; - if (!inrange) { - tip = intl.formatMessage({ id: 'your_price_range_tip' }); - } else if (liquidity.status_in_other_seed == 'staked') { - const { mft_id } = liquidity; - const link: string = get_target_seed_url_link({ - all_seeds, - mft_id, - }); - tip = ( - <> - {' '} -
{ - openUrl(link); - }} - > - - - - -
- - ); - } else { - const v_liquidity = mint_liquidity(liquidity, seed_id); - const rate = new BigNumber(min_deposit).dividedBy(v_liquidity); - let rate_display = rate.toFixed(1); - if (rate.isGreaterThan(rate_display)) { - rate_display = new BigNumber(rate_display).plus(0.1).toFixed(); - } - tip = - intl.formatMessage({ id: 'minimum_tip' }) + - ' ' + - `${rate_display}x` + - ' ' + - intl.formatMessage({ id: 'your_liquidity_3' }); - } - return tip; - } - function get_target_seed_url_link({ - all_seeds, - mft_id, - }: { - all_seeds: Seed[]; - mft_id: string; - }) { - const seed_id = REF_UNI_V3_SWAP_CONTRACT_ID + '@' + mft_id.slice(1); - const temps = all_seeds.filter((seed: Seed) => { - return seed.seed_id == seed_id; - }); - temps.sort((b: Seed, a: Seed) => { - if (b.farmList[0].status == 'Ended') return 1; - if (a.farmList[0].status == 'Ended') return -1; - return 0; - }); - const target = temps[0]; - const [fixRange, pool_id, left_point, right_point] = mft_id.split('&'); - const status = target?.farmList[0].status == 'Ended' ? 'e' : 'r'; - const params = `${get_pool_name(pool_id)}[${left_point}-${right_point}]`; - const link = `/v2farms/${params}-${status}`; - return link; - } - function apr_title() { - if ( - liquidity_status_string == 'farming' || - liquidity_status_string == 'partialfarming' - ) { - return ; - } else { - return ; - } - } - const showStakeButton = - !isEnded && liquidity_operation_display.indexOf('stake') > -1; - const showUnStakeButton = liquidity_operation_display.indexOf('unstake') > -1; - return ( - <> - {/* for PC */} -
{ - setHover(false); - }} - > -
- - - NFT ID #{liquidity.lpt_id.split('#')[1]} - -
-
+ {unclaimedRewardsData.showClaimButton ? ( +
setHover(true)} - className="flex items-stretch justify-between pt-7 pb-3.5 px-6" - > -
- - - - - {get_liquidity_value_display(liquidity)} - -
-
- - - - - {get_your_range(liquidity)} - -
-
-
- {apr_title()} - { - e.stopPropagation(); - setDclCalcVisible(true); - }} - className="text-farmText ml-1.5 cursor-pointer hover:text-greenColor" - /> -
-
- - {display_your_apr()} - - -
-
-
- - - -
- {liquidity_status_display} -
-
-
-
-
{ - goYourLiquidityDetail(liquidity); - }} - className="flex items-center text-sm text-primaryText hover:text-white cursor-pointer" - > - - - - -
- {liquidity_status_string == 'unavailable' ? ( -
- {unavailableDiv()} -
- ) : null} -
+ -
- { - stakeNFT(liquidity); - }} - color="#fff" - disabled={ - nft_stake_loading || - liquidity_partialfarming_stakebuttonstatus_display == - 'disable' - ? true - : false - } - btnClassName={ - nft_stake_loading || - liquidity_partialfarming_stakebuttonstatus_display == - 'disable' - ? 'cursor-not-allowed' - : '' - } - minWidth="6rem" - className={`h-8 px-4 text-center text-sm text-white focus:outline-none ${ - nft_stake_loading || - liquidity_partialfarming_stakebuttonstatus_display == - 'disable' - ? 'opacity-40' - : '' - } ${showStakeButton ? '' : 'hidden'}`} - > - ( - - )} - /> - - -
- { - unStakeNFT(liquidity); - }} - color="#fff" - minWidth="6rem" - disabled={nft_unStake_loading ? true : false} - className={`flex items-center justify-center h-8 px-4 ml-2.5 text-center text-sm text-white focus:outline-none font-semibold bg-bgGreyDefault hover:bg-bgGreyHover ${ - nft_unStake_loading ? 'opacity-40' : '' - } ${showUnStakeButton ? '' : 'hidden'}`} - > - ( - - )} - /> - -
+ >
+ { + e.stopPropagation(); + claimReward(); + }} + > + } + /> +
-
- {/* for Mobile */} + ) : null} +
-
- - - NFT ID #{liquidity.lpt_id.split('#')[1]} - -
-
-
- - - - - {get_liquidity_value_display(liquidity)} - -
-
- - - - - {get_your_range(liquidity)} - -
-
-
- {apr_title()} - { - e.stopPropagation(); - setDclCalcVisible(true); - }} - className="text-farmText ml-1.5 cursor-pointer hover:text-greenColor" - /> -
- - {display_your_apr()} - -
-
- - - -
- {liquidity_status_display} -
-
-
-
-
-
- { - stakeNFT(liquidity); - }} - color="#fff" - disabled={ - nft_stake_loading || - liquidity_partialfarming_stakebuttonstatus_display == - 'disable' || - !showStakeButton - } - className={`h-10 px-4 text-center text-sm text-white focus:outline-none ${ - nft_stake_loading || - liquidity_partialfarming_stakebuttonstatus_display == - 'disable' || - !showStakeButton - ? 'opacity-40' - : '' - }`} + {unclaimedRewardsData.list.map( + ( + { + token, + amount, + preAmount, + }: { token: TokenMetadata; amount: string; preAmount: string }, + index: number + ) => { + return ( +
- ( - +
+ + + {toRealSymbol(token.symbol)} + +
+
+ {preAmount ? ( + <> + + {preAmount} + + + + + {amount} + + ) : ( + {amount} )} - /> - - -
- { - unStakeNFT(liquidity); - }} - color="#fff" - disabled={nft_unStake_loading ? true : false} - className={`flex items-center justify-center w-1 flex-grow h-10 text-center text-sm text-white focus:outline-none font-semibold bg-bgGreyDefault hover:bg-bgGreyHover ml-2.5 ${ - nft_unStake_loading ? 'opacity-40' : '' - } ${showUnStakeButton ? '' : 'hidden'}`} - > - ( - - )} - /> - -
- {liquidity_status_string == 'unavailable' ? ( -
- {unavailableDiv()} -
- ) : null} -
{ - goYourLiquidityDetail(liquidity); - }} - className="flex items-center text-sm text-primaryText hover:text-white cursor-pointer mt-4" - > - - - - -
-
+
+
+ ); + } + )}
- {dclCalcVisible ? ( - { - e.stopPropagation(); - setDclCalcVisible(false); - }} - seed={detailData} - liquidity={liquidity} - tokenPriceList={tokenPriceList} - style={{ - overlay: { - backdropFilter: 'blur(15px)', - WebkitBackdropFilter: 'blur(15px)', - }, - content: { - outline: 'none', - transform: 'translate(-50%, -50%)', - }, - }} - /> - ) : null} - +
); } function AddLoginEntryBar() { @@ -2559,4 +1862,3 @@ function AddLoginEntryBar() {
); } -type StatusType = 'farming' | 'unavailable' | 'unfarming' | 'partialfarming'; diff --git a/src/components/farm/FarmsDclDetailCopy.tsx b/src/components/farm/FarmsDclDetailCopy.tsx new file mode 100644 index 000000000..aa7f06efe --- /dev/null +++ b/src/components/farm/FarmsDclDetailCopy.tsx @@ -0,0 +1,2562 @@ +import React, { + useEffect, + useRef, + useState, + useContext, + useMemo, + createContext, +} from 'react'; +import { FormattedMessage, useIntl } from 'react-intl'; +import { isMobile } from '~utils/device'; +import { + ArrowLeftIcon, + UpArrowIcon, + BoostRightArrowIcon, + BoostOptIcon, + DclFarmIcon, + NFTIdIcon, + LinkArrowIcon, + NewTag, + CalcIcon, +} from 'src/components/icon/FarmBoost'; +import { RefreshIcon } from 'src/components/icon/swapV3'; +import { AddButtonIcon } from 'src/components/icon/V3'; +import { useHistory, useLocation } from 'react-router-dom'; +import getConfig from '../../services/config'; +import { LinkIcon, ArrowDownHollow } from 'src/components/icon'; +import { + FarmBoost, + Seed, + claimRewardBySeed_boost, + BoostConfig, + stake_boost_nft, + unStake_boost_nft, +} from '~services/farm'; +import { WalletContext } from '../../utils/wallets-integration'; +import { + toPrecision, + toReadableNumber, + toNonDivisibleNumber, + toInternationalCurrencySystem, + formatWithCommas, + calculateFairShare, +} from '../../utils/numbers'; +import BigNumber from 'bignumber.js'; +import { + GradientButton, + ButtonTextWrapper, + OprationButton, + ConnectToNearBtn, +} from 'src/components/button/Button'; +import { getMftTokenId, toRealSymbol } from 'src/utils/token'; +import ReactTooltip from 'react-tooltip'; +import QuestionMark from '~components/farm/QuestionMark'; +import { LOVE_TOKEN_DECIMAL } from '../../state/referendum'; +import { + getPriceByPoint, + UserLiquidityInfo, + get_total_value_by_liquidity_amount_dcl, + mint_liquidity, + get_valid_range, + allocation_rule_liquidities, + TOKEN_LIST_FOR_RATE, + get_matched_seeds_for_dcl_pool, + displayNumberToAppropriateDecimals, + get_intersection_radio, + get_intersection_icon_by_radio, + getEffectiveFarmList, + sort_tokens_by_base, + get_pool_name, + openUrl, +} from 'src/services/commonV3'; +import { list_liquidities, dcl_mft_balance_of } from '../../services/swapV3'; +import { AddNewPoolV3 } from '~components/pool/AddNewPoolV3'; +import { ftGetTokenMetadata, TokenMetadata } from '~services/ft-contract'; +import CalcModelDcl from '../../components/farm/CalcModelDcl'; +import moment from 'moment'; +const ONLY_ZEROS = /^0*\.?0*$/; +const { REF_VE_CONTRACT_ID, REF_UNI_V3_SWAP_CONTRACT_ID } = getConfig(); +const FarmContext = createContext(null); +export default function FarmsDclDetail(props: { + detailData: Seed; + emptyDetailData: Function; + tokenPriceList: any; + loveSeed: Seed; + boostConfig: BoostConfig; + user_data: Record; + user_data_loading: Boolean; + dayVolumeMap: Record; + all_seeds: Seed[]; +}) { + const { + detailData, + emptyDetailData, + tokenPriceList, + loveSeed, + boostConfig, + user_data, + user_data_loading, + dayVolumeMap, + all_seeds, + } = props; + const [listLiquidities, setListLiquidities] = useState( + [] + ); + const [listLiquidities_inFarimg, set_listLiquidities_inFarimg] = useState< + UserLiquidityInfo[] + >([]); + const [listLiquidities_unFarimg, set_listLiquidities_unFarimg] = useState< + UserLiquidityInfo[] + >([]); + const [listLiquidities_unavailable, set_listLiquidities_unavailable] = + useState([]); + const [listLiquiditiesLoading, setListLiquiditiesLoading] = useState(true); + const [rangeSort, setRangeSort] = useState(true); + const [addLiquidityModalVisible, setAddLiquidityModalVisible] = + useState(false); + const [mft_balance_in_dcl_account, set_mft_balance_in_dcl_account] = + useState('0'); + const [claimLoading, setClaimLoading] = useState(false); + const [showDetail, setShowDetail] = useState(false); + const [betterSeed, setBetterSeed] = useState(); + const [isNewSeed, setIsNewSeed] = useState(false); + const [seedDclCalcVisible, setSeedDclCalcVisible] = useState(false); + const s = localStorage.getItem('unavailable_positions'); + const [show_unavailable_positions, set_show_unavailable_positions] = useState( + s == '1' ? true : false + ); + const { + user_seeds_map = {}, + user_unclaimed_map = {}, + user_unclaimed_token_meta_map = {}, + } = user_data; + const history = useHistory(); + const intl = useIntl(); + const tokens = sortTokens(detailData.pool.tokens_meta_data); + const { globalState } = useContext(WalletContext); + const isSignedIn = globalState.isSignedIn; + const rate_need_to_reverse_display = useMemo(() => { + const { tokens_meta_data } = detailData.pool; + if (tokens_meta_data) { + const [tokenX] = tokens_meta_data; + if (TOKEN_LIST_FOR_RATE.indexOf(tokenX.symbol) > -1) return true; + return false; + } + }, [detailData]); + const isEnded = useMemo(() => { + if (detailData?.farmList) { + const farms = detailData.farmList; + return farms[0].status == 'Ended'; + } + return false; + }, [detailData]); + useEffect(() => { + get_farms_data(); + }, [all_seeds]); + useEffect(() => { + if (isSignedIn) { + get_list_liquidities(); + get_mft_balance_of(); + } + }, [isSignedIn, user_data_loading, all_seeds]); + useEffect(() => { + if (rate_need_to_reverse_display) { + setRangeSort(false); + } + }, [rate_need_to_reverse_display]); + const unclaimedRewardsData = useMemo(() => { + return getTotalUnclaimedRewards(); + }, [user_unclaimed_map[detailData.seed_id]]); + function getTotalUnclaimedRewards() { + let totalPrice = 0; + let resultTip = ''; + const tempFarms = {}; + + detailData.farmList.forEach((farm: FarmBoost) => { + tempFarms[farm.terms.reward_token] = true; + }); + const unclaimed = user_unclaimed_map[detailData.seed_id] || {}; + const unClaimedTokenIds = Object.keys(unclaimed); + const tokenList: any[] = []; + unClaimedTokenIds?.forEach((tokenId: string) => { + const token: TokenMetadata = user_unclaimed_token_meta_map[tokenId]; + // total price + const { id, decimals, icon } = token; + const amount = toReadableNumber(decimals, unclaimed[id] || '0'); + const tokenPrice = tokenPriceList[id]?.price; + if (tokenPrice && tokenPrice != 'N/A') { + totalPrice += +amount * tokenPrice; + } + // rewards number + let displayNum = ''; + if (new BigNumber('0').isEqualTo(amount)) { + displayNum = '-'; + } else if (new BigNumber('0.001').isGreaterThan(amount)) { + displayNum = '<0.001'; + } else { + displayNum = new BigNumber(amount).toFixed(3, 1); + } + // before boost number + let beforeNum = ''; + // if (radio) { // Reserved comment + // const v = new BigNumber(amount).dividedBy(radio); + // if (new BigNumber('0').isEqualTo(v)) { + // beforeNum = '-'; + // } else if (new BigNumber('0.001').isGreaterThan(v)) { + // beforeNum = '<0.001'; + // } else { + // beforeNum = new BigNumber(v).toFixed(3, 1); + // } + // } + const tempTokenData = { + token, + amount: displayNum, + preAmount: beforeNum, + }; + tokenList.push(tempTokenData); + const txt = intl.formatMessage({ id: 'ended_search' }); + const itemHtml = `
+ +
+ ${formatWithCommas(displayNum)} + ${ + !isEnded && !tempFarms[id] + ? `${txt}` + : '' + } +
+
`; + resultTip += itemHtml; + }); + if (totalPrice == 0) { + return { + worth: , + showClaimButton: false, + tip: resultTip, + list: tokenList, + }; + } else if (new BigNumber('0.01').isGreaterThan(totalPrice)) { + return { + worth: '<$0.01', + showClaimButton: true, + tip: resultTip, + list: tokenList, + }; + } else { + return { + worth: `$${toInternationalCurrencySystem(totalPrice.toString(), 2)}`, + showClaimButton: true, + tip: resultTip, + list: tokenList, + }; + } + } + async function get_farms_data() { + const matched_seeds = get_matched_seeds_for_dcl_pool({ + seeds: all_seeds, + pool_id: detailData.pool.pool_id, + sort: 'new', + }); + const targetSeed = matched_seeds[0]; + if (matched_seeds.length > 1 && targetSeed.seed_id != detailData.seed_id) { + setBetterSeed(targetSeed); + } + if (matched_seeds.length > 1 && targetSeed.seed_id == detailData.seed_id) { + setIsNewSeed(true); + } + } + async function get_mft_balance_of() { + const { seed_decimal, seed_id } = detailData; + const [contractId, temp_pool_id] = seed_id.split('@'); + const mft_id = `:${temp_pool_id}`; + const balance = await dcl_mft_balance_of(mft_id); + set_mft_balance_in_dcl_account(balance); + } + async function get_list_liquidities() { + const list: UserLiquidityInfo[] = await list_liquidities(); + if (list.length > 0 && !user_data_loading && all_seeds.length > 0) { + let temp_unavailable_final: UserLiquidityInfo[] = []; + let temp_free_final: UserLiquidityInfo[] = []; + let temp_farming_final: UserLiquidityInfo[] = []; + const { free_amount = '0', locked_amount = '0' } = + user_seeds_map[detailData.seed_id] || {}; + const user_seed_amount = new BigNumber(free_amount) + .plus(locked_amount) + .toFixed(); + const [temp_farming, temp_free, temp_unavailable] = + allocation_rule_liquidities({ + list, + user_seed_amount, + seed: detailData, + }); + temp_unavailable_final = temp_unavailable; + temp_free_final = temp_free; + temp_farming_final = temp_farming; + const liquidities_minted_in_another_seed = temp_unavailable.filter( + (liquidity: UserLiquidityInfo) => { + const { mft_id } = liquidity; + const { seed_id } = detailData; + if (mft_id) { + const [contractId, temp_pool_id] = seed_id.split('@'); + const [fixRange_s, pool_id_s, left_point_s, right_point_s] = + temp_pool_id.split('&'); + const [fixRange_l, pool_id_l, left_point_l, right_point_l] = + mft_id.split('&'); + return ( + left_point_s != left_point_l || right_point_s != right_point_l + ); + } + } + ); + if (liquidities_minted_in_another_seed.length > 0) { + const liquidity_another = liquidities_minted_in_another_seed[0]; + const { mft_id } = liquidity_another; + const list_new = JSON.parse(JSON.stringify(list)); + const seed_id_another = + REF_UNI_V3_SWAP_CONTRACT_ID + '@' + mft_id.slice(1); + const { free_amount = '0', locked_amount = '0' } = + user_seeds_map[seed_id_another] || {}; + const user_seed_amount_another = new BigNumber(free_amount) + .plus(locked_amount) + .toFixed(); + const seed_another = all_seeds.find((seed: Seed) => { + return seed.seed_id == seed_id_another; + }); + if (seed_another) { + const [ + temp_farming_another, + temp_free_another, + temp_unavailable_another, + ] = allocation_rule_liquidities({ + list: list_new, + user_seed_amount: user_seed_amount_another, + seed: seed_another, + }); + const temp_farming_another_map = {}; + temp_farming_another.forEach((liquidity: UserLiquidityInfo) => { + temp_farming_another_map[liquidity.lpt_id] = liquidity; + }); + const { min_deposit } = detailData; + liquidities_minted_in_another_seed.forEach( + (liquidity: UserLiquidityInfo) => { + const liquidity_another: UserLiquidityInfo = + temp_farming_another_map[liquidity.lpt_id]; + const v_liquidity = mint_liquidity(liquidity, detailData.seed_id); + if (+liquidity_another?.part_farm_ratio > 0) { + liquidity.status_in_other_seed = 'staked'; + } + if (new BigNumber(v_liquidity).isLessThan(min_deposit)) { + liquidity.less_than_min_deposit = true; + } + } + ); + } + const temp_unavailable_new: UserLiquidityInfo[] = []; + const frees_extra = temp_unavailable.filter( + (liquidity: UserLiquidityInfo) => { + const [left_point, right_point] = get_valid_range( + liquidity, + detailData.seed_id + ); + const inRange = right_point > left_point; + const { amount, mft_id } = liquidity; + const amount_is_little = new BigNumber(amount).isLessThan(1000000); + if ( + !( + liquidity.status_in_other_seed == 'staked' || + liquidity.less_than_min_deposit || + !inRange || + (!mft_id && amount_is_little) + ) + ) + return true; + temp_unavailable_new.push(liquidity); + } + ); + temp_free_final = temp_free.concat(frees_extra); + temp_unavailable_final = temp_unavailable_new; + } + const matched_liquidities = temp_farming_final + .concat(temp_free_final) + .concat(temp_unavailable_final); + set_listLiquidities_inFarimg(temp_farming_final); + set_listLiquidities_unFarimg(temp_free_final); + set_listLiquidities_unavailable(temp_unavailable_final); + setListLiquidities(matched_liquidities); + } + if (!user_data_loading) { + setListLiquiditiesLoading(false); + } + } + function sortTokens(tokens: TokenMetadata[]) { + tokens.sort((a: TokenMetadata, b: TokenMetadata) => { + if (a.symbol === 'NEAR') return 1; + if (b.symbol === 'NEAR') return -1; + return 0; + }); + return tokens; + } + const goBacktoFarms = () => { + history.replace('/v2farms'); + emptyDetailData(); + }; + const displaySymbols = () => { + const tokens_sort = sort_tokens_by_base(detailData.pool.tokens_meta_data); + let result = ''; + tokens_sort.forEach((token: TokenMetadata, index: number) => { + const symbol = toRealSymbol(token.symbol); + if (index == detailData.pool.tokens_meta_data.length - 1) { + result += symbol; + } else { + result += symbol + '-'; + } + }); + return result; + }; + const displayImgs = () => { + const tokenList: any[] = []; + const tokens_sort = sort_tokens_by_base(tokens || []); + tokens_sort.forEach((token: TokenMetadata) => { + tokenList.push( + + ); + }); + return tokenList; + }; + const goPoolPage = () => { + const poolId = detailData.pool.pool_id; + const params_str = get_pool_name(poolId); + openUrl(`/poolV2/${params_str}`); + }; + function getBoostMutil() { + if (REF_VE_CONTRACT_ID && !boostConfig) return ''; + const { affected_seeds = {} } = boostConfig || {}; + const { seed_id } = detailData; + const love_user_seed = user_seeds_map[REF_VE_CONTRACT_ID]; + const base = affected_seeds[seed_id]; + if (base && loveSeed) { + const { free_amount = 0, locked_amount = 0 } = love_user_seed || {}; + const totalStakeLoveAmount = toReadableNumber( + LOVE_TOKEN_DECIMAL, + new BigNumber(free_amount).plus(locked_amount).toFixed() + ); + if (+totalStakeLoveAmount > 0) { + let result; + if (+totalStakeLoveAmount < 1) { + result = 1; + } else { + result = new BigNumber(1) + .plus(Math.log(+totalStakeLoveAmount) / Math.log(base)) + .toFixed(2); + } + return result; + } + return ''; + } + return ''; + } + function getBoostDom() { + if (REF_VE_CONTRACT_ID && !boostConfig) return ''; + const { affected_seeds = {} } = boostConfig || {}; + const { seed_id } = detailData; + const base = affected_seeds[seed_id]; + if (base && loveSeed) { + const tip = intl.formatMessage({ id: 'boostFarmTip' }); + const result: string = `
${tip}
`; + return ( +
+
+
+ + +
+ +
+
+ ); + } + } + function getTotalApr() { + const farms = detailData.farmList; + let apr = 0; + const allPendingFarms = isPending(); + farms.forEach(function (item: FarmBoost) { + const pendingFarm = item.status == 'Created' || item.status == 'Pending'; + if (allPendingFarms || (!allPendingFarms && !pendingFarm)) { + apr = +new BigNumber(apr).plus(item.apr).toFixed(); + } + }); + return apr * 100; + } + function isPending() { + let pending: boolean = true; + const farms = detailData.farmList; + for (let i = 0; i < farms.length; i++) { + if (farms[i].status != 'Created' && farms[i].status != 'Pending') { + pending = false; + break; + } + } + return pending; + } + function getAprTip() { + // isYour?: Boolean todo 暂时去掉 + const tempList = detailData.farmList; + const lastList: any[] = []; + const pending_farms: FarmBoost[] = []; + const no_pending_farms: FarmBoost[] = []; + let totalApr; + const baseApr = getTotalApr(); + const txt = intl.formatMessage({ id: 'reward_apr' }); + tempList.forEach((farm: FarmBoost) => { + if (farm.status == 'Created') { + pending_farms.push(farm); + } else { + no_pending_farms.push(farm); + } + }); + if (pending_farms.length > 0) { + pending_farms.forEach((farm: FarmBoost) => { + lastList.push({ + rewardToken: farm.token_meta_data, + apr: new BigNumber(farm.apr || 0) + .multipliedBy(100) + .toFixed() + .toString(), + startTime: farm.terms.start_at, + pending: true, + }); + }); + } + if (no_pending_farms.length > 0) { + const mergedFarms = mergeCommonRewardsFarms( + JSON.parse(JSON.stringify(no_pending_farms)) + ); + mergedFarms.forEach((farm: FarmBoost) => { + lastList.push({ + rewardToken: farm.token_meta_data, + apr: new BigNumber(farm.apr || 0) + .multipliedBy(100) + .toFixed() + .toString(), + }); + }); + } + totalApr = baseApr; + // show last display string + let result: string = ''; + result = ` +
+ ${txt} + ${ + toPrecision(totalApr.toString(), 2) + '%' + } +
+ `; + lastList.forEach((item: any) => { + const { rewardToken, apr: baseApr, pending, startTime } = item; + const token = rewardToken; + let itemHtml = ''; + let apr = baseApr; + if (pending) { + const startDate = moment.unix(startTime).format('YYYY-MM-DD'); + const txt = intl.formatMessage({ id: 'start' }); + itemHtml = `
+ +
+ + + +
+
`; + } else { + itemHtml = `
+ + +
`; + } + result += itemHtml; + }); + return result; + } + function mergeCommonRewardsFarms(farms: FarmBoost[]) { + const tempMap = {}; + farms.forEach((farm: FarmBoost) => { + const { reward_token, daily_reward } = farm.terms; + let preMergedfarms: FarmBoost = tempMap[reward_token]; + if (preMergedfarms) { + preMergedfarms.apr = new BigNumber(preMergedfarms.apr || 0) + .plus(farm.apr) + .toFixed() + .toString(); + preMergedfarms.terms.daily_reward = new BigNumber( + preMergedfarms.terms.daily_reward + ) + .plus(daily_reward) + .toFixed(); + } else { + tempMap[reward_token] = farm; + } + }); + return Object.values(tempMap); + } + function getRange() { + const [contractId, temp_pool_id] = detailData.seed_id.split('@'); + const [fixRange, dcl_pool_id, left_point, right_point] = + temp_pool_id.split('&'); + const [token_x_metadata, token_y_metadata] = + detailData.pool.tokens_meta_data; + const decimalRate = + Math.pow(10, token_x_metadata.decimals) / + Math.pow(10, token_y_metadata.decimals); + let left_price = getPriceByPoint(+left_point, decimalRate); + let right_price = getPriceByPoint(+right_point, decimalRate); + if (!rangeSort) { + const temp = left_price; + left_price = new BigNumber(1).dividedBy(right_price).toFixed(); + right_price = new BigNumber(1).dividedBy(temp).toFixed(); + } + const display_left_price = left_price; + const display_right_price = right_price; + return ( +
+
+ { + setRangeSort(!rangeSort); + }} + > + + 1 {rangeSort ? token_x_metadata.symbol : token_y_metadata.symbol}= + +
+
+ + {displayNumberToAppropriateDecimals(display_left_price)} ~{' '} + {displayNumberToAppropriateDecimals(display_right_price)} + + + {rangeSort ? token_y_metadata.symbol : token_x_metadata.symbol} + +
+
+ ); + } + function valueOfRewardsTip() { + const tip = intl.formatMessage({ id: 'farmRewardsCopy' }); + let result: string = `
${tip}
`; + return result; + } + function getRewardsPerWeekTip() { + const tempList: FarmBoost[] = detailData.farmList; + const lastList: any[] = []; + const pending_farms: FarmBoost[] = []; + const no_pending_farms: FarmBoost[] = []; + tempList.forEach((farm: FarmBoost) => { + if (farm.status == 'Created') { + pending_farms.push(farm); + } else { + no_pending_farms.push(farm); + } + }); + if (pending_farms.length > 0) { + pending_farms.forEach((farm: FarmBoost) => { + const { decimals } = farm.token_meta_data; + const weekAmount = toReadableNumber( + decimals, + new BigNumber(farm.terms.daily_reward).multipliedBy(7).toFixed() + ); + lastList.push({ + commonRewardToken: farm.token_meta_data, + commonRewardTotalRewardsPerWeek: weekAmount, + startTime: farm.terms.start_at, + pending: true, + }); + }); + } + if (no_pending_farms.length > 0) { + const mergedFarms = mergeCommonRewardsFarms( + JSON.parse(JSON.stringify(no_pending_farms)) + ); + mergedFarms.forEach((farm: FarmBoost) => { + const { decimals } = farm.token_meta_data; + const weekAmount = toReadableNumber( + decimals, + new BigNumber(farm.terms.daily_reward).multipliedBy(7).toFixed() + ); + lastList.push({ + commonRewardToken: farm.token_meta_data, + commonRewardTotalRewardsPerWeek: weekAmount, + }); + }); + } + // show last display string + const rewards_week_txt = intl.formatMessage({ id: 'rewards_week' }); + let result: string = `
${rewards_week_txt}
`; + let itemHtml: string = ''; + lastList.forEach((item: any) => { + const { + commonRewardToken, + commonRewardTotalRewardsPerWeek, + pending, + startTime, + } = item; + const token = commonRewardToken; + const txt = intl.formatMessage({ id: 'start' }); + function display_number(value: string | number) { + if (!value) return value; + const [whole, decimals] = value.toString().split('.'); + const whole_format = formatWithCommas(whole); + if (+whole < 1 && decimals) { + return whole_format + '.' + decimals; + } else { + return whole_format; + } + } + if (pending) { + itemHtml = `
+
+ +
+ + +
`; + } else { + itemHtml = `
+ + +
`; + } + + result += itemHtml; + }); + return result; + } + function get_total_staked() { + if (+detailData.seedTvl == 0) { + return '-'; + } else { + return `$${toInternationalCurrencySystem(detailData.seedTvl, 2)}`; + } + } + function get_total_apr() { + let apr = getTotalApr(); + if (+apr == 0) { + return '-'; + } else { + return toPrecision(apr.toString(), 2) + '%'; + } + } + function totalTvlPerWeekDisplay() { + const farms = detailData.farmList; + const rewardTokenIconMap = {}; + let totalPrice = 0; + const effectiveFarms = getEffectiveFarmList(farms); + farms.forEach((farm: FarmBoost) => { + const { id, icon } = farm.token_meta_data; + rewardTokenIconMap[id] = icon; + }); + effectiveFarms.forEach((farm: FarmBoost) => { + const { id, decimals } = farm.token_meta_data; + const { daily_reward } = farm.terms; + const tokenPrice = tokenPriceList[id]?.price; + if (tokenPrice && tokenPrice != 'N/A') { + const tokenAmount = toReadableNumber(decimals, daily_reward); + totalPrice += +new BigNumber(tokenAmount) + .multipliedBy(tokenPrice) + .toFixed(); + } + }); + totalPrice = +new BigNumber(totalPrice).multipliedBy(7).toFixed(); + const totalPriceDisplay = + totalPrice == 0 + ? '-' + : '$' + toInternationalCurrencySystem(totalPrice.toString(), 2); + return ( + <> +
+
+ {Object.values(rewardTokenIconMap).map( + (icon: string, index: number) => { + return ( + + ); + } + )} +
+ +
+
+ {totalPriceDisplay} + +
+ + ); + } + function claimReward() { + if (claimLoading) return; + setClaimLoading(true); + claimRewardBySeed_boost(detailData.seed_id) + // .then(() => { + // window.location.reload(); + // }) + .catch((error) => { + setClaimLoading(false); + // setError(error); + }); + } + function rewardRangeTip() { + const tip = intl.formatMessage({ id: 'reward_range_tip' }); + let result: string = `
${tip}
`; + return result; + } + function switchDetailButton() { + setShowDetail(!showDetail); + } + function getBetterSeedSymbols() { + let result = ''; + const tokens = sort_tokens_by_base(detailData.pool.tokens_meta_data); + tokens.forEach((token: TokenMetadata, index: number) => { + const symbol = toRealSymbol(token.symbol); + if (index == detailData.pool.tokens_meta_data.length - 1) { + result += symbol; + } else { + result += symbol + '-'; + } + }); + return result; + } + function goBetterSeed() { + const [contractId, temp_pool_id] = betterSeed.seed_id.split('@'); + const [fixRange, pool_id, left_point, right_point] = + temp_pool_id.split('&'); + const mft_id = `${get_pool_name(pool_id)}[${left_point}-${right_point}]`; + openUrl(`/v2farms/${mft_id}-r`); + } + function getFee() { + const [tokenx, tokeny, fee] = detailData?.pool?.pool_id?.split('|') || ''; + return +fee / 10000 + '%'; + } + const radio = getBoostMutil(); + return ( +
+
+
+ + +
+
+ + +
+
+
+
+ {displayImgs()} +
+
+ + {displaySymbols()} + +
+ + + + + {getFee()} + +
+
+ + +
+ {isEnded ? ( + + + + ) : null} + {radio ? ( +
+ {`x${toPrecision(radio.toString(), 2)}`} +
+ ) : ( + getBoostDom() + )} + {isNewSeed ? ( + + ) : null} +
+
+ + + + + {getFee()} + +
+
+
+ +
+ + +
+
+ {/* new Farm is coming Banner */} + {betterSeed ? ( +
+
+ + {isEnded ? ( + + ) : ( + + )} + + + {getBetterSeedSymbols()} + + + ! + +
+ +
+ ) : null} + + {/* baseData for PC*/} +
+
+
+
+ + { + e.stopPropagation(); + setSeedDclCalcVisible(true); + }} + className="text-farmText ml-1.5 cursor-pointer hover:text-greenColor" + /> +
+
+ {get_total_apr()} + +
+
+
+
+ + + +
+ + +
+
+
+ { + setRangeSort(!rangeSort); + }} + > + {getRange()} +
+
+
+
+
+ +
+ + +
+
+
+ {totalTvlPerWeekDisplay()} +
+
+
+ {/* baseData for Mobile*/} +
+
+
+
+ + { + e.stopPropagation(); + setSeedDclCalcVisible(true); + }} + className="text-farmText ml-1.5 cursor-pointer hover:text-greenColor" + /> +
+ {get_total_apr()} +
+
+
+
+
+ + + +
+ + +
+
+ {getRange()} +
+
+
+
+ +
+ + +
+
+
+ {totalTvlPerWeekDisplay()} +
+
+
+ {/* login area */} + + {/* unClaimed Rewards for PC */} +
+
+ +
+ + +
+
+
+
+ + {unclaimedRewardsData.worth} + + +
+ { + if (!unclaimedRewardsData.showClaimButton) return; + e.stopPropagation(); + claimReward(); + }} + > + } + /> + +
+
+ {/* unClaimed Rewards for Mobile */} +
+
+
+ +
+ + +
+
+ +
+ + {unclaimedRewardsData.worth} + + +
+
+ {unclaimedRewardsData.showClaimButton ? ( +
+
+ + +
+ { + e.stopPropagation(); + claimReward(); + }} + > + } + /> + +
+ ) : null} + +
+ {unclaimedRewardsData.list.map( + ( + { + token, + amount, + preAmount, + }: { token: TokenMetadata; amount: string; preAmount: string }, + index: number + ) => { + return ( +
+
+ + + {toRealSymbol(token.symbol)} + +
+
+ {preAmount ? ( + <> + {preAmount} + + + + {amount} + + ) : ( + {amount} + )} +
+
+ ); + } + )} +
+
+ {/* Your Positions */} +
+
+
{ + setAddLiquidityModalVisible(true); + }} + className={`flex items-center text-sm text-primaryText p-1.5 cursor-pointer border border-v3HoverDarkBgColor hover:text-white hover:bg-dclButtonBgColor border-dashed rounded-lg ${ + listLiquidities.length > 0 ? '' : 'hidden' + }`} + > + + +
+
+ {listLiquidities.length == 0 && !listLiquiditiesLoading && !isEnded ? ( +
+
+
+ + + + { + setAddLiquidityModalVisible(true); + }} + color="#fff" + borderRadius="8px" + className={`flex-shrink-0 px-4 h-10 text-center text-sm text-white ml-2 xsm:w-full xsm:ml-0 xsm:mt-2.5`} + > + + +
+
+ ) : null} + + {listLiquidities_inFarimg.length > 0 ? ( + <> +
+ +
+ {listLiquidities_inFarimg.map((liquidity: UserLiquidityInfo) => { + return ( + + ); + })} + + ) : null} + + {listLiquidities_unFarimg.length > 0 ? ( + <> +
+ +
+ {listLiquidities_unFarimg.map((liquidity: UserLiquidityInfo) => { + return ( + + ); + })}{' '} + + ) : null} + + {listLiquidities_unavailable.length > 0 ? ( + <> +
{ + const current_status = !show_unavailable_positions; + localStorage.setItem( + 'unavailable_positions', + current_status ? '1' : '0' + ); + set_show_unavailable_positions(current_status); + }} + > + + {show_unavailable_positions ? ( + + ) : ( + + )} + + +
+
+ {listLiquidities_unavailable.map( + (liquidity: UserLiquidityInfo) => { + return ( + + ); + } + )} +
+ + ) : null} +
+
+ {/* add liquidity modal */} + { + setAddLiquidityModalVisible(false); + }} + seed={detailData} + tokenPriceList={tokenPriceList} + style={{ + overlay: { + backdropFilter: 'blur(15px)', + WebkitBackdropFilter: 'blur(15px)', + }, + content: { + outline: 'none', + transform: 'translate(-50%, -50%)', + }, + }} + > + {/* caculator */} + {seedDclCalcVisible ? ( + { + e.stopPropagation(); + setSeedDclCalcVisible(false); + }} + seed={detailData} + tokenPriceList={tokenPriceList} + style={{ + overlay: { + backdropFilter: 'blur(15px)', + WebkitBackdropFilter: 'blur(15px)', + }, + content: { + outline: 'none', + transform: 'translate(-50%, -50%)', + }, + }} + /> + ) : null} +
+ ); +} + +function LiquidityLine(props: { + liquidity: UserLiquidityInfo; + status: StatusType; +}) { + const { liquidity, status } = props; + const { + detailData, + tokenPriceList, + tokens, + mft_balance_in_dcl_account, + rate_need_to_reverse_display, + all_seeds, + isPending, + isEnded, + rangeSort, + } = useContext(FarmContext); + const [nft_stake_loading, set_nft_stake_loading] = useState(false); + const [nft_unStake_loading, set_nft_unStake_loading] = useState(false); + const [hover, setHover] = useState(false); + const [dclCalcVisible, setDclCalcVisible] = useState(false); + const liquidity_status_string: StatusType | string = useMemo(() => { + if (!(liquidity && detailData)) return ''; + const { unfarm_part_amount } = liquidity; + let farmStatus: StatusType = status; + if (status == 'farming' && +unfarm_part_amount > 0) { + farmStatus = 'partialfarming'; + } + return farmStatus; + }, [liquidity, detailData]); + const [ + liquidity_status_display, + liquidity_operation_display, + liquidity_partialfarming_stakebuttonstatus_display, + ]: any = useMemo(() => { + if (!(liquidity && detailData && liquidity_status_string)) + return [null, [], '']; + const part_farm_ratio = liquidity.part_farm_ratio; + let status; + let operation: string[] = []; + let stakeButtonStatus = ''; + if (liquidity_status_string == 'farming') { + status = ( + + + + ); + operation = ['unstake']; + } else if (liquidity_status_string == 'unavailable') { + status = ( + + + + ); + } else if (liquidity_status_string == 'unfarming') { + status = ( + + + + ); + operation = ['stake']; + } else { + const part_farm_ratio_big = new BigNumber(part_farm_ratio); + let percent = '-%'; + if (part_farm_ratio_big.isLessThan(1)) { + percent = '<1%'; + } else { + percent = `${part_farm_ratio_big.toFixed(0, 1)}%`; + } + status = ( + + {percent} Partial Farming + + ); + operation = ['stake', 'unstake']; + if ( + new BigNumber(liquidity.v_liquidity).isLessThan(detailData.min_deposit) + ) { + stakeButtonStatus = 'disable'; + } else { + stakeButtonStatus = 'enable'; + } + } + return [status, operation, stakeButtonStatus]; + }, [liquidity, detailData, liquidity_status_string]); + useEffect(() => { + get_each_token_apr_for_nft(); + }, [detailData, tokenPriceList, liquidity]); + const intl = useIntl(); + + function get_liquidity_value(liquidity: UserLiquidityInfo) { + const { left_point, right_point, amount } = liquidity; + const poolDetail = detailData.pool; + const { token_x, token_y } = poolDetail; + const v = get_total_value_by_liquidity_amount_dcl({ + left_point, + right_point, + poolDetail, + amount, + price_x_y: { + [token_x]: tokenPriceList[token_x]?.price || '0', + [token_y]: tokenPriceList[token_y]?.price || '0', + }, + metadata_x_y: { + [token_x]: tokens[0], + [token_y]: tokens[1], + }, + }); + return v; + } + function get_liquidity_value_display(liquidity: UserLiquidityInfo) { + const v = get_liquidity_value(liquidity); + const v_big = new BigNumber(v); + if (v_big.isLessThan(0.01)) { + return `<$0.01`; + } else { + return `$${formatWithCommas(toPrecision(v.toString(), 2))}`; + } + } + function get_your_range(liquidity: UserLiquidityInfo) { + const { left_point, right_point } = liquidity; + const [token_x_metadata, token_y_metadata] = + detailData.pool.tokens_meta_data; + const [fixRange, dcl_pool_id, seed_left_point, seed_right_point] = + detailData.seed_id.split('@')[1].split('&'); + let icon; + const radio = get_intersection_radio({ + left_point_liquidity: left_point, + right_point_liquidity: right_point, + left_point_seed: seed_left_point, + right_point_seed: seed_right_point, + }); + const p = new BigNumber(radio); + const Icon = get_intersection_icon_by_radio(radio); + icon = ; + const decimalRate = + Math.pow(10, token_x_metadata.decimals) / + Math.pow(10, token_y_metadata.decimals); + let left_price = getPriceByPoint(+left_point, decimalRate); + let right_price = getPriceByPoint(+right_point, decimalRate); + if (!rangeSort) { + const temp = left_price; + left_price = new BigNumber(1).dividedBy(right_price).toFixed(); + right_price = new BigNumber(1).dividedBy(temp).toFixed(); + } + const display_left_price = left_price; + const display_right_price = right_price; + function rangeTip() { + // const tip = intl.formatMessage({ id: 'farmRewardsCopy' }); + const tip = + 'The intersection of your price range and the farm reward range.'; + let result: string = `
${tip}
`; + return result; + } + function displayP(p: BigNumber) { + if (p.isEqualTo(0)) { + return '0%'; + } else if (p.isLessThan(1)) { + return '1%'; + } else { + return p?.toFixed(0, 1) + '%'; + } + } + return ( +
+ + {displayNumberToAppropriateDecimals(display_left_price)} ~{' '} + {displayNumberToAppropriateDecimals(display_right_price)} + +
+ + {icon} + + + +
+
+ ); + } + function get_each_token_apr_for_nft() { + const { farmList, total_seed_amount, total_seed_power } = detailData; + // principal + const total_principal = get_liquidity_value(liquidity); + // lp percent + let percent: BigNumber = new BigNumber(0); + const mint_amount = mint_liquidity(liquidity, detailData.seed_id); + const part_farm_ratio = liquidity.part_farm_ratio; + if (+part_farm_ratio == 100) { + // full farming + if (+total_seed_power > 0) { + percent = new BigNumber(mint_amount).dividedBy(total_seed_power); + } + } else if (+part_farm_ratio > 0 && +part_farm_ratio < 100) { + // partial Farming + if (+total_seed_power > 0) { + const partial_amount = new BigNumber(mint_amount) + .multipliedBy(part_farm_ratio) + .dividedBy(100); + percent = partial_amount.dividedBy(total_seed_power); + } + } else { + // unFarming, unavailable + const temp_total = new BigNumber(total_seed_power || 0).plus(mint_amount); + if (temp_total.isGreaterThan(0)) { + percent = new BigNumber(mint_amount).dividedBy(temp_total); + } + } + // get apr per token + const farmList_parse = JSON.parse(JSON.stringify(farmList || [])); + farmList_parse.forEach((farm: FarmBoost) => { + const { token_meta_data } = farm; + const { daily_reward, reward_token } = farm.terms; + const quantity = toReadableNumber(token_meta_data.decimals, daily_reward); + const reward_token_price = Number( + tokenPriceList[reward_token]?.price || 0 + ); + const cur_token_rewards = new BigNumber(quantity) + .multipliedBy(reward_token_price) + .multipliedBy(365); + const user_can_get_token_rewards = new BigNumber( + cur_token_rewards + ).multipliedBy(percent); + if (+total_principal > 0) { + farm.yourNFTApr = new BigNumber(user_can_get_token_rewards) + .dividedBy(total_principal) + .toFixed(); + } else { + farm.yourNFTApr = '0'; + } + }); + liquidity.farmList = farmList_parse; + } + function get_your_total_apr() { + const farms = liquidity.farmList || []; + let apr = new BigNumber(0); + const allPendingFarms = isPending(); + farms.forEach(function (item: FarmBoost) { + const pendingFarm = item.status == 'Created' || item.status == 'Pending'; + if (allPendingFarms || !pendingFarm) { + apr = new BigNumber(apr).plus(item.yourNFTApr || 0); + } + }); + return apr.multipliedBy(100).toFixed(); + } + function display_your_apr() { + const total_apr = new BigNumber(get_your_total_apr()); + if (total_apr.isEqualTo('0')) { + return '0%'; + } else if (total_apr.isLessThan(0.01)) { + return `<0.01%`; + } else { + return `${toPrecision(total_apr.toFixed(), 2)}%`; + } + } + function get_your_apr_copy(liquidity: UserLiquidityInfo) { + const { farmList, total_seed_amount, total_seed_power } = detailData; + // principal + const total_principal = get_liquidity_value(liquidity); + // seed total rewards + let total_rewards = '0'; + farmList.forEach((farm: FarmBoost) => { + const { token_meta_data } = farm; + const { daily_reward, reward_token } = farm.terms; + const quantity = toReadableNumber(token_meta_data.decimals, daily_reward); + const reward_token_price = Number( + tokenPriceList[reward_token]?.price || 0 + ); + const cur_token_rewards = new BigNumber(quantity) + .multipliedBy(reward_token_price) + .multipliedBy(365); + total_rewards = cur_token_rewards.plus(total_rewards).toFixed(); + }); + // lp percent + let percent: BigNumber = new BigNumber(0); + const mint_amount = mint_liquidity(liquidity, detailData.seed_id); + const part_farm_ratio = liquidity.part_farm_ratio; + if (+part_farm_ratio == 100) { + // full farming + if (+total_seed_power > 0) { + percent = new BigNumber(mint_amount).dividedBy(total_seed_power); + } + } else if (+part_farm_ratio > 0 && +part_farm_ratio < 100) { + // partial Farming + if (+total_seed_power > 0) { + const partial_amount = new BigNumber(mint_amount) + .multipliedBy(part_farm_ratio) + .dividedBy(100); + percent = partial_amount.dividedBy(total_seed_power); + } + } else { + // unFarming, unavailable + const temp_total = new BigNumber(total_seed_power || 0).plus(mint_amount); + if (temp_total.isGreaterThan(0)) { + percent = new BigNumber(mint_amount).dividedBy(temp_total); + } + } + // profit + let profit; + if (percent) { + profit = percent.multipliedBy(total_rewards); + } + // your apr + if (profit) { + if (+total_principal == 0) { + return 0; + } + const your_apr = profit.dividedBy(total_principal).multipliedBy(100); + if (your_apr.isEqualTo('0')) { + return '0%'; + } else if (your_apr.isLessThan(0.01)) { + return `<0.01%`; + } else { + return `${toPrecision(your_apr.toFixed(), 2)}%`; + } + } else { + return '-'; + } + } + function getYourLiquidityAprTip() { + const { farmList } = liquidity; + if (!farmList) return ''; + const tempList = farmList; + const lastList: any[] = []; + const pending_farms: FarmBoost[] = []; + const no_pending_farms: FarmBoost[] = []; + const totalApr = get_your_total_apr(); + const txt = intl.formatMessage({ id: 'reward_apr' }); + tempList.forEach((farm: FarmBoost) => { + if (farm.status == 'Created') { + pending_farms.push(farm); + } else { + no_pending_farms.push(farm); + } + }); + if (pending_farms.length > 0) { + pending_farms.forEach((farm: FarmBoost) => { + lastList.push({ + rewardToken: farm.token_meta_data, + apr: new BigNumber(farm.yourNFTApr || 0) + .multipliedBy(100) + .toFixed() + .toString(), + startTime: farm.terms.start_at, + pending: true, + }); + }); + } + if (no_pending_farms.length > 0) { + const mergedFarms = mergeCommonRewardsFarms( + JSON.parse(JSON.stringify(no_pending_farms)) + ); + mergedFarms.forEach((farm: FarmBoost) => { + lastList.push({ + rewardToken: farm.token_meta_data, + apr: new BigNumber(farm.yourNFTApr || 0) + .multipliedBy(100) + .toFixed() + .toString(), + }); + }); + } + // show last display string + let result: string = ''; + result = ` +
+ ${txt} + ${ + toPrecision(totalApr.toString(), 2) + '%' + } +
+ `; + lastList.forEach((item: any) => { + const { rewardToken, apr, pending, startTime } = item; + const token = rewardToken; + let itemHtml = ''; + if (pending) { + const startDate = moment.unix(startTime).format('YYYY-MM-DD'); + const txt = intl.formatMessage({ id: 'start' }); + itemHtml = `
+ +
+ + + +
+
`; + } else { + itemHtml = `
+ + +
`; + } + result += itemHtml; + }); + return result; + } + function mergeCommonRewardsFarms(farms: FarmBoost[]) { + const tempMap = {}; + farms.forEach((farm: FarmBoost) => { + const { reward_token, daily_reward } = farm.terms; + let preMergedfarms: FarmBoost = tempMap[reward_token]; + if (preMergedfarms) { + preMergedfarms.yourNFTApr = new BigNumber( + preMergedfarms.yourNFTApr || 0 + ) + .plus(farm.yourNFTApr) + .toFixed() + .toString(); + preMergedfarms.terms.daily_reward = new BigNumber( + preMergedfarms.terms.daily_reward + ) + .plus(daily_reward) + .toFixed(); + } else { + tempMap[reward_token] = farm; + } + }); + return Object.values(tempMap); + } + function stakeNFT(liquidity: UserLiquidityInfo) { + set_nft_stake_loading(true); + let finalAmount; + const { seed_id, min_deposit } = detailData; + const { mft_id, part_farm_ratio, unfarm_part_amount } = liquidity; + const v_liquidity = mint_liquidity(liquidity, seed_id); + let needUnstake = false; + let withdraw_amount = '0'; + if (!mft_id || new BigNumber(part_farm_ratio || 0).isEqualTo(0)) { + finalAmount = v_liquidity; + } else { + if (new BigNumber(unfarm_part_amount).isLessThan(min_deposit)) { + needUnstake = true; + withdraw_amount = new BigNumber(v_liquidity) + .minus(unfarm_part_amount) + .toFixed(); + } else { + finalAmount = unfarm_part_amount; + } + } + stake_boost_nft({ + liquidity, + seed_id, + stakeAmount: finalAmount, + withdraw_amount, + needUnstake, + }); + } + function unStakeNFT(liquidity: UserLiquidityInfo) { + set_nft_unStake_loading(true); + const amount = liquidity.v_liquidity; + const mft_balance_big = new BigNumber(mft_balance_in_dcl_account); + const amount_big = new BigNumber(amount); + let finalAmount = '0'; + if (mft_balance_big.isEqualTo('0')) { + finalAmount = amount; + } else if (mft_balance_big.isGreaterThanOrEqualTo(amount)) { + finalAmount = '0'; + } else { + finalAmount = amount_big.minus(mft_balance_in_dcl_account).toFixed(); + } + unStake_boost_nft({ + lpt_id: liquidity.lpt_id, + seed_id: detailData.seed_id, + withdraw_amount: finalAmount, + }); + } + function goYourLiquidityDetail(liquidity: UserLiquidityInfo) { + liquidity.lpt_id.split('#')[0]; + const pool_id = liquidity.lpt_id.split('#')[0]; + const lpt_index = liquidity.lpt_id.split('#')[1]; + openUrl(`/yoursLiquidityDetailV2/${get_pool_name(pool_id)}@${lpt_index}`); + } + function unavailableTip() { + const tip = unavailableText(); + const result: string = `
${tip}
`; + return result; + } + function unavailableText() { + let tip = ''; + const { seed_id, min_deposit } = detailData; + const [left_point, right_point] = get_valid_range(liquidity, seed_id); + const inrange = +right_point > +left_point; + if (!inrange) { + tip = intl.formatMessage({ id: 'your_price_range_tip' }); + } else if (liquidity.status_in_other_seed == 'staked') { + tip = intl.formatMessage({ id: 'position_has_staked_tip' }); + } else { + const v_liquidity = mint_liquidity(liquidity, seed_id); + const rate = new BigNumber(min_deposit).dividedBy(v_liquidity); + let rate_display = rate.toFixed(1); + if (rate.isGreaterThan(rate_display)) { + rate_display = new BigNumber(rate_display).plus(0.1).toFixed(); + } + // your liquidity + tip = + intl.formatMessage({ id: 'minimum_tip' }) + + ' ' + + `${rate_display}x` + + ' ' + + intl.formatMessage({ id: 'your_liquidity_3' }); + } + return tip; + } + function unavailableDiv() { + let tip; + const { seed_id, min_deposit } = detailData; + const [left_point, right_point] = get_valid_range(liquidity, seed_id); + const inrange = +right_point > +left_point; + if (!inrange) { + tip = intl.formatMessage({ id: 'your_price_range_tip' }); + } else if (liquidity.status_in_other_seed == 'staked') { + const { mft_id } = liquidity; + const link: string = get_target_seed_url_link({ + all_seeds, + mft_id, + }); + tip = ( + <> + {' '} +
{ + openUrl(link); + }} + > + + + + +
+ + ); + } else { + const v_liquidity = mint_liquidity(liquidity, seed_id); + const rate = new BigNumber(min_deposit).dividedBy(v_liquidity); + let rate_display = rate.toFixed(1); + if (rate.isGreaterThan(rate_display)) { + rate_display = new BigNumber(rate_display).plus(0.1).toFixed(); + } + tip = + intl.formatMessage({ id: 'minimum_tip' }) + + ' ' + + `${rate_display}x` + + ' ' + + intl.formatMessage({ id: 'your_liquidity_3' }); + } + return tip; + } + function get_target_seed_url_link({ + all_seeds, + mft_id, + }: { + all_seeds: Seed[]; + mft_id: string; + }) { + const seed_id = REF_UNI_V3_SWAP_CONTRACT_ID + '@' + mft_id.slice(1); + const temps = all_seeds.filter((seed: Seed) => { + return seed.seed_id == seed_id; + }); + temps.sort((b: Seed, a: Seed) => { + if (b.farmList[0].status == 'Ended') return 1; + if (a.farmList[0].status == 'Ended') return -1; + return 0; + }); + const target = temps[0]; + const [fixRange, pool_id, left_point, right_point] = mft_id.split('&'); + const status = target?.farmList[0].status == 'Ended' ? 'e' : 'r'; + const params = `${get_pool_name(pool_id)}[${left_point}-${right_point}]`; + const link = `/v2farms/${params}-${status}`; + return link; + } + function apr_title() { + if ( + liquidity_status_string == 'farming' || + liquidity_status_string == 'partialfarming' + ) { + return ; + } else { + return ; + } + } + const showStakeButton = + !isEnded && liquidity_operation_display.indexOf('stake') > -1; + const showUnStakeButton = liquidity_operation_display.indexOf('unstake') > -1; + return ( + <> + {/* for PC */} +
{ + setHover(false); + }} + > +
+ + + NFT ID #{liquidity.lpt_id.split('#')[1]} + +
+
+
setHover(true)} + className="flex items-stretch justify-between pt-7 pb-3.5 px-6" + > +
+ + + + + {get_liquidity_value_display(liquidity)} + +
+
+ + + + + {get_your_range(liquidity)} + +
+
+
+ {apr_title()} + { + e.stopPropagation(); + setDclCalcVisible(true); + }} + className="text-farmText ml-1.5 cursor-pointer hover:text-greenColor" + /> +
+
+ + {display_your_apr()} + + +
+
+
+ + + +
+ {liquidity_status_display} +
+
+
+
+
{ + goYourLiquidityDetail(liquidity); + }} + className="flex items-center text-sm text-primaryText hover:text-white cursor-pointer" + > + + + + +
+ {liquidity_status_string == 'unavailable' ? ( +
+ {unavailableDiv()} +
+ ) : null} +
+
+ { + stakeNFT(liquidity); + }} + color="#fff" + disabled={ + nft_stake_loading || + liquidity_partialfarming_stakebuttonstatus_display == + 'disable' + ? true + : false + } + btnClassName={ + nft_stake_loading || + liquidity_partialfarming_stakebuttonstatus_display == + 'disable' + ? 'cursor-not-allowed' + : '' + } + minWidth="6rem" + className={`h-8 px-4 text-center text-sm text-white focus:outline-none ${ + nft_stake_loading || + liquidity_partialfarming_stakebuttonstatus_display == + 'disable' + ? 'opacity-40' + : '' + } ${showStakeButton ? '' : 'hidden'}`} + > + ( + + )} + /> + + +
+ { + unStakeNFT(liquidity); + }} + color="#fff" + minWidth="6rem" + disabled={nft_unStake_loading ? true : false} + className={`flex items-center justify-center h-8 px-4 ml-2.5 text-center text-sm text-white focus:outline-none font-semibold bg-bgGreyDefault hover:bg-bgGreyHover ${ + nft_unStake_loading ? 'opacity-40' : '' + } ${showUnStakeButton ? '' : 'hidden'}`} + > + ( + + )} + /> + +
+
+
+
+ {/* for Mobile */} +
+
+ + + NFT ID #{liquidity.lpt_id.split('#')[1]} + +
+
+
+ + + + + {get_liquidity_value_display(liquidity)} + +
+
+ + + + + {get_your_range(liquidity)} + +
+
+
+ {apr_title()} + { + e.stopPropagation(); + setDclCalcVisible(true); + }} + className="text-farmText ml-1.5 cursor-pointer hover:text-greenColor" + /> +
+ + {display_your_apr()} + +
+
+ + + +
+ {liquidity_status_display} +
+
+
+
+
+
+ { + stakeNFT(liquidity); + }} + color="#fff" + disabled={ + nft_stake_loading || + liquidity_partialfarming_stakebuttonstatus_display == + 'disable' || + !showStakeButton + } + className={`h-10 px-4 text-center text-sm text-white focus:outline-none ${ + nft_stake_loading || + liquidity_partialfarming_stakebuttonstatus_display == + 'disable' || + !showStakeButton + ? 'opacity-40' + : '' + }`} + > + ( + + )} + /> + + +
+ { + unStakeNFT(liquidity); + }} + color="#fff" + disabled={nft_unStake_loading ? true : false} + className={`flex items-center justify-center w-1 flex-grow h-10 text-center text-sm text-white focus:outline-none font-semibold bg-bgGreyDefault hover:bg-bgGreyHover ml-2.5 ${ + nft_unStake_loading ? 'opacity-40' : '' + } ${showUnStakeButton ? '' : 'hidden'}`} + > + ( + + )} + /> + +
+ {liquidity_status_string == 'unavailable' ? ( +
+ {unavailableDiv()} +
+ ) : null} +
{ + goYourLiquidityDetail(liquidity); + }} + className="flex items-center text-sm text-primaryText hover:text-white cursor-pointer mt-4" + > + + + + +
+
+
+ {dclCalcVisible ? ( + { + e.stopPropagation(); + setDclCalcVisible(false); + }} + seed={detailData} + liquidity={liquidity} + tokenPriceList={tokenPriceList} + style={{ + overlay: { + backdropFilter: 'blur(15px)', + WebkitBackdropFilter: 'blur(15px)', + }, + content: { + outline: 'none', + transform: 'translate(-50%, -50%)', + }, + }} + /> + ) : null} + + ); +} +function AddLoginEntryBar() { + const { globalState } = useContext(WalletContext); + const isSignedIn = globalState.isSignedIn; + if (isSignedIn) return null; + return ( +
+
+
+ +
+
+ ); +} +type StatusType = 'farming' | 'unavailable' | 'unfarming' | 'partialfarming'; diff --git a/src/components/farm/FarmsHome.tsx b/src/components/farm/FarmsHome.tsx index b914c87d9..f8cb213b6 100644 --- a/src/components/farm/FarmsHome.tsx +++ b/src/components/farm/FarmsHome.tsx @@ -655,7 +655,7 @@ export default function FarmsHome(props: any) { const pool_id = get_pool_id(layer2[0]); const point_str = layer2[1].substring(0, layer2[1].length - 3); const status = layer2[1].substring(layer2[1].length - 1); - const p_arr = point_str.split('-'); + const p_arr = split_point_string(point_str); const [lp, rp] = p_arr; return `${pool_id}&${lp}&${rp}-${status}`; } else { @@ -669,6 +669,17 @@ export default function FarmsHome(props: any) { return ''; } + function split_point_string(str: string) { + const arr = Array.from(str); + let index; + for (let i = 0; i < arr.length; i++) { + if (str[i] == '-' && i !== 0) { + index = i; + break; + } + } + return [str.slice(0, index), str.slice(index + 1)]; + } async function get_user_unWithDraw_rewards() { if (isSignedIn) { const userRewardList = await get_unWithDraw_rewards(); @@ -3496,12 +3507,14 @@ function WithDrawBox(props: { const [key, v] = arr; const singlePrice = tokenPriceList[key]?.price; const token = rewardTokenList[key]; - const number: any = toReadableNumber(token.decimals, v); - if (singlePrice && singlePrice != 'N/A') { - totalUnWithDraw = BigNumber.sum( - singlePrice * number, - totalUnWithDraw - ).toNumber(); + if (token) { + const number: any = toReadableNumber(token.decimals, v); + if (singlePrice && singlePrice != 'N/A') { + totalUnWithDraw = BigNumber.sum( + singlePrice * number, + totalUnWithDraw + ).toNumber(); + } } }); if (totalUnWithDraw > 0) { @@ -3751,12 +3764,14 @@ function WithDrawb(props: { const [key, v] = arr; const singlePrice = tokenPriceList[key]?.price; const token = rewardTokenList[key]; - const number: any = toReadableNumber(token.decimals, v); - if (singlePrice && singlePrice != 'N/A') { - totalUnWithDraw = BigNumber.sum( - singlePrice * number, - totalUnWithDraw - ).toNumber(); + if (token) { + const number: any = toReadableNumber(token.decimals, v); + if (singlePrice && singlePrice != 'N/A') { + totalUnWithDraw = BigNumber.sum( + singlePrice * number, + totalUnWithDraw + ).toNumber(); + } } }); if (totalUnWithDraw > 0) { @@ -4087,12 +4102,14 @@ function WithDrawModal(props: { const [key, v] = arr; const singlePrice = tokenPriceList[key]?.price; const token = rewardTokenList[key]; - const number: any = toReadableNumber(token.decimals, v); - if (singlePrice && singlePrice != 'N/A') { - totalUnWithDraw = BigNumber.sum( - singlePrice * number, - totalUnWithDraw - ).toNumber(); + if (token) { + const number: any = toReadableNumber(token.decimals, v); + if (singlePrice && singlePrice != 'N/A') { + totalUnWithDraw = BigNumber.sum( + singlePrice * number, + totalUnWithDraw + ).toNumber(); + } } }); if (totalUnWithDraw > 0) { diff --git a/src/components/farm/utils.ts b/src/components/farm/utils.ts new file mode 100644 index 000000000..c621d0808 --- /dev/null +++ b/src/components/farm/utils.ts @@ -0,0 +1,60 @@ +import Big from 'big.js'; +import { + formatWithCommas, + toInternationalCurrencySystem, +} from '../../utils/numbers'; +export const formatWithCommas_usd = (v: string | number) => { + if (isInvalid(v)) return '$-'; + const big = Big(v); + if (big.eq(0)) { + return '$0'; + } else if (big.lt(0.01)) { + return '<$0.01'; + } else if (big.lt(10000)) { + return '$' + formatWithCommas(big.toFixed(2, 1)); + } else { + return '$' + formatWithCommas(big.toFixed(0, 1)); + } +}; + +export const formatPercentage = (v: string | number) => { + if (isInvalid(v)) return '-%'; + const big = Big(v); + if (big.eq(0)) { + return '0%'; + } else if (big.lt(1)) { + return '<1%'; + } else { + return big.toFixed(0, 1) + '%'; + } +}; +export const formatNumber = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.01'; + } else { + return big.toFixed(2, 1); + } +}; +export const formatPrice = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.0001'; + } else { + return big.toFixed(4, 1); + } +}; +export const formatToInternationalCurrencySystem$ = (v: string | number) => { + if (isInvalid(v)) return '$-'; + return '$' + toInternationalCurrencySystem(Big(v || 0).toFixed(), 2); +}; +export const isInvalid = function (v: any) { + if (v === '' || v === undefined || v === null) return true; + return false; +}; diff --git a/src/components/forms/SelectToken.tsx b/src/components/forms/SelectToken.tsx index 5c5a2374a..a636fe9bb 100644 --- a/src/components/forms/SelectToken.tsx +++ b/src/components/forms/SelectToken.tsx @@ -767,6 +767,7 @@ export function SelectTokenDCL({ onSelect, selected, className, + notNeedSortToken, }: { selectTokenIn?: (token: TokenMetadata) => void; selectTokenOut?: (token: TokenMetadata) => void; @@ -774,6 +775,7 @@ export function SelectTokenDCL({ selectedToken?: TokenMetadata; selected?: JSX.Element; className?: string; + notNeedSortToken?: boolean; }) { const allPools = useAllPoolsV2(); @@ -796,7 +798,9 @@ export function SelectTokenDCL({ const handleSelect = (p: PoolInfo) => { // select token in const { token_x_metadata, token_y_metadata } = p; - const tokens = sort_tokens_by_base([token_x_metadata, token_y_metadata]); + const tokens = notNeedSortToken + ? [token_x_metadata, token_y_metadata] + : sort_tokens_by_base([token_x_metadata, token_y_metadata]); if (!selectedToken) { selectTokenIn(tokens[0]); diff --git a/src/components/forms/SlippageSelector.tsx b/src/components/forms/SlippageSelector.tsx index efe8b31f4..0337e30c4 100644 --- a/src/components/forms/SlippageSelector.tsx +++ b/src/components/forms/SlippageSelector.tsx @@ -473,9 +473,11 @@ export function PoolSlippageSelector({ export function PoolSlippageSelectorV3({ slippageTolerance, onChange, + textColor, }: { slippageTolerance: number; onChange: (slippage: number) => void; + textColor?: string; }) { const validSlippages = [0.1, 0.5, 1.0]; const intl = useIntl(); @@ -485,7 +487,9 @@ export function PoolSlippageSelectorV3({ <>
-
+
{ + onSortChange('top_bin_apr'); + setShowModal(false); + }} + > + Top Bin APR (24h) +
); }; diff --git a/src/components/pool/AddNewPoolV3.tsx b/src/components/pool/AddNewPoolV3.tsx index 66128cfd9..f302bb25b 100644 --- a/src/components/pool/AddNewPoolV3.tsx +++ b/src/components/pool/AddNewPoolV3.tsx @@ -38,7 +38,6 @@ import { DEFAULTSELECTEDFEE, POINTLEFTRANGE, POINTRIGHTRANGE, - useAddAndRemoveUrlHandle, drawChartData, TOKEN_LIST_FOR_RATE, displayNumberToAppropriateDecimals, diff --git a/src/components/pool/PoolTabV3.tsx b/src/components/pool/PoolTabV3.tsx index 25da37a7b..961f80c1b 100644 --- a/src/components/pool/PoolTabV3.tsx +++ b/src/components/pool/PoolTabV3.tsx @@ -120,11 +120,22 @@ export const PoolTabV3 = ({ const item2_hashId = +item2.lpt_id.split('#')[1]; return item1_hashId - item2_hashId; }); + setListLiquidities(list); } } - const countV2 = listLiquiditiesLoading ? 0 : listLiquidities.length; + const groupedListByPoolId = listLiquidities.reduce((prev, cur) => { + if (!prev[cur.pool_id]) { + prev[cur.pool_id] = []; + } + prev[cur.pool_id].push(cur); + return prev; + }, {}); + + const countV2 = listLiquiditiesLoading + ? 0 + : Object.keys(groupedListByPoolId).length; const { finalStakeList, stakeList, v2StakeList, stakeListDone } = useStakeListByAccountId(); diff --git a/src/components/pool/RemoveOldPoolV3.tsx b/src/components/pool/RemoveOldPoolV3.tsx new file mode 100644 index 000000000..7ae8dc2ba --- /dev/null +++ b/src/components/pool/RemoveOldPoolV3.tsx @@ -0,0 +1,523 @@ +import { path } from 'animejs'; +import React, { useEffect, useMemo, useState, useContext, useRef } from 'react'; +import { FormattedMessage } from 'react-intl'; +import { WalletContext } from '../../utils/wallets-integration'; +import { useHistory } from 'react-router-dom'; +import { Card } from 'src/components/card/Card'; +import { isMobile } from '~utils/device'; +import { ModalClose } from 'src/components/icon'; +import { TokenMetadata } from '../../services/ft-contract'; +import { SwitchButton, Slider } from 'src/components/icon/V3'; +import { + GradientButton, + ButtonTextWrapper, + ConnectToNearBtn, +} from '../../components/button/Button'; +import { PoolSlippageSelectorV3 } from 'src/components/forms/SlippageSelector'; +import Modal from 'react-modal'; +import BigNumber from 'bignumber.js'; +import { + toPrecision, + toReadableNumber, + toNonDivisibleNumber, + formatWithCommas, +} from 'src/utils/numbers'; +import { + getPriceByPoint, + CONSTANT_D, + UserLiquidityInfo, + getXAmount_per_point_by_Lx, + getYAmount_per_point_by_Ly, + sort_tokens_by_base, +} from '../../services/commonV3'; +import { PoolInfo, remove_liquidity } from '../../services/swapV3'; +import _ from 'lodash'; +import { REF_POOL_NAV_TAB_KEY } from './PoolTabV3'; +export const RemoveOldPoolV3 = (props: any) => { + const { + tokenMetadata_x_y, + poolDetail, + userLiquidity, + tokenPriceList, + isLegacy, + ...restProps + }: { + tokenMetadata_x_y: TokenMetadata[]; + poolDetail: PoolInfo; + userLiquidity: UserLiquidityInfo; + tokenPriceList: any; + isLegacy?: boolean; + restProps: any; + } = props; + const [slippageTolerance, setSlippageTolerance] = useState(0.5); + const [tokenXAmount, setTokenXAmount] = useState(''); + const [tokenYAmount, setTokenYAmount] = useState(''); + const [liquidityAmount, setLiquidityAmount] = useState(''); + const [removeAmount, setRemoveAmount] = useState(''); + const [removeTokenXAmount, setRemoveTokenXAmount] = useState(''); + const [removeTokenYAmount, setRemoveTokenYAmount] = useState(''); + const [isInrange, setIsInrange] = useState(true); + const [removeLoading, setRemoveLoading] = useState(false); + const [rateDirection, setRateDirection] = useState(true); + const [removePercentAmount, setRemovePercentAmount] = useState('100'); + const [removePercentList] = useState([0, 25, 50, 75, 100]); + const { globalState } = useContext(WalletContext); + const v3PoolRemoveRef = useRef(null); + const sliderRef = useRef(null); + const isSignedIn = globalState.isSignedIn; + useEffect(() => { + if (tokenMetadata_x_y && userLiquidity && poolDetail) { + const { current_point } = poolDetail; + const { left_point, right_point } = userLiquidity; + if (current_point >= left_point && right_point > current_point) { + setIsInrange(true); + } else { + setIsInrange(false); + } + get_liquidity_x_y(); + getLiquidityAmount(); + } + }, [tokenMetadata_x_y, userLiquidity, poolDetail]); + useEffect(() => { + if (liquidityAmount) { + changeRemoveAmount('100'); + } + }, [liquidityAmount]); + useEffect(() => { + if (v3PoolRemoveRef.current) { + v3PoolRemoveRef.current.style.backgroundSize = `${removePercentAmount}% 100%`; + } + if (sliderRef.current) { + sliderRef.current.style.left = `${+removePercentAmount}%`; + const marginLeft = -13 - (20 * +removePercentAmount) / 100; + sliderRef.current.style.marginLeft = `${marginLeft}px`; + } + }, [removePercentAmount]); + function get_liquidity_x_y() { + const [tokenX, tokenY] = tokenMetadata_x_y; + const { left_point, right_point, amount: L } = userLiquidity; + const { current_point } = poolDetail; + // in range + if (current_point >= left_point && right_point > current_point) { + const tokenYAmount = getY(left_point, current_point, L, tokenY); + const tokenXAmount = getX(current_point + 1, right_point, L, tokenX); + const { amountx, amounty } = get_X_Y_In_CurrentPoint(tokenX, tokenY, L); + setTokenXAmount(new BigNumber(tokenXAmount).plus(amountx).toFixed()); + setTokenYAmount(new BigNumber(tokenYAmount).plus(amounty).toFixed()); + } + // only y token + if (current_point >= right_point) { + const tokenYAmount = getY(left_point, right_point, L, tokenY); + setTokenYAmount(tokenYAmount); + } + // only x token + if (left_point > current_point) { + const tokenXAmount = getX(left_point, right_point, L, tokenX); + setTokenXAmount(tokenXAmount); + } + } + function getLiquidityPrice() { + if (tokenPriceList && tokenMetadata_x_y) { + const [tokenX, tokenY] = tokenMetadata_x_y; + const priceX = tokenPriceList[tokenX.id]?.price || 0; + const priceY = tokenPriceList[tokenY.id]?.price || 0; + const tokenYTotalPrice = new BigNumber(tokenYAmount || 0).multipliedBy( + priceY + ); + const tokenXTotalPrice = new BigNumber(tokenXAmount || 0).multipliedBy( + priceX + ); + let total_price = tokenYTotalPrice.plus(tokenXTotalPrice); + total_price = new BigNumber(removePercentAmount) + .multipliedBy(total_price) + .dividedBy(100); + if (total_price.isEqualTo(0)) { + return '$0'; + } else if (total_price.isLessThan('0.01')) { + return '$<0.01'; + } else { + return `$` + formatWithCommas(toPrecision(total_price.toFixed(), 2)); + } + } + } + function getLiquidityAmount() { + const { amount } = userLiquidity; + setLiquidityAmount(amount); + } + + function displayLiquidityAmount() { + if (liquidityAmount) { + return toPrecision(liquidityAmount, 3); + } + } + function getY( + leftPoint: number, + rightPoint: number, + L: string, + token: TokenMetadata + ) { + const y = new BigNumber(L).multipliedBy( + (Math.pow(Math.sqrt(CONSTANT_D), rightPoint) - + Math.pow(Math.sqrt(CONSTANT_D), leftPoint)) / + (Math.sqrt(CONSTANT_D) - 1) + ); + const y_result = y.toFixed(); + return toReadableNumber(token.decimals, toPrecision(y_result, 0)); + } + function getX( + leftPoint: number, + rightPoint: number, + L: string, + token: TokenMetadata + ) { + const x = new BigNumber(L) + .multipliedBy( + (Math.pow(Math.sqrt(CONSTANT_D), rightPoint - leftPoint) - 1) / + (Math.pow(Math.sqrt(CONSTANT_D), rightPoint) - + Math.pow(Math.sqrt(CONSTANT_D), rightPoint - 1)) + ) + .toFixed(); + return toReadableNumber(token.decimals, toPrecision(x, 0)); + } + function get_X_Y_In_CurrentPoint( + tokenX: TokenMetadata, + tokenY: TokenMetadata, + L: string + ) { + const { liquidity, liquidity_x, current_point } = poolDetail; + const liquidity_y_big = new BigNumber(liquidity).minus(liquidity_x); + let Ly = '0'; + let Lx = '0'; + // only remove y + if (liquidity_y_big.isGreaterThanOrEqualTo(L)) { + Ly = L; + } else { + // have x and y + Ly = liquidity_y_big.toFixed(); + Lx = new BigNumber(L).minus(Ly).toFixed(); + } + const amountX = getXAmount_per_point_by_Lx(Lx, current_point); + const amountY = getYAmount_per_point_by_Ly(Ly, current_point); + const amountX_read = toReadableNumber( + tokenX.decimals, + toPrecision(amountX, 0) + ); + const amountY_read = toReadableNumber( + tokenY.decimals, + toPrecision(amountY, 0) + ); + return { amountx: amountX_read, amounty: amountY_read }; + } + + function getPoolFee() { + if (poolDetail) { + return poolDetail.fee / 10000; + } + return ''; + } + function changeRemoveAmount(value: string) { + setRemovePercentAmount(value); + const amount = new BigNumber(liquidityAmount) + .multipliedBy(value) + .dividedBy(100) + .toFixed(0, 1); + setRemoveAmount(amount); + getMinimumInfo(amount); + } + function getMinimumInfo(amount: string) { + if (liquidityAmount) { + const proportion = new BigNumber(amount || 0).dividedBy(liquidityAmount); + setRemoveTokenXAmount( + proportion.multipliedBy(tokenXAmount || '0').toFixed() + ); + setRemoveTokenYAmount( + proportion.multipliedBy(tokenYAmount || '0').toFixed() + ); + } + } + function getMinTokenAmount() { + const rate = 100 - slippageTolerance; + const result: any = {}; + if (removeTokenXAmount) { + const minX = new BigNumber(removeTokenXAmount || 0).multipliedBy( + rate / 100 + ); + let displayX = ''; + if (minX.isEqualTo(0)) { + displayX = '0'; + } else if (minX.isLessThan(0.001)) { + displayX = '<0.001'; + } else { + displayX = toPrecision(minX.toFixed(), 3); + } + result.minX = minX.toFixed(); + result.displayX = displayX; + } + if (removeTokenYAmount) { + const minY = new BigNumber(removeTokenYAmount || 0).multipliedBy( + rate / 100 + ); + let displayY = ''; + if (minY.isEqualTo(0)) { + displayY = '0'; + } else if (minY.isLessThan(0.001)) { + displayY = '<0.001'; + } else { + displayY = toPrecision(minY.toFixed(), 3); + } + result.minY = minY.toFixed(); + result.displayY = displayY; + } + if (tokenPriceList && tokenMetadata_x_y) { + const [tokenX, tokenY] = tokenMetadata_x_y; + const priceX = tokenPriceList[tokenX.id]?.price || 0; + const priceY = tokenPriceList[tokenY.id]?.price || 0; + const tokenXTotalPrice = new BigNumber(result.minX || 0).multipliedBy( + priceX + ); + const tokenYTotalPrice = new BigNumber(result.minY || 0).multipliedBy( + priceY + ); + const total_price = tokenYTotalPrice.plus(tokenXTotalPrice); + if (total_price.isEqualTo(0)) { + result.minPrice = '$0'; + } else if (total_price.isLessThan('0.001')) { + result.minPrice = '<$0.001'; + } else { + result.minPrice = `$` + toPrecision(total_price.toFixed(), 3); + } + } + return result; + } + function remove() { + setRemoveLoading(true); + const [tokenX, tokenY] = tokenMetadata_x_y; + const { lpt_id, mft_id } = userLiquidity; + + sessionStorage.setItem(REF_POOL_NAV_TAB_KEY, '/yourliquidity'); + + remove_liquidity({ + token_x: tokenX, + token_y: tokenY, + lpt_id, + mft_id, + amount: removeAmount, + min_amount_x: toNonDivisibleNumber(tokenX.decimals, MINDATA.minX), + min_amount_y: toNonDivisibleNumber(tokenY.decimals, MINDATA.minY), + isLegacy, + }); + } + function switchRate() { + setRateDirection(!rateDirection); + } + function getCurrentPrice(type: string) { + if (poolDetail && tokenMetadata_x_y) { + const { current_point } = poolDetail; + const [tokenX, tokenY] = tokenMetadata_x_y; + const rate = + Math.pow(10, tokenX.decimals) / Math.pow(10, tokenY.decimals); + let price = getPriceByPoint(current_point, rate); + if (type == 'l') { + price = new BigNumber('1').dividedBy(price).toFixed(); + } + + const price_big = new BigNumber(price); + if (price_big.isLessThan('0.001')) { + return '<0.001'; + } else { + return toPrecision(price, 6); + } + } + } + function getTokenPrice(type: string) { + if (tokenPriceList && tokenMetadata_x_y) { + const [tokenX, tokenY] = tokenMetadata_x_y; + if (type == 'l') { + return tokenPriceList[tokenX.id]?.price || '-'; + } + if (type == 'r') { + return tokenPriceList[tokenY.id]?.price || '-'; + } + } + return '-'; + } + const MINDATA: { + minX: string; + displayX: string; + minY: string; + displayY: string; + minPrice: string; + } = getMinTokenAmount(); + const isRemoveLiquidityDisabled = !( + +removeAmount > 0 && + new BigNumber(removeAmount || 0).isLessThanOrEqualTo(liquidityAmount || 0) + ); + const tokens = sort_tokens_by_base(tokenMetadata_x_y); + return ( + + +
+ + + +
+ +
+
+
+
+
+ + +
+ + {tokens[0]?.symbol}/{tokens[1]?.symbol} + +
+ {getLiquidityPrice()} +
+
+
+ {removePercentList.map((p) => { + return ( +
{ + if (isLegacy) return; + changeRemoveAmount(p.toString()); + }} + > + + {p}% + + +
+ ); + })} +
+
+ { + changeRemoveAmount(e.target.value); + }} + disabled={isLegacy ? true : false} + value={removePercentAmount} + type="range" + className={`w-full ${ + isLegacy ? 'pause cursor-not-allowed' : 'cursor-pointer' + }`} + style={{ backgroundSize: '100% 100%' }} + min="0" + max="100" + step="any" + /> +
+ + + {toPrecision(removePercentAmount.toString(), 0)}% + +
+
+
+
+ +
+
+
+ + + +
+
+
+ + {MINDATA.displayX || '-'} + +
+ + + {tokenMetadata_x_y && tokenMetadata_x_y[0].symbol} + +
+
+
+ + {MINDATA.displayY || '-'} + +
+ + + {tokenMetadata_x_y && tokenMetadata_x_y[1].symbol} + +
+
+
+ {isSignedIn ? ( + + } + /> + + ) : ( +
+ +
+ )} +
+
+ ); +}; diff --git a/src/components/pool/RemovePoolV3.tsx b/src/components/pool/RemovePoolV3.tsx index 22a7416cb..a5562d33e 100644 --- a/src/components/pool/RemovePoolV3.tsx +++ b/src/components/pool/RemovePoolV3.tsx @@ -1,13 +1,10 @@ -import { path } from 'animejs'; import React, { useEffect, useMemo, useState, useContext, useRef } from 'react'; import { FormattedMessage } from 'react-intl'; import { WalletContext } from '../../utils/wallets-integration'; -import { useHistory } from 'react-router-dom'; import { Card } from 'src/components/card/Card'; -import { isMobile } from 'src/utils/device'; import { ModalClose } from 'src/components/icon'; import { TokenMetadata } from '../../services/ft-contract'; -import { SwitchButton, Slider } from 'src/components/icon/V3'; +import ReactSlider from 'react-slider'; import { GradientButton, ButtonTextWrapper, @@ -20,7 +17,6 @@ import { toPrecision, toReadableNumber, toNonDivisibleNumber, - formatWithCommas, } from 'src/utils/numbers'; import { getPriceByPoint, @@ -29,126 +25,373 @@ import { getXAmount_per_point_by_Lx, getYAmount_per_point_by_Ly, sort_tokens_by_base, + getBinPointByPrice, + getBinPointByPoint, } from '../../services/commonV3'; -import { PoolInfo, remove_liquidity } from '../../services/swapV3'; -import _ from 'lodash'; +import { + PoolInfo, + batch_remove_liquidity_contract, +} from '../../services/swapV3'; import { REF_POOL_NAV_TAB_KEY } from './PoolTabV3'; +import Big from 'big.js'; +import { + get_custom_config_for_chart, + get_default_config_for_chart, +} from '../../components/d3Chart/config'; +import { + IChartItemConfig, + IChartConfig, +} from '../../components/d3Chart/interfaces'; +import { + formatNumber, + formatWithCommas_usd, +} from '../../components/d3Chart/utils'; +import { + IAddLiquidityInfo, + IRemoveLiquidityInfo, + IBatchUpdateiquidityInfo, +} from 'src/pages/poolsV3/interfaces'; +import DclChart from '../../components/d3Chart/DclChart'; +import { isMobile } from 'src/utils/device'; +import QuestionMark from 'src/components/farm/QuestionMark'; +import ReactTooltip from 'react-tooltip'; +import { useWalletSelector } from '../../context/WalletSelectorContext'; + +export type RemoveType = 'left' | 'right' | 'all'; + export const RemovePoolV3 = (props: any) => { const { tokenMetadata_x_y, poolDetail, - userLiquidity, tokenPriceList, isLegacy, + listLiquidities, ...restProps }: { tokenMetadata_x_y: TokenMetadata[]; poolDetail: PoolInfo; - userLiquidity: UserLiquidityInfo; tokenPriceList: any; isLegacy?: boolean; restProps: any; + listLiquidities: UserLiquidityInfo[]; } = props; + const SLOT_NUMBER = get_slot_number_in_a_bin(); const [slippageTolerance, setSlippageTolerance] = useState(0.5); - const [tokenXAmount, setTokenXAmount] = useState(''); - const [tokenYAmount, setTokenYAmount] = useState(''); - const [liquidityAmount, setLiquidityAmount] = useState(''); - const [removeAmount, setRemoveAmount] = useState(''); - const [removeTokenXAmount, setRemoveTokenXAmount] = useState(''); - const [removeTokenYAmount, setRemoveTokenYAmount] = useState(''); - const [isInrange, setIsInrange] = useState(true); + const pair_is_reverse = + sort_tokens_by_base(tokenMetadata_x_y)[0].id !== tokenMetadata_x_y[0].id; + const tokens = tokenMetadata_x_y; + const { decimals: token_x_decimals } = tokens[0]; + const { decimals: token_y_decimals } = tokens[1]; const [removeLoading, setRemoveLoading] = useState(false); - const [rateDirection, setRateDirection] = useState(true); - const [removePercentAmount, setRemovePercentAmount] = useState('100'); - const [removePercentList] = useState([0, 25, 50, 75, 100]); + const [removeType, setRemoveType] = useState('all'); const { globalState } = useContext(WalletContext); - const v3PoolRemoveRef = useRef(null); - const sliderRef = useRef(null); const isSignedIn = globalState.isSignedIn; + + const [minPoint, setMinPoint] = useState(); + const [maxPoint, setMaxPoint] = useState(); + const [minPrice, setMinPrice] = useState(''); + const [maxPrice, setMaxPrice] = useState(''); + const [maxBinAmount, setMaxBinAmount] = useState(); + + const [minBoxPrice, setMinBoxPrice] = useState(''); + const [maxBoxPrice, setMaxBoxPrice] = useState(''); + const [minBoxPoint, setMinBoxPoint] = useState(); + const [maxBoxPoint, setMaxBoxPoint] = useState(); + const [binBoxAmount, setBinBoxAmount] = useState(''); + const [show_boundary_tip, set_show_boundary_tip] = useState(false); + const [boundary_is_diff, set_boundary_is_diff] = useState(false); + + const { selector } = useWalletSelector(); useEffect(() => { - if (tokenMetadata_x_y && userLiquidity && poolDetail) { - const { current_point } = poolDetail; - const { left_point, right_point } = userLiquidity; - if (current_point >= left_point && right_point > current_point) { - setIsInrange(true); + // init + if (tokens && poolDetail && listLiquidities) { + get_user_points_range(); + } + }, [tokens, poolDetail, listLiquidities]); + useEffect(() => { + if (minBoxPoint && maxBoxPoint) { + let bin_amount; + if (pair_is_reverse) { + bin_amount = get_bin_amount_by_points(maxBoxPoint, minBoxPoint); } else { - setIsInrange(false); + bin_amount = get_bin_amount_by_points(minBoxPoint, maxBoxPoint); } - get_liquidity_x_y(); - getLiquidityAmount(); + setBinBoxAmount(bin_amount); } - }, [tokenMetadata_x_y, userLiquidity, poolDetail]); + }, [minBoxPoint, maxBoxPoint]); useEffect(() => { - if (liquidityAmount) { - changeRemoveAmount('100'); + if (binBoxAmount !== '') { + handleBinAmountToAppropriateAmount(+binBoxAmount); } - }, [liquidityAmount]); + }, [binBoxAmount]); useEffect(() => { - if (v3PoolRemoveRef.current) { - v3PoolRemoveRef.current.style.backgroundSize = `${removePercentAmount}% 100%`; + if (boundary_is_diff && removeType == 'all') { + set_show_boundary_tip(true); + } else { + set_show_boundary_tip(false); } - if (sliderRef.current) { - sliderRef.current.style.left = `${+removePercentAmount}%`; - const marginLeft = -13 - (20 * +removePercentAmount) / 100; - sliderRef.current.style.marginLeft = `${marginLeft}px`; + }, [boundary_is_diff, removeType]); + const [ + min_received_x_amount, + min_received_y_amount, + min_received_total_value, + ] = useMemo(() => { + if (tokenMetadata_x_y && minBoxPoint && maxBoxPoint) { + const { total_token_x_amount, total_token_y_amount, total_value } = + get_minimum_received_data(); + return [ + formatNumber(total_token_x_amount, 'down'), + formatNumber(total_token_y_amount, 'down'), + formatWithCommas_usd(total_value), + ]; } - }, [removePercentAmount]); - function get_liquidity_x_y() { - const [tokenX, tokenY] = tokenMetadata_x_y; - const { left_point, right_point, amount: L } = userLiquidity; - const { current_point } = poolDetail; - // in range - if (current_point >= left_point && right_point > current_point) { - const tokenYAmount = getY(left_point, current_point, L, tokenY); - const tokenXAmount = getX(current_point + 1, right_point, L, tokenX); - const { amountx, amounty } = get_X_Y_In_CurrentPoint(tokenX, tokenY, L); - setTokenXAmount(new BigNumber(tokenXAmount).plus(amountx).toFixed()); - setTokenYAmount(new BigNumber(tokenYAmount).plus(amounty).toFixed()); + return ['0', '0', '$0']; + }, [ + tokenPriceList, + tokenMetadata_x_y, + minBoxPoint, + maxBoxPoint, + slippageTolerance, + ]); + function get_will_deleted_nfts() { + let whole_deleted_nfts: UserLiquidityInfo[] = []; + let broken_deleted_nfts: UserLiquidityInfo[] = []; + if (removeType == 'all') { + whole_deleted_nfts = [].concat(listLiquidities); + } else if (removeType == 'left') { + listLiquidities.forEach((l: UserLiquidityInfo) => { + const { left_point, right_point } = l; + if (pair_is_reverse) { + if (right_point > maxBoxPoint) { + if (left_point >= maxBoxPoint) { + whole_deleted_nfts.push(l); + } else { + broken_deleted_nfts.push(l); + } + } + } else { + if (left_point < maxBoxPoint) { + if (right_point <= maxBoxPoint) { + whole_deleted_nfts.push(l); + } else { + broken_deleted_nfts.push(l); + } + } + } + }); + } else if (removeType == 'right') { + listLiquidities.forEach((l: UserLiquidityInfo) => { + const { left_point, right_point } = l; + if (pair_is_reverse) { + if (left_point < minBoxPoint) { + if (right_point <= minBoxPoint) { + whole_deleted_nfts.push(l); + } else { + broken_deleted_nfts.push(l); + } + } + } else { + if (right_point > minBoxPoint) { + if (left_point >= minBoxPoint) { + whole_deleted_nfts.push(l); + } else { + broken_deleted_nfts.push(l); + } + } + } + }); } - // only y token - if (current_point >= right_point) { - const tokenYAmount = getY(left_point, right_point, L, tokenY); - setTokenYAmount(tokenYAmount); + return { + whole_deleted_nfts, + broken_deleted_nfts, + }; + } + function get_slot_number_in_a_bin() { + const pool_id = poolDetail?.pool_id; + const { bin } = get_default_config_for_chart() as IChartItemConfig; + const custom_config: IChartConfig = get_custom_config_for_chart(); + const slots = custom_config[pool_id]?.bin || bin; + return slots; + } + function get_user_points_range() { + const user_points: number[] = []; + listLiquidities.forEach((l: UserLiquidityInfo) => { + user_points.push(l.left_point, l.right_point); + }); + user_points.sort((b, a) => b - a); + const user_min_point = user_points[0]; + const user_max_point = user_points[user_points.length - 1]; + const min_point = get_bin_point_by_point(user_min_point, 'floor'); + const max_point = get_bin_point_by_point(user_max_point, 'ceil'); + if (min_point !== user_min_point || max_point !== user_max_point) { + set_boundary_is_diff(true); } - // only x token - if (left_point > current_point) { - const tokenXAmount = getX(left_point, right_point, L, tokenX); - setTokenXAmount(tokenXAmount); + let min_price, max_price; + if (pair_is_reverse) { + min_price = reverse_price(get_bin_price_by_point(max_point)); + max_price = reverse_price(get_bin_price_by_point(min_point)); + } else { + min_price = get_bin_price_by_point(min_point); + max_price = get_bin_price_by_point(max_point); + } + + const max_bin_amount = get_bin_amount_by_points(min_point, max_point); + setMinPoint(min_point); + setMaxPoint(max_point); + setMinPrice(min_price); + setMaxPrice(max_price); + setMaxBinAmount(max_bin_amount); + + setMinBoxPrice(min_price); + setMaxBoxPrice(max_price); + if (pair_is_reverse) { + setMinBoxPoint(max_point); + setMaxBoxPoint(min_point); + } else { + setMinBoxPoint(min_point); + setMaxBoxPoint(max_point); } + setBinBoxAmount(max_bin_amount); } - function getLiquidityPrice() { - if (tokenPriceList && tokenMetadata_x_y) { - const [tokenX, tokenY] = tokenMetadata_x_y; - const priceX = tokenPriceList[tokenX.id]?.price || 0; - const priceY = tokenPriceList[tokenY.id]?.price || 0; - const tokenYTotalPrice = new BigNumber(tokenYAmount || 0).multipliedBy( - priceY - ); - const tokenXTotalPrice = new BigNumber(tokenXAmount || 0).multipliedBy( - priceX - ); - let total_price = tokenYTotalPrice.plus(tokenXTotalPrice); - total_price = new BigNumber(removePercentAmount) - .multipliedBy(total_price) - .dividedBy(100); - if (total_price.isEqualTo(0)) { - return '$0'; - } else if (total_price.isLessThan('0.01')) { - return '$<0.01'; + function reverse_price(price: string) { + if (Big(price).eq(0)) return '0'; + return Big(1).div(price).toFixed(); + } + function get_bin_amount_by_points(left_point: number, right_point: number) { + const { point_delta } = poolDetail; + const binWidth = SLOT_NUMBER * point_delta; + const bin_amount = Big(right_point - left_point) + .div(binWidth) + .toFixed(); + return bin_amount; + } + function get_bin_price_by_point(point: number) { + const decimalRate = + Math.pow(10, token_x_decimals) / Math.pow(10, token_y_decimals); + const price = getPriceByPoint(point, decimalRate); + return price; + } + function get_bin_point_by_price(price: string) { + const point_delta = poolDetail.point_delta; + const decimalRate = + Math.pow(10, token_y_decimals) / Math.pow(10, token_x_decimals); + const point = getBinPointByPrice( + point_delta, + price, + decimalRate, + SLOT_NUMBER + ); + return point; + } + function get_bin_point_by_point( + point: number, + type: 'round' | 'floor' | 'ceil' + ) { + const point_delta = poolDetail.point_delta; + const bin_point = getBinPointByPoint(point_delta, SLOT_NUMBER, point, type); + return bin_point; + } + function handleMinBoxPriceToAppropriatePoint() { + /** + * min price <= price < max box price + */ + let appropriate_price; + let appropriate_point; + const big_price = Big(minBoxPrice || 0); + if (big_price.lt(minPrice)) { + appropriate_price = minPrice; + if (pair_is_reverse) { + appropriate_point = maxPoint; + } else { + appropriate_point = minPoint; + } + } else if (big_price.gt(maxBoxPrice)) { + appropriate_price = maxBoxPrice; + appropriate_point = maxBoxPoint; + } else { + if (pair_is_reverse) { + appropriate_point = get_bin_point_by_price(reverse_price(minBoxPrice)); + appropriate_price = reverse_price( + get_bin_price_by_point(appropriate_point) + ); } else { - return `$` + formatWithCommas(toPrecision(total_price.toFixed(), 2)); + appropriate_point = get_bin_point_by_price(minBoxPrice); + appropriate_price = get_bin_price_by_point(appropriate_point); } } + setMinBoxPrice(appropriate_price); + setMinBoxPoint(appropriate_point); } - function getLiquidityAmount() { - const { amount } = userLiquidity; - setLiquidityAmount(amount); + function handleMaxBoxPriceToAppropriatePoint() { + /** + * min box price <= price <= max price + */ + let appropriate_price; + let appropriate_point; + const big_price = Big(maxBoxPrice || 0); + if (big_price.lt(minBoxPrice)) { + appropriate_price = minBoxPrice; + appropriate_point = minBoxPoint; + } else if (big_price.gt(maxPrice)) { + appropriate_price = maxPrice; + if (pair_is_reverse) { + appropriate_point = minPoint; + } else { + appropriate_point = maxPoint; + } + } else { + if (pair_is_reverse) { + appropriate_point = get_bin_point_by_price(reverse_price(maxBoxPrice)); + appropriate_price = reverse_price( + get_bin_price_by_point(appropriate_point) + ); + } else { + appropriate_point = get_bin_point_by_price(maxBoxPrice); + appropriate_price = get_bin_price_by_point(appropriate_point); + } + } + setMaxBoxPrice(appropriate_price); + setMaxBoxPoint(appropriate_point); } - - function displayLiquidityAmount() { - if (liquidityAmount) { - return toPrecision(liquidityAmount, 3); + /** + * 左右点位改变会触发bin amount随之更改 + * bin amount 修改会改变可以修改的点位 + * 0 <= bin amount < max bin amount + */ + function handleBinAmountToAppropriateAmount(binAmount: number) { + const amount_int = binAmount || +binBoxAmount; + const { point_delta } = poolDetail; + const binWidth = SLOT_NUMBER * point_delta; + let appropriate_amount = amount_int; + if (amount_int > +maxBinAmount) { + appropriate_amount = +maxBinAmount; } + if (removeType == 'left') { + let right_box_point, right_box_price; + if (pair_is_reverse) { + right_box_point = maxPoint - binWidth * appropriate_amount; + right_box_price = reverse_price( + get_bin_price_by_point(right_box_point) + ); + } else { + right_box_point = minPoint + binWidth * appropriate_amount; + right_box_price = get_bin_price_by_point(right_box_point); + } + setMaxBoxPoint(right_box_point); + setMaxBoxPrice(right_box_price); + } else if (removeType == 'right') { + let left_box_point, left_box_price; + if (pair_is_reverse) { + left_box_point = minPoint + binWidth * appropriate_amount; + left_box_price = reverse_price(get_bin_price_by_point(left_box_point)); + } else { + left_box_point = maxPoint - binWidth * appropriate_amount; + left_box_price = get_bin_price_by_point(left_box_point); + } + setMinBoxPoint(left_box_point); + setMinBoxPrice(left_box_price); + } + setBinBoxAmount(appropriate_amount.toString()); } function getY( leftPoint: number, @@ -208,157 +451,299 @@ export const RemovePoolV3 = (props: any) => { ); return { amountx: amountX_read, amounty: amountY_read }; } + function batch_remove_nfts() { + setRemoveLoading(true); + const [tokenX, tokenY] = tokenMetadata_x_y; + sessionStorage.setItem(REF_POOL_NAV_TAB_KEY, '/yourliquidity'); + let batch_remove_liquidity: IRemoveLiquidityInfo[]; + let batch_update_liquidity: IBatchUpdateiquidityInfo; + let mint_liquidities: UserLiquidityInfo[] = []; + const { whole_deleted_nfts, broken_deleted_nfts } = get_will_deleted_nfts(); + const { pool_id } = poolDetail; + /** + * step1 找到被截断的nft的 未截断的区间 + * step2 找到区间,也知道高度==>推导出这个区间的 tokenx的数量和tokeny的数量 + * step3 未截断的区间 和 token 数量作为 添加nft的参数 + */ + if (broken_deleted_nfts.length) { + const removeLiquidityInfos: IRemoveLiquidityInfo[] = []; + const addLiquidityInfoList: IAddLiquidityInfo[] = []; + broken_deleted_nfts.forEach((l: UserLiquidityInfo) => { + const { amount, lpt_id, left_point, right_point, mft_id } = l; + const [new_left_point, new_right_point] = get_un_deleted_range(l); + const [new_token_x_amount, new_token_y_amount] = + get_min_x_y_amount_of_liquidity({ + left_point: new_left_point, + right_point: new_right_point, + amount, + }); + const [min_token_x_amount, min_token_y_amount] = + get_min_x_y_amount_of_liquidity({ + left_point: left_point, + right_point: right_point, + amount, + }); + addLiquidityInfoList.push({ + pool_id, + left_point: new_left_point, + right_point: new_right_point, + amount_x: toNonDivisibleNumber(tokenX.decimals, new_token_x_amount), + amount_y: toNonDivisibleNumber(tokenY.decimals, new_token_y_amount), + min_amount_x: '0', + min_amount_y: '0', + }); + removeLiquidityInfos.push({ + lpt_id, + amount, + min_amount_x: toNonDivisibleNumber( + tokenX.decimals, + min_token_x_amount + ), + min_amount_y: toNonDivisibleNumber( + tokenY.decimals, + min_token_y_amount + ), + }); + if (mft_id) { + mint_liquidities.push(l); + } + }); - function getPoolFee() { - if (poolDetail) { - return poolDetail.fee / 10000; + batch_update_liquidity = { + remove_liquidity_infos: removeLiquidityInfos, + add_liquidity_infos: addLiquidityInfoList, + }; } - return ''; - } - function changeRemoveAmount(value: string) { - setRemovePercentAmount(value); - const amount = new BigNumber(liquidityAmount) - .multipliedBy(value) - .dividedBy(100) - .toFixed(0, 1); - setRemoveAmount(amount); - getMinimumInfo(amount); - } - function getMinimumInfo(amount: string) { - if (liquidityAmount) { - const proportion = new BigNumber(amount || 0).dividedBy(liquidityAmount); - setRemoveTokenXAmount( - proportion.multipliedBy(tokenXAmount || '0').toFixed() - ); - setRemoveTokenYAmount( - proportion.multipliedBy(tokenYAmount || '0').toFixed() - ); + if (whole_deleted_nfts.length) { + const batchRemoveLiquidity: IRemoveLiquidityInfo[] = []; + whole_deleted_nfts.forEach((l: UserLiquidityInfo) => { + const { amount, lpt_id, left_point, right_point, mft_id } = l; + const [min_token_x_amount, min_token_y_amount] = + get_min_x_y_amount_of_liquidity({ + left_point: left_point, + right_point: right_point, + amount, + }); + batchRemoveLiquidity.push({ + lpt_id, + amount, + min_amount_x: toNonDivisibleNumber( + tokenX.decimals, + min_token_x_amount + ), + min_amount_y: toNonDivisibleNumber( + tokenY.decimals, + min_token_y_amount + ), + }); + if (mft_id) { + mint_liquidities.push(l); + } + }); + batch_remove_liquidity = batchRemoveLiquidity; } + batch_remove_liquidity_contract({ + token_x: tokenX, + token_y: tokenY, + batch_remove_liquidity, + batch_update_liquidity, + mint_liquidities, + selectedWalletId: selector.store.getState().selectedWalletId, + }).then(() => { + sessionStorage.setItem('REMOVE_POOL_ID', pool_id); + }); } - function getMinTokenAmount() { - const rate = 100 - slippageTolerance; - const result: any = {}; - if (removeTokenXAmount) { - const minX = new BigNumber(removeTokenXAmount || 0).multipliedBy( - rate / 100 - ); - let displayX = ''; - if (minX.isEqualTo(0)) { - displayX = '0'; - } else if (minX.isLessThan(0.001)) { - displayX = '<0.001'; - } else { - displayX = toPrecision(minX.toFixed(), 3); - } - result.minX = minX.toFixed(); - result.displayX = displayX; + function get_minimum_received_data() { + /** + * step1 完整删除的nfts,求出每个nft 对应的最小 x,y 的数量 + * step2 截段的nfts,求出每个nft被删除那一段流动性 对应的最小 x,y的数量 + * step3 把上述step1, step2 得到的x,y 累加起来即可 + */ + let total_token_x_amount = Big(0); + let total_token_y_amount = Big(0); + let minimum_total_value = Big(0); + const { whole_deleted_nfts, broken_deleted_nfts } = get_will_deleted_nfts(); + if (whole_deleted_nfts.length) { + whole_deleted_nfts.forEach((l: UserLiquidityInfo) => { + const { amount, left_point, right_point } = l; + const [min_token_x_amount, min_token_y_amount] = + get_min_x_y_amount_of_liquidity({ + left_point: left_point, + right_point: right_point, + amount, + }); + total_token_x_amount = total_token_x_amount.plus( + min_token_x_amount || 0 + ); + total_token_y_amount = total_token_y_amount.plus( + min_token_y_amount || 0 + ); + }); } - if (removeTokenYAmount) { - const minY = new BigNumber(removeTokenYAmount || 0).multipliedBy( - rate / 100 - ); - let displayY = ''; - if (minY.isEqualTo(0)) { - displayY = '0'; - } else if (minY.isLessThan(0.001)) { - displayY = '<0.001'; - } else { - displayY = toPrecision(minY.toFixed(), 3); - } - result.minY = minY.toFixed(); - result.displayY = displayY; + if (broken_deleted_nfts.length) { + broken_deleted_nfts.forEach((l: UserLiquidityInfo) => { + const { amount, left_point, right_point } = l; + const [new_left_point, new_right_point] = get_un_deleted_range(l); + const [new_token_x_amount, new_token_y_amount] = + get_x_y_amount_of_liquidity({ + left_point: new_left_point, + right_point: new_right_point, + amount, + }); + const [min_token_x_amount, min_token_y_amount] = + get_min_x_y_amount_of_liquidity({ + left_point: left_point, + right_point: right_point, + amount, + }); + const broken_min_token_x_amount = + Big(min_token_x_amount).minus(new_token_x_amount); + const broken_min_token_y_amount = + Big(min_token_y_amount).minus(new_token_y_amount); + if (broken_min_token_x_amount.gt(0)) { + total_token_x_amount = total_token_x_amount.plus( + broken_min_token_x_amount || 0 + ); + } + if (broken_min_token_y_amount.gt(0)) { + total_token_y_amount = total_token_y_amount.plus( + broken_min_token_y_amount || 0 + ); + } + }); } if (tokenPriceList && tokenMetadata_x_y) { const [tokenX, tokenY] = tokenMetadata_x_y; const priceX = tokenPriceList[tokenX.id]?.price || 0; const priceY = tokenPriceList[tokenY.id]?.price || 0; - const tokenXTotalPrice = new BigNumber(result.minX || 0).multipliedBy( - priceX - ); - const tokenYTotalPrice = new BigNumber(result.minY || 0).multipliedBy( - priceY - ); - const total_price = tokenYTotalPrice.plus(tokenXTotalPrice); - if (total_price.isEqualTo(0)) { - result.minPrice = '$0'; - } else if (total_price.isLessThan('0.001')) { - result.minPrice = '<$0.001'; - } else { - result.minPrice = `$` + toPrecision(total_price.toFixed(), 3); - } + const token_x_value = total_token_x_amount.mul(priceX); + const token_y_value = total_token_y_amount.mul(priceY); + minimum_total_value = token_x_value.plus(token_y_value); } - return result; - } - function remove() { - setRemoveLoading(true); - const [tokenX, tokenY] = tokenMetadata_x_y; - const { lpt_id, mft_id } = userLiquidity; - - sessionStorage.setItem(REF_POOL_NAV_TAB_KEY, '/yourliquidity'); - - remove_liquidity({ - token_x: tokenX, - token_y: tokenY, - lpt_id, - mft_id, - amount: removeAmount, - min_amount_x: toNonDivisibleNumber(tokenX.decimals, MINDATA.minX), - min_amount_y: toNonDivisibleNumber(tokenY.decimals, MINDATA.minY), - isLegacy, - }); + const rate = (100 - slippageTolerance) / 100; + return { + total_token_x_amount: total_token_x_amount.toFixed(), + total_token_y_amount: total_token_y_amount.toFixed(), + minimum_total_value: minimum_total_value.toFixed(), + total_value: minimum_total_value.div(rate).toFixed(), + }; } - function switchRate() { - setRateDirection(!rateDirection); - } - function getCurrentPrice(type: string) { - if (poolDetail && tokenMetadata_x_y) { - const { current_point } = poolDetail; - const [tokenX, tokenY] = tokenMetadata_x_y; - const rate = - Math.pow(10, tokenX.decimals) / Math.pow(10, tokenY.decimals); - let price = getPriceByPoint(current_point, rate); - if (type == 'l') { - price = new BigNumber('1').dividedBy(price).toFixed(); + function get_un_deleted_range(liquidity: UserLiquidityInfo) { + const { left_point, right_point } = liquidity; + // intersection part + let intersection_l, intersection_r; + if (pair_is_reverse) { + intersection_l = Math.min(right_point, minBoxPoint); + intersection_r = Math.max(left_point, maxBoxPoint); + } else { + intersection_l = Math.max(left_point, minBoxPoint); + intersection_r = Math.min(right_point, maxBoxPoint); + } + // intersection part + let un_intersection_l; + let un_intersection_r; + if (removeType == 'left') { + un_intersection_l = intersection_r; + if (pair_is_reverse) { + un_intersection_r = left_point; + } else { + un_intersection_r = right_point; } - - const price_big = new BigNumber(price); - if (price_big.isLessThan('0.001')) { - return '<0.001'; + } else if (removeType == 'right') { + if (pair_is_reverse) { + un_intersection_l = right_point; } else { - return toPrecision(price, 6); + un_intersection_l = left_point; } + un_intersection_r = intersection_l; + } + if (pair_is_reverse) { + return [un_intersection_r, un_intersection_l]; + } else { + return [un_intersection_l, un_intersection_r]; } } - function getTokenPrice(type: string) { - if (tokenPriceList && tokenMetadata_x_y) { - const [tokenX, tokenY] = tokenMetadata_x_y; - if (type == 'l') { - return tokenPriceList[tokenX.id]?.price || '-'; - } - if (type == 'r') { - return tokenPriceList[tokenY.id]?.price || '-'; - } + function get_x_y_amount_of_liquidity(liquidity: { + left_point: number; + right_point: number; + amount: string; + }) { + const [tokenX, tokenY] = tokenMetadata_x_y; + const { left_point, right_point, amount: L } = liquidity; + const { current_point } = poolDetail; + let curTokenXAmount = '0'; + let curTokenYAmount = '0'; + // in range + if (current_point >= left_point && right_point > current_point) { + curTokenXAmount = getX(current_point + 1, right_point, L, tokenX); + curTokenYAmount = getY(left_point, current_point, L, tokenY); + const { amountx, amounty } = get_X_Y_In_CurrentPoint(tokenX, tokenY, L); + curTokenXAmount = Big(amountx || '0') + .plus(curTokenXAmount || '0') + .toFixed(); + curTokenYAmount = Big(amounty || '0') + .plus(curTokenYAmount || '0') + .toFixed(); } - return '-'; + // only y token + if (current_point >= right_point) { + curTokenYAmount = getY(left_point, right_point, L, tokenY); + } + // only x token + if (left_point > current_point) { + curTokenXAmount = getX(left_point, right_point, L, tokenX); + } + return [curTokenXAmount, curTokenYAmount]; } - const MINDATA: { - minX: string; - displayX: string; - minY: string; - displayY: string; - minPrice: string; - } = getMinTokenAmount(); - const isRemoveLiquidityDisabled = !( - +removeAmount > 0 && - new BigNumber(removeAmount || 0).isLessThanOrEqualTo(liquidityAmount || 0) - ); - const tokens = sort_tokens_by_base(tokenMetadata_x_y); + function get_min_x_y_amount_of_liquidity(liquidity: { + left_point: number; + right_point: number; + amount: string; + }) { + const rate = (100 - slippageTolerance) / 100; + const [token_x_amount, token_y_amount] = + get_x_y_amount_of_liquidity(liquidity); + const min_token_x_amount = Big(token_x_amount || 0) + .mul(rate) + .toFixed(); + const min_token_y_amount = Big(token_y_amount || 0) + .mul(rate) + .toFixed(); + return [min_token_x_amount, min_token_y_amount]; + } + function get_boundary_tip() { + const tip = + 'The Min Price and Max price displayed here correspond to the boundaries of the bins. Since you added liquidity before the upgrade, the liquidity boundaries are within the bins containing the Min Price or Max price. Therefore, your actual price range may differ from the the Min Price or Max price displayed here.'; + let result: string = `
${tip}
`; + return result; + } + const isRemoveLiquidityDisabled = minBoxPoint == maxBoxPoint; + const is_mobile = isMobile(); + const cardWidth = is_mobile ? '100vw' : '550px'; + const cardHeight = is_mobile ? '70vh' : '95vh'; return ( - + + {/* Title */}
@@ -367,9 +752,14 @@ export const RemovePoolV3 = (props: any) => {
+ {/* Symbol pairs */}
-
+
{ >
- {tokens[0]?.symbol}/{tokens[1]?.symbol} + {pair_is_reverse + ? `${tokens[1]?.symbol}-${tokens[0]?.symbol}` + : `${tokens[0]?.symbol}-${tokens[1]?.symbol}`}
- {getLiquidityPrice()} + + {min_received_total_value} +
-
-
- {removePercentList.map((p) => { - return ( -
{ - if (isLegacy) return; - changeRemoveAmount(p.toString()); - }} - > - - {p}% - - -
- ); - })} +
+ + ( + {pair_is_reverse + ? `${tokens[0]?.symbol}/${tokens[1]?.symbol}` + : `${tokens[1]?.symbol}/${tokens[0]?.symbol}`} + ) + + {maxPoint && ( + + )} +
+ {/* Removing way */} +
+
+ +
+
+
{ + e.preventDefault(); + e.stopPropagation(); + setRemoveType('left'); + setMinBoxPrice(minPrice); + if (pair_is_reverse) { + setMinBoxPoint(maxPoint); + } else { + setMinBoxPoint(minPoint); + } + }} + > + +
+ +
{ + e.preventDefault(); + e.stopPropagation(); + setRemoveType('right'); + setMaxBoxPrice(maxPrice); + if (pair_is_reverse) { + setMaxBoxPoint(minPoint); + } else { + setMaxBoxPoint(maxPoint); + } + }} + > + +
+ +
{ + e.preventDefault(); + e.stopPropagation(); + setRemoveType('all'); + setMinBoxPrice(minPrice); + setMaxBoxPrice(maxPrice); + if (pair_is_reverse) { + setMinBoxPoint(maxPoint); + setMaxBoxPoint(minPoint); + } else { + setMinBoxPoint(minPoint); + setMaxBoxPoint(maxPoint); + } + }} + > + +
-
+
+ {/* remove slider */} + {/* binBoxAmount 控制 */} + { + setBinBoxAmount(v.toString()); + }} + value={+binBoxAmount} + min={0} + max={+maxBinAmount} + step={1} + > + {/* Set points */} +
+ {/* min price */} +
+ + + + { - changeRemoveAmount(e.target.value); + const value = e.target.value; + setMinBoxPrice(value); + }} + inputMode="decimal" + onBlur={() => { + handleMinBoxPriceToAppropriatePoint(); }} - disabled={isLegacy ? true : false} - value={removePercentAmount} - type="range" - className={`w-full ${ - isLegacy ? 'pause cursor-not-allowed' : 'cursor-pointer' + disabled={removeType !== 'right'} + > +
+ {/* max price */} +
+ + + + { + const value = e.target.value; + setMaxBoxPrice(value); + }} + value={maxBoxPrice !== '' ? toPrecision(maxBoxPrice, 8) : ''} + inputMode="decimal" + onBlur={() => { + handleMaxBoxPriceToAppropriatePoint(); + }} + disabled={removeType !== 'left'} + > +
+ {/* bin amount */} +
+ + + + + +
+
+ {/* Tip */} + {show_boundary_tip ? ( +
+ {/* */}
- - - {toPrecision(removePercentAmount.toString(), 0)}% - + +
+ + Why the Min Price or Max price here differ from my actual price + range? +
-
+ ) : null} + + {/* Slippage */}
+ {/* Minimum received */}
-
- +
+ -
-
-
- - {MINDATA.displayX || '-'} - -
+ +
+
- - {tokenMetadata_x_y && tokenMetadata_x_y[0].symbol} + + + {min_received_x_amount}
-
-
- - {MINDATA.displayY || '-'} - -
+
- - {tokenMetadata_x_y && tokenMetadata_x_y[1].symbol} + + + {min_received_y_amount}
+ {/* Button */} {isSignedIn ? ( { isRemoveLiquidityDisabled ? 'cursor-not-allowed' : '' }`} className={`mt-8 w-full h-14 text-center text-lg text-white focus:outline-none font-semibold`} - backgroundImage="linear-gradient(270deg, #7F43FF 0%, #00C6A2 97.06%)" + backgroundImage="linear-gradient(180deg, #C0B1A3 0%, #92877D 100%)" > { ); }; +export function IntegerInputComponent({ + value, + setValue, + disabled, + className, + max, + onBlur, +}: any) { + const removeLeadingZeros = (s: string) => { + const oldLen = s.length; + s = s.replace(/^0+/, ''); + + if (s.length === 0 && oldLen > 0) { + s = '0'; + } + + if (max && Number(s) > max) { + return max; + } + + return s; + }; + + const handleChange = (val: string) => { + val = val.replace(/[^\d]/g, ''); + val = removeLeadingZeros(val); + setValue(val); + }; + + return ( +
+ { + if (onBlur) { + onBlur(); + } else if (!target.value) { + setValue(1); + } + }} + onChange={({ target }) => { + handleChange(target.value); + }} + /> +
+ ); +} diff --git a/src/components/pool/YourLiquidityV1.tsx b/src/components/pool/YourLiquidityV1.tsx index 295d5fc07..7a8f33364 100644 --- a/src/components/pool/YourLiquidityV1.tsx +++ b/src/components/pool/YourLiquidityV1.tsx @@ -583,7 +583,11 @@ function LiquidityContainerStyle2() { const simplePoolsFinal = useMemo(() => { const activeSimplePools: PoolRPCView[] = pools.filter( (p: PoolRPCView, i: number) => { - return batchTotalSharesSimplePools[i] !== 0 && p.id !== vePool?.id; + if (!vePool || !getConfig().REF_VE_CONTRACT_ID) { + return batchTotalSharesSimplePools[i] !== 0; + } else { + return batchTotalSharesSimplePools[i] !== 0 && p.id !== vePool?.id; + } } ); return activeSimplePools; @@ -598,9 +602,9 @@ function LiquidityContainerStyle2() { }, [batchTotalShares]); return (
- {vePool ? ( + {!vePool || !getConfig().REF_VE_CONTRACT_ID ? null : ( - ) : null} + )} {stablePoolsFinal.map((pool: PoolRPCView) => { return ( ([]); + + const [groupYourLiquidity, setGroupYourLiquidity] = useState(); + const [liquidities_details_list, set_iquidities_details_list] = useState< UserLiquidityInfo[] >([]); @@ -93,6 +111,7 @@ export function YourLiquidityV2(props: any) { const { globalState } = useContext(WalletContext); const isSignedIn = globalState.isSignedIn; + useRemoveLiquidityUrlHandle(); useEffect(() => { getBoostTokenPrices().then(setTokenPriceList); get_all_seeds().then((seeds: Seed[]) => { @@ -129,6 +148,67 @@ export function YourLiquidityV2(props: any) { } return temp_map; }, [liquidities_details_list]); + useEffect(() => { + if ( + !liquidities_list || + !all_pools_map || + !liquidities_tokens_metas || + !tokenPriceList || + !dcl_liquidities_details_map || + Object.keys(dcl_liquidities_details_map).length === 0 + ) { + return; + } + Promise.all( + liquidities_list.map((liquidity) => + getYourLiquidityData({ + liquidity, + all_seeds, + tokenPriceList, + poolDetail: all_pools_map?.[liquidity.pool_id], + liquidityDetail: dcl_liquidities_details_map?.[liquidity.lpt_id], + liquidities_tokens_metas, + }) + ) + ).then((yourLiquidityList) => { + const groupedLiquidity = yourLiquidityList.reduce((acc, cur) => { + const { pool_id } = cur; + const [token_x, token_y] = pool_id.split('|'); + const poolDetail = all_pools_map[pool_id]; + const tokensMeta = [ + liquidities_tokens_metas[token_x], + liquidities_tokens_metas[token_y], + ]; + + if (acc[pool_id]) { + acc[pool_id].push(cur); + + return acc; + } else { + return { + ...acc, + [pool_id]: [ + { + ...cur, + poolDetail, + tokensMeta, + }, + ], + }; + } + }, {} as Record); + + setGroupYourLiquidity(groupedLiquidity); + }); + }, [ + liquidities_list, + all_seeds, + all_pools_map, + liquidities_tokens_metas, + tokenPriceList, + dcl_liquidities_details_map, + Object.keys(dcl_liquidities_details_map).length === 0, + ]); async function get_list_liquidities() { const list: UserLiquidityInfo[] = await list_liquidities(); if (list.length > 0) { @@ -168,7 +248,17 @@ export function YourLiquidityV2(props: any) { set_dcl_liquidities_details_list_done(true); } setLiquidityLoadingDone && setLiquidityLoadingDone(true); - setLiquidityQuantity && setLiquidityQuantity(list.length); + + const groupedList = list.reduce((pre, cur) => { + const { pool_id } = cur; + const pool = pre[pool_id] || []; + pool.push(cur); + pre[pool_id] = pool; + return pre; + }, {}); + + setLiquidityQuantity && + setLiquidityQuantity(Object.keys(groupedList).length); } async function get_all_pools_detail() { const pool_ids = new Set(); @@ -258,28 +348,59 @@ export function YourLiquidityV2(props: any) { } return (
- {liquidities_list.map((liquidity: UserLiquidityInfo, index: number) => { - return ( -
- + {groupYourLiquidity && Object.entries(groupYourLiquidity).length && ( +
+
+
- ); - })} + +
+ +
+ +
APR(24h)
+
+ +
+
+ )} + + {groupYourLiquidity && + Object.entries(groupYourLiquidity).map( + ([id, liquidity]: any, index: number) => { + return ( + l.liquidityDetail)} + tokenPriceList={tokenPriceList} + all_seeds={all_seeds} + styleType={styleType} + /> + ); + } + )}
); } -function UserLiquidityLine({ + +// a function just to return your liquidity data +async function getYourLiquidityData({ liquidity, all_seeds, - styleType, tokenPriceList, poolDetail, liquidities_tokens_metas, @@ -287,105 +408,33 @@ function UserLiquidityLine({ }: { liquidity: UserLiquidityInfo; all_seeds: Seed[]; - styleType: string; tokenPriceList: Record; poolDetail: PoolInfo; liquidities_tokens_metas: Record; liquidityDetail: UserLiquidityInfo; }) { - const [hover, setHover] = useState(false); - const [isInrange, setIsInrange] = useState(true); - const [your_liquidity, setYour_liquidity] = useState(''); - const [claimLoading, setClaimLoading] = useState(false); - const [showRemoveBox, setShowRemoveBox] = useState(false); - const [showAddBox, setShowAddBox] = useState(false); - const [related_farms, set_related_farms] = useState([]); - const [is_in_farming, set_is_in_farming] = useState(false); - const [related_seed_info, set_related_seed_info] = useState< - Record - >({}); + function get_your_liquidity_in_farm_range() { + if (!related_seed_info.targetSeed || !related_seed_info.targetSeed.seed_id) + return '0'; - const { lpt_id, pool_id, left_point, right_point, amount: L } = liquidity; - const [token_x, token_y, fee] = pool_id.split('|'); - const tokenMetadata_x_y = liquidities_tokens_metas - ? [liquidities_tokens_metas[token_x], liquidities_tokens_metas[token_y]] - : null; - const rate_need_to_reverse_display = useMemo(() => { - if (tokenMetadata_x_y) { - const [tokenX] = tokenMetadata_x_y; - if (TOKEN_LIST_FOR_RATE.indexOf(tokenX.symbol) > -1) return true; - return false; - } - }, [tokenMetadata_x_y?.length]); + const seed_info = related_seed_info.targetSeed.seed_id.split('&'); + const farmRangeLeft = Number(seed_info[2]); + const farmRangeRight = Number(seed_info[3]); + const { current_point } = poolDetail; - const history = useHistory(); - useEffect(() => { - get_pool_related_farms(); - }, []); - useEffect(() => { - if (poolDetail) { - judge_is_in_range(); - } - }, [poolDetail]); - useEffect(() => { - if (tokenMetadata_x_y && poolDetail && tokenPriceList) { - const { current_point } = poolDetail; - get_your_liquidity(current_point); - } - }, [poolDetail, tokenMetadata_x_y, tokenPriceList]); - useEffect(() => {}, []); - useEffect(() => { - const info = get_detail_the_liquidity_refer_to_seed({ - liquidity, - all_seeds, - is_in_farming, - related_farms, - tokenPriceList, - }); - set_related_seed_info(info); - }, [liquidity, all_seeds, is_in_farming, tokenPriceList, related_farms]); - async function get_pool_related_farms() { - const is_in_farming = - liquidity.part_farm_ratio && +liquidity.part_farm_ratio > 0; - if (is_in_farming) { - const id = liquidity.mft_id.slice(1); - const seed_id = REF_UNI_V3_SWAP_CONTRACT_ID + '@' + id; - const farmList = await list_seed_farms(seed_id); - set_related_farms(farmList); - } - set_is_in_farming(is_in_farming); - } - async function judge_is_in_range() { - if (poolDetail) { - const { current_point } = poolDetail; - if (current_point >= left_point && right_point > current_point) { - setIsInrange(true); - } else { - setIsInrange(false); - } - } - } - function getRate(direction: string) { - let value = ''; - if (tokenMetadata_x_y) { - const [tokenX, tokenY] = tokenMetadata_x_y; - const decimalRate = - Math.pow(10, tokenX.decimals) / Math.pow(10, tokenY.decimals); - if (direction == 'left') { - value = getPriceByPoint(left_point, decimalRate); - } else if (direction == 'right') { - value = getPriceByPoint(right_point, decimalRate); - } - if (rate_need_to_reverse_display && +value !== 0) { - value = new BigNumber(1).dividedBy(value).toFixed(); - } - } - return displayNumberToAppropriateDecimals(value); - } - function getLpt_id() { - return lpt_id.split('#')[1]; + if (left_point > farmRangeRight || right_point < farmRangeLeft) return '0'; + + return get_your_liquidity( + current_point, + left_point >= farmRangeLeft ? left_point : farmRangeLeft, + right_point <= farmRangeRight ? right_point : farmRangeRight + ); } - function get_your_liquidity(current_point: number) { + function get_your_liquidity( + current_point: number, + left_point: number, + right_point: number + ) { const [tokenX, tokenY] = tokenMetadata_x_y; const priceX = tokenPriceList[tokenX.id]?.price || 0; const priceY = tokenPriceList[tokenY.id]?.price || 0; @@ -413,7 +462,8 @@ function UserLiquidityLine({ const tokenXTotalPrice = new BigNumber(tokenXAmount).multipliedBy(priceX); total_price = tokenXTotalPrice.toFixed(); } - setYour_liquidity(formatWithCommas(toPrecision(total_price, 2))); + + return total_price; } function getY( leftPoint: number, @@ -473,36 +523,6 @@ function UserLiquidityLine({ ); return { amountx: amountX_read, amounty: amountY_read }; } - function claimRewards(e: any) { - e.stopPropagation(); - if (!canClaim()) return; - setClaimLoading(true); - const [tokenX, tokenY] = tokenMetadata_x_y; - remove_liquidity({ - token_x: tokenX, - token_y: tokenY, - lpt_id, - amount: '0', - mft_id: '', - min_amount_x: '0', - min_amount_y: '0', - }); - } - function goYourLiquidityDetailPage(goType?: string) { - const pool_id = lpt_id.split('#')[0]; - const lptId = lpt_id.split('#')[1]; - const pool_name = get_pool_name(pool_id); - const link = `${pool_name}@${lptId}`; - if (goType == 'new window') { - openUrl(`/yoursLiquidityDetailV2/${link}`); - } else { - history.push(`/yoursLiquidityDetailV2/${link}`); - } - } - function goPoolDetailPage() { - const params_str = get_pool_name(liquidity.pool_id); - openUrl(`/poolV2/${params_str}`); - } function getTokenFeeAmount(p: string) { if (liquidityDetail && tokenMetadata_x_y && tokenPriceList) { const [tokenX, tokenY] = tokenMetadata_x_y; @@ -516,21 +536,9 @@ function UserLiquidityLine({ unclaimed_fee_y || '0' ); if (p == 'l') { - if (new BigNumber(fee_x_amount).isEqualTo('0')) { - return 0; - } else if (new BigNumber(fee_x_amount).isLessThan('0.001')) { - return '<0.001'; - } else { - return toPrecision(fee_x_amount, 3); - } + return fee_x_amount; } else if (p == 'r') { - if (new BigNumber(fee_y_amount).isEqualTo('0')) { - return 0; - } else if (new BigNumber(fee_y_amount).isLessThan('0.001')) { - return '<0.001'; - } else { - return toPrecision(fee_y_amount, 3); - } + return fee_y_amount; } else if (p == 'p') { const tokenxSinglePrice = tokenPriceList[tokenX.id]?.price || '0'; const tokenySinglePrice = tokenPriceList[tokenY.id]?.price || '0'; @@ -541,50 +549,27 @@ function UserLiquidityLine({ tokenySinglePrice ); const totalPrice = priceX.plus(priceY); - if (totalPrice.isEqualTo('0')) { - return $0; - } else if (totalPrice.isLessThan('0.01')) { - return '<$0.001'; - } else { - return '$' + toPrecision(totalPrice.toFixed(), 2); - } - } - } - } - function canClaim() { - if (liquidityDetail) { - const { unclaimed_fee_x, unclaimed_fee_y } = liquidityDetail; - if (+unclaimed_fee_x > 0 || +unclaimed_fee_y > 0) return true; - } - return false; - } - function getRateMapTokens() { - if (tokenMetadata_x_y) { - const [tokenX, tokenY] = tokenMetadata_x_y; - if (rate_need_to_reverse_display) { - return `${tokenX.symbol}/${tokenY.symbol}`; - } else { - return `${tokenY.symbol}/${tokenX.symbol}`; + + return scientificNotationToString(totalPrice.toString()); } } } - function mobile_ReferenceToken(direction: string) { + function getRate(direction: string) { + let value = ''; if (tokenMetadata_x_y) { const [tokenX, tokenY] = tokenMetadata_x_y; + const decimalRate = + Math.pow(10, tokenX.decimals) / Math.pow(10, tokenY.decimals); if (direction == 'left') { - if (rate_need_to_reverse_display) { - return tokenY.symbol; - } else { - return tokenX.symbol; - } + value = getPriceByPoint(left_point, decimalRate); } else if (direction == 'right') { - if (rate_need_to_reverse_display) { - return tokenX.symbol; - } else { - return tokenY.symbol; - } + value = getPriceByPoint(right_point, decimalRate); + } + if (rate_need_to_reverse_display && +value !== 0) { + value = new BigNumber(1).dividedBy(value).toFixed(); } } + return value; } function go_farm() { const [fixRange, pool_id, left_point, right_point] = @@ -603,402 +588,1128 @@ function UserLiquidityLine({ } openUrl(url); } - const { - Icon: Liquidity_icon, - your_apr: liquidity_your_apr, - link: liquidity_link, - inRange: liquidity_inRange, - status: liquidity_staked_farm_status, - } = related_seed_info; - return ( - <> - - {styleType == '1' ? ( - - ) : ( - - )} - - - ); + function getRateMapTokens() { + if (tokenMetadata_x_y) { + const [tokenX, tokenY] = tokenMetadata_x_y; + if (rate_need_to_reverse_display) { + return `${tokenX.symbol}/${tokenY.symbol}`; + } else { + return `${tokenY.symbol}/${tokenX.symbol}`; + } + } + } + const { lpt_id, pool_id, left_point, right_point, amount: L } = liquidity; + const [token_x, token_y, fee] = pool_id.split('|'); + const tokenMetadata_x_y = liquidities_tokens_metas + ? [liquidities_tokens_metas[token_x], liquidities_tokens_metas[token_y]] + : null; + let rate_need_to_reverse_display: boolean; + + if (tokenMetadata_x_y) { + const [tokenX] = tokenMetadata_x_y; + if (TOKEN_LIST_FOR_RATE.indexOf(tokenX.symbol) > -1) + rate_need_to_reverse_display = true; + else rate_need_to_reverse_display = false; + } + // the value of the liquidity; + let your_liquidity: any; + if (tokenMetadata_x_y && poolDetail && tokenPriceList) { + const { current_point } = poolDetail; + your_liquidity = get_your_liquidity(current_point, left_point, right_point); + } + const is_in_farming = + liquidity.part_farm_ratio && +liquidity.part_farm_ratio > 0; + let related_farms: any; + if (is_in_farming) { + const id = liquidity.mft_id.slice(1); + const seed_id = REF_UNI_V3_SWAP_CONTRACT_ID + '@' + id; + related_farms = await list_seed_farms(seed_id); + } + const related_seed_info = get_detail_the_liquidity_refer_to_seed({ + liquidity, + all_seeds, + is_in_farming, + related_farms, + tokenPriceList, + }); + const tokenFeeLeft = getTokenFeeAmount('l'); + const tokenFeeRight = getTokenFeeAmount('r'); + const tokenFeeValue = getTokenFeeAmount('p'); + const rangeMin = getRate(rate_need_to_reverse_display ? 'right' : 'left'); + const rangeMax = getRate(rate_need_to_reverse_display ? 'left' : 'right'); + const ratedMapTokens = getRateMapTokens(); + const your_liquidity_farm = get_your_liquidity_in_farm_range(); + return { + fee, + your_liquidity, + your_liquidity_farm, + related_seed_info, + go_farm, + tokenFeeLeft: tokenFeeLeft || '0', + tokenFeeRight: tokenFeeRight || '0', + rate_need_to_reverse_display, + related_farms, + is_in_farming, + liquidity, + pool_id, + rangeMax, + rangeMin, + ratedMapTokens, + tokenMetadata_x_y, + liquidityDetail, + tokenFeeValue, + }; } +export function findRangeIntersection(arr: number[][]) { + if (!Array.isArray(arr) || arr.length === 0) { + return []; + } + + arr.sort((a, b) => a[0] - b[0]); + + let intersection = [arr[0]]; + + for (let i = 1; i < arr.length; i++) { + const current = arr[i]; + const [currentStart, currentEnd] = current; + const [prevStart, prevEnd] = intersection[intersection.length - 1]; + + if (currentStart > prevEnd) { + intersection.push(current); + } else { + const start = Math.min(currentStart, prevStart); + const end = Math.max(currentEnd, prevEnd); + intersection[intersection.length - 1] = [start, end]; + } + } -function UserLiquidityLineStyle1() { + return intersection; +} +const GroupData = createContext(null); +function UserLiquidityLineStyleGroup({ + groupYourLiquidityList, + liquidities_list, + tokenPriceList, + all_seeds, + styleType, +}: { + groupYourLiquidityList: any[]; + liquidities_list: UserLiquidityInfo[]; + tokenPriceList: any; + all_seeds: Seed[]; + styleType: string; +}) { + const publicData = groupYourLiquidityList[0]; const { - hover, - setHover, - getLpt_id, - goYourLiquidityDetailPage, tokenMetadata_x_y, fee, - Liquidity_icon, - liquidity_link, - is_in_farming, - liquidity_inRange, - getRate, - rate_need_to_reverse_display, - getRateMapTokens, - isInrange, - liquidity_your_apr, - liquidity_staked_farm_status, - your_liquidity, - setShowAddBox, - setShowRemoveBox, - getTokenFeeAmount, - canClaim, - go_farm, - mobile_ReferenceToken, - claimRewards, - claimLoading, - showRemoveBox, + pool_id, + related_seed_info, + ratedMapTokens, + related_farms, poolDetail, - tokenPriceList, - liquidityDetail, - showAddBox, - } = useContext(LiquidityContext); + go_farm, + } = publicData; + const [hover, setHover] = useState(false); + const [showRemoveBox, setShowRemoveBox] = useState(false); + const [accountAPR, setAccountAPR] = useState(''); + const [claim_loading, set_claim_loading] = useState(false); + + // new + const [farm_icon, set_farm_icon] = useState<'single' | 'muti'>(); + const [tip_seed, set_tip_seed] = useState(); + const [joined_seeds, set_joined_seeds] = useState(); + const [joined_seeds_done, set_joined_seeds_done] = useState(false); + + const [removeButtonTip, setRemoveButtonTip] = useState(false); + const tokens = sort_tokens_by_base(tokenMetadata_x_y); - return ( -
setHover(true)} - onMouseLeave={() => setHover(false)} - > - {/* for PC */} -
-
- - - NFT ID #{getLpt_id()} - -
-
-
-
-
-
- - -
- - {tokens[0]?.['symbol']}-{tokens[1]?.['symbol']} - -
- - - - {+fee / 10000}% -
- {Liquidity_icon ? ( -
{ - e.stopPropagation(); - if (liquidity_link) { - openUrl(liquidity_link); - } - }} - className={`flex items-center justify-center border border-greenColor rounded-lg px-1 ml-2 ${ - liquidity_link ? 'cursor-pointer' : '' - } ${ - is_in_farming || liquidity_inRange ? '' : 'opacity-40' - }`} - > - - - {' '} - -
- ) : null} -
-
- - - - - {getRate(rate_need_to_reverse_display ? 'right' : 'left')} - - - - - - - {getRate(rate_need_to_reverse_display ? 'left' : 'right')} - - - {getRateMapTokens()} - -
- - - {isInrange ? ( - - ) : ( - - )} - + const { accountId } = useWalletSelector(); + const history = useHistory(); + useEffect(() => { + if ( + poolDetail && + tokenPriceList && + tokenMetadata_x_y && + liquidities_list.length + ) { + get_24_apr(); + } + }, [poolDetail, tokenPriceList, tokenMetadata_x_y, liquidities_list]); + useEffect(() => { + if ( + all_seeds.length && + groupYourLiquidityList.length && + liquidities_list.length + ) { + // get all seeds of the dcl pool + const [activeSeeds, endedSeeds, matchedSeeds] = + get_matched_all_seeds_of_a_dcl_pool({ + seeds: all_seeds, + pool_id, + }); + // farm icon + if (activeSeeds.length) { + const muti = activeSeeds.find((seed: Seed) => { + return seed.farmList.length > 1; + }); + if (muti) { + set_farm_icon('muti'); + } else { + set_farm_icon('single'); + } + } + // tip + const latest_seed = activeSeeds[0]; + if (latest_seed) { + const exist = groupYourLiquidityList.find((l: any) => { + const liquidity: UserLiquidityInfo = l.liquidity; + const { part_farm_ratio } = liquidity; + const is_in_farming = part_farm_ratio && +part_farm_ratio > 0; + if (!is_in_farming) { + if (whether_liquidity_can_farm_in_seed(liquidity, latest_seed)) { + return true; + } + } + }); + if (exist) { + const seed_apr = getSeedApr(latest_seed); + set_tip_seed({ + seed: latest_seed, + seed_apr: seed_apr == 0 ? '-' : formatPercentage(seed_apr), + go_farm_url_link: get_go_seed_link_url(latest_seed), + }); + } + } + // get all seed that user had joined + let joined_seeds: IUserJoinedSeed; + const in_farming_liquidities = groupYourLiquidityList.filter((l: any) => { + return l.is_in_farming; + }); + in_farming_liquidities.forEach((l: any) => { + const [fixRange_l, pool_id_l, left_point_l, right_point_l] = + l.liquidity.mft_id.split('&'); + const targetSeed = matchedSeeds.find((seed: Seed) => { + const [contractId, mft_id] = seed.seed_id.split('@'); + if (contractId == REF_UNI_V3_SWAP_CONTRACT_ID) { + const [ + fixRange, + pool_id_from_seed, + left_point_seed, + right_point_seed, + ] = mft_id.split('&'); + return ( + left_point_l == left_point_seed && + right_point_l == right_point_seed + ); + } + }); + if (targetSeed) { + if (!joined_seeds) { + joined_seeds = { + [targetSeed.seed_id]: { + seed: targetSeed, + liquidities: [l.liquidity], + }, + }; + } else if (!joined_seeds[targetSeed.seed_id]) { + joined_seeds[targetSeed.seed_id] = { + seed: targetSeed, + liquidities: [l.liquidity], + }; + } else { + joined_seeds[targetSeed.seed_id].liquidities.push(l.liquidity); + } + } + }); + + // get farm apr and value of user's investment + joined_seeds && + Object.values(joined_seeds).forEach( + (joined_seed_info: IUserJoinedSeedDetail) => { + const { seed, liquidities } = joined_seed_info; + joined_seed_info.seed_apr = formatPercentage(getSeedApr(seed)); + joined_seed_info.value_of_investment = formatWithCommas_usd( + getTotalValueInFarmsOfLiquidities(seed, liquidities) + ); + if (seed.farmList[0].status == 'Ended') { + joined_seed_info.seed_status = 'ended'; + joined_seed_info.seed_status_num = 3; + } else { + if (latest_seed.seed_id == seed.seed_id) { + joined_seed_info.seed_status = 'run'; + joined_seed_info.seed_status_num = 1; + } else { + joined_seed_info.seed_status = 'would_ended'; + joined_seed_info.seed_status_num = 2; + } + } + joined_seed_info.go_farm_url_link = get_go_seed_link_url(seed); + } + ); + set_joined_seeds(joined_seeds); + set_joined_seeds_done(true); + } + }, [groupYourLiquidityList, liquidities_list, tokenPriceList, all_seeds]); + + function get_go_seed_link_url(seed: Seed) { + const [fixRange, dcl_pool_id, left_point_seed, right_point_seed] = + seed.seed_id.split('@')[1].split('&'); + const link_params = `${get_pool_name( + dcl_pool_id + )}[${left_point_seed}-${right_point_seed}]`; + if (seed.farmList[0].status == 'Ended') { + return `/v2farms/${link_params}-e`; + } else { + return `/v2farms/${link_params}-r`; + } + } + function getSeedApr(seed: Seed) { + const farms = seed.farmList; + let apr = 0; + const allPendingFarms = isPending(seed); + farms.forEach(function (item: FarmBoost) { + const pendingFarm = item.status == 'Created' || item.status == 'Pending'; + if (allPendingFarms || (!allPendingFarms && !pendingFarm)) { + apr = +new BigNumber(apr).plus(item.apr).toFixed(); + } + }); + return apr * 100; + } + function getTotalValueInFarmsOfLiquidities( + seed: Seed, + liquidities: UserLiquidityInfo[] + ) { + let total_value = Big(0); + liquidities.forEach((l: UserLiquidityInfo) => { + let v = get_range_part_value(l, seed); + if (Big(l.unfarm_part_amount || 0).gt(0)) { + v = Big(l.part_farm_ratio).div(100).mul(v).toFixed(); + } + total_value = total_value.plus(v || 0); + }); + return total_value.toFixed(); + } + async function get_24_apr() { + let apr_24 = ''; + const dcl_fee_result: IDCLAccountFee | any = await getDCLAccountFee({ + pool_id, + account_id: accountId, + }); + if (dcl_fee_result) { + // 24h profit + poolDetail.token_x_metadata = tokenMetadata_x_y[0]; + poolDetail.token_y_metadata = tokenMetadata_x_y[1]; + // total unClaimed fee + const [unClaimed_tvl_fee] = get_unClaimed_fee_data( + liquidities_list, + poolDetail, + tokenPriceList + ); + apr_24 = get_account_24_apr( + unClaimed_tvl_fee, + dcl_fee_result, + poolDetail, + tokenPriceList + ); + } + setAccountAPR(formatPercentage(apr_24)); + } + + const groupList = () => { + const [total_x, total_y] = get_token_amount_in_user_liquidities({ + user_liquidities: liquidities_list, + pool: poolDetail, + token_x_metadata: tokenMetadata_x_y[0], + token_y_metadata: tokenMetadata_x_y[1], + }); + + const price_x = tokenPriceList?.[tokenMetadata_x_y[0].id]?.price || 0; + const price_y = tokenPriceList?.[tokenMetadata_x_y[1].id]?.price || 0; + + const total_x_value = Big(price_x).mul(total_x); + const total_y_value = Big(price_y).mul(total_y); + + const your_liquidity = total_x_value.plus(total_y_value); + + const tokenFeeLeft = groupYourLiquidityList.reduce((prev, cur) => { + return new Big(prev || '0').plus(new Big(cur.tokenFeeLeft || '0')); + }, new Big(0)); + + const tokenFeeRight = groupYourLiquidityList.reduce((prev, cur) => { + return new Big(prev || '0').plus(new Big(cur.tokenFeeRight || '0')); + }, new Big(0)); + + const tokenFeeValue = groupYourLiquidityList.reduce((prev, cur) => { + return new Big(prev || '0').plus(new Big(cur.tokenFeeValue || '0')); + }, new Big(0)); + + const rangeList = groupYourLiquidityList.map((g) => [ + Number(g.rangeMin), + Number(g.rangeMax), + ]); + + const pointRangeList = groupYourLiquidityList.map((g) => [ + g.liquidity.left_point, + g.liquidity.right_point, + ]); + + const is_in_farming = groupYourLiquidityList.reduce( + (pre, cur) => !!pre || !!cur.is_in_farming, + false + ); + + const intersectionRangeList = findRangeIntersection(rangeList); + const intersectionPointRangeList = findRangeIntersection(pointRangeList); + + const current_point = poolDetail.current_point; + + const isInRange = intersectionPointRangeList.some( + (range) => current_point >= range[0] && current_point <= range[1] + ); + + const canClaim = tokenFeeLeft.plus(tokenFeeRight).gt(0); + + // on farm not on myself + const farmApr: Big = !publicData.related_seed_info.your_apr + ? '' + : groupYourLiquidityList.reduce((prev: any, cur: any) => { + return new Big(prev || '0').plus( + new Big(cur.related_seed_info.your_apr_raw || '0') + ); + }, new Big(0)); + + let farmRangeLeft; + let farmRangeRight; + + if (related_seed_info.targetSeed) { + const seed_info = related_seed_info.targetSeed.seed_id.split('&'); + farmRangeLeft = Number(seed_info[2]); + farmRangeRight = Number(seed_info[3]); + } + + const totalLiquidityAmount = groupYourLiquidityList.reduce( + (pre, cur) => { + const { mft_id } = cur.liquidity; + + const { your_liquidity_farm } = cur; + + return { + total: pre.total.plus(new Big(your_liquidity_farm)), + in_farm: mft_id + ? pre.in_farm.plus(new Big(your_liquidity_farm)) + : pre.in_farm, + }; + }, + { + total: new Big(0), + in_farm: new Big(0), + } + ); + + return { + ...publicData, + your_liquidity: formatWithCommas_usd(your_liquidity.toFixed()), + tokenFeeLeft: formatNumber(tokenFeeLeft.toFixed()), + tokenFeeRight: formatNumber(tokenFeeRight.toFixed()), + tokenFeeValue: formatWithCommas_usd_down(tokenFeeValue.toFixed()), + intersectionRangeList: intersectionRangeList.map((range) => [ + new Big(range[0]).toFixed(), + new Big(range[1]).toFixed(), + ]), + is_in_farming, + intersectionPointRangeList, + isInRange, + canClaim, + farmApr: !farmApr + ? '' + : farmApr.eq(new Big(0)) + ? '0%' + : farmApr.lt(0.01) + ? '<0.01%' + : farmApr.toFixed(2, 0) + '%', + in_farm_percent: totalLiquidityAmount.total.eq(0) + ? '0%' + : toPrecision( + scientificNotationToString( + totalLiquidityAmount.in_farm + .div(totalLiquidityAmount.total) + .times(100) + .toString() + ), + 2 + ) + '%', + }; + }; + + const groupedData = groupList(); + + const { + your_liquidity, + tokenFeeLeft, + tokenFeeRight, + tokenFeeValue, + intersectionRangeList, + isInRange, + canClaim, + } = groupedData; + + function goDetailV2() { + const url_pool_id = get_pool_name(pool_id); + history.push(`/poolV2/${url_pool_id}`); + } + function claimRewards() { + if (!canClaim) return; + + set_claim_loading(true); + const lpt_ids: string[] = []; + groupYourLiquidityList + .map((g) => g.liquidityDetail) + .forEach((liquidity: UserLiquidityInfo) => { + const { unclaimed_fee_x, unclaimed_fee_y } = liquidity; + if (+unclaimed_fee_x > 0 || +unclaimed_fee_y > 0) { + lpt_ids.push(liquidity.lpt_id); + } + }); + claim_all_liquidity_fee({ + token_x: tokenMetadata_x_y[0], + token_y: tokenMetadata_x_y[1], + lpt_ids, + }); + } + function isPending(seed: Seed) { + let pending: boolean = true; + const farms = seed.farmList; + for (let i = 0; i < farms.length; i++) { + if (farms[i].status != 'Created' && farms[i].status != 'Pending') { + pending = false; + break; + } + } + return pending; + } + function get_range_part_value(liquidity: UserLiquidityInfo, seed: Seed) { + const [left_point, right_point] = get_valid_range(liquidity, seed.seed_id); + const v = get_liquidity_value_of_range( + liquidity, + seed, + left_point, + right_point + ); + return v; + } + function get_liquidity_value_of_range( + liquidity: UserLiquidityInfo, + seed: Seed, + leftPoint: number, + rightPoint: number + ) { + const { amount } = liquidity; + const poolDetail = seed.pool; + const { token_x, token_y } = poolDetail; + const v = get_total_value_by_liquidity_amount_dcl({ + left_point: leftPoint || liquidity.left_point, + right_point: rightPoint || liquidity.right_point, + poolDetail, + amount, + price_x_y: { + [token_x]: tokenPriceList[token_x]?.price || '0', + [token_y]: tokenPriceList[token_y]?.price || '0', + }, + metadata_x_y: { + [token_x]: tokenMetadata_x_y[0], + [token_y]: tokenMetadata_x_y[1], + }, + }); + return v; + } + function sort_joined_seeds( + b: IUserJoinedSeedDetail, + a: IUserJoinedSeedDetail + ) { + return b.seed_status_num - a.seed_status_num; + } + return ( + + {styleType == '2' ? ( + + ) : ( + + )} + + ); +} +function UserLiquidityLineStyleGroupStyle1() { + if (is_mobile) { + return ( + + ); + } else { + return ( + + ); + } +} +function UserLiquidityLineStyleGroupStyle1Mobile() { + const { + hover, + setHover, + tip_seed, + goDetailV2, + tokens, + fee, + farm_icon, + intersectionRangeList, + ratedMapTokens, + isInRange, + accountAPR, + joined_seeds, + sort_joined_seeds, + your_liquidity, + tokenMetadata_x_y, + tokenFeeLeft, + tokenFeeRight, + canClaim, + claimRewards, + claim_loading, + history, + joined_seeds_done, + setRemoveButtonTip, + setShowRemoveBox, + removeButtonTip, + showRemoveBox, + liquidities_list, + poolDetail, + tokenPriceList, + } = useContext(GroupData); + return ( + <> +
{ + goDetailV2(); + }} + > + {/* head */} +
+
+ + +
+
+
+ + {tokens[0]?.['symbol']}-{tokens[1]?.['symbol']} + + + {isInRange ? ( + + ) : ( + + )} + +
+ +
+
+ DCL +
+
+ + + + {+fee / 10000}% +
+ {farm_icon ? ( +
+
+ ) : null} +
+
+
+ {/* body */} +
+
+ + Price Range + +
+
+ {intersectionRangeList.map((range: string[], i: number) => { + return ( +
+ + {displayNumberToAppropriateDecimals(range[0])} + + - + + + {displayNumberToAppropriateDecimals(range[1])} + + {intersectionRangeList.length > 1 && + i < intersectionRangeList.length - 1 && ( + , + )} +
+ ); + })}
+ + {ratedMapTokens} +
-
- {liquidity_your_apr && - (!is_in_farming || liquidity_staked_farm_status == 'end') ? ( +
+ + APR(24h) + +
+ {accountAPR || '-'} + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { seed_apr, seed_status } = joined_seed_info; + if (seed_status == 'ended') return null; + if (length == 1) { + return ( +
+ + + + {seed_apr} +
+ ); + } else { + return ( +
+ + ({seed_status == 'run' ? 'new' : 'pre.'}) APR + + {seed_apr} +
+ ); + } + })} +
+ ) : tip_seed ? ( +
+ + + + 0% +
+ ) : null} +
+
+
+ + + +
+ + + {tokenFeeLeft || '-'} + + + + {tokenFeeRight || '-'} + +
+
+
+ {/* foot */} +
+
+
+ Your Liquidity +
+ + {your_liquidity} + + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { + seed_status, + value_of_investment, + go_farm_url_link, + } = joined_seed_info; + if (length == 1) { + return ( + + ); + } else { + return ( + + ); + } + })} +
+ ) : tip_seed ? ( + + ) : null} +
+
+
{ + e.stopPropagation(); + claimRewards(); + }} > - - - {liquidity_staked_farm_status == 'end' ? ( - - ) : ( - - )}{' '} - {liquidity_your_apr} - -
{ - openUrl(liquidity_link); - }} - > - - {liquidity_staked_farm_status == 'end' ? ( - - ) : ( - - )} - - -
+ } + />
- ) : null} - -
-
- - - - - ${your_liquidity || '-'} - - { + { + e.stopPropagation(); + const pool_name = get_pool_name(poolDetail.pool_id); + history.push(`/addLiquidityV2#${pool_name}`); + }} + color="#fff" + minWidth="5rem" + borderRadius="8px" + className={`px-3 w-24 flex-grow h-8 text-center text-sm text-white focus:outline-none`} + > + + +
{ + if (!!joined_seeds && !is_mobile) { + setRemoveButtonTip(true); + } + }} + onMouseLeave={() => { + if (!!joined_seeds) { + setRemoveButtonTip(false); + } + }} + onClick={(e) => { + if (is_mobile) { e.stopPropagation(); - setShowAddBox(true); - }} - color="#fff" - minWidth="5rem" - disabled={is_in_farming} - borderRadius="8px" - btnClassName={is_in_farming ? 'cursor-not-allowed' : ''} - className={`px-3 h-8 text-center text-sm text-white gotham_bold focus:outline-none mr-2.5 ${ - is_in_farming ? 'opacity-40 ' : '' - }`} - > - - + setRemoveButtonTip(!removeButtonTip); + } + }} + > { e.stopPropagation(); setShowRemoveBox(true); }} rounded="rounded-lg" - disabled={is_in_farming} + disabled={!!joined_seeds} px="px-0" py="py-1" style={{ minWidth: '5rem' }} - className={`flex-grow gotham_bold text-sm text-greenColor h-8 ${ - is_in_farming ? 'opacity-40' : '' + className={`flex-grow w-24 text-sm text-greenColor h-8 ${ + !!joined_seeds ? 'opacity-40 pointer-events-none' : '' }`} > - {is_in_farming ? ( -
- -
{ - e.stopPropagation(); - go_farm(); - }} - > - - {liquidity_staked_farm_status == 'end' ? ( - - ) : ( - - )} - - -
-
- ) : null} +
+ You have liquidity in farm, please unstake from{' '} + { + localStorage.setItem('BOOST_FARM_TAB', 'yours'); + openUrl('/v2farms'); + }} + > + Your Farm + {' '} + first. +
-
- - - +
+
+ {/* tip */} + {tip_seed ? ( +
+ + Farm APR up to{' '} + {tip_seed.seed_apr} + +
{ + e.stopPropagation(); + openUrl(tip_seed.go_farm_url_link); + }} + > + + + + +
+
+ ) : null} +
+
+ {showRemoveBox ? ( + { + setShowRemoveBox(false); + }} + listLiquidities={liquidities_list} + tokenMetadata_x_y={tokenMetadata_x_y} + poolDetail={poolDetail} + tokenPriceList={tokenPriceList} + userLiquidity={list_liquidities[0]} + style={{ + overlay: { + backdropFilter: 'blur(15px)', + WebkitBackdropFilter: 'blur(15px)', + }, + content: { + outline: 'none', + transform: 'translate(-50%, -50%)', + }, + }} + > + ) : null} + + ); +} +function UserLiquidityLineStyleGroupStyle1Pc() { + const { + hover, + setHover, + tip_seed, + goDetailV2, + tokens, + fee, + farm_icon, + intersectionRangeList, + ratedMapTokens, + isInRange, + accountAPR, + joined_seeds, + sort_joined_seeds, + your_liquidity, + tokenMetadata_x_y, + tokenFeeLeft, + tokenFeeRight, + canClaim, + claimRewards, + claim_loading, + history, + joined_seeds_done, + setRemoveButtonTip, + setShowRemoveBox, + removeButtonTip, + showRemoveBox, + liquidities_list, + poolDetail, + tokenPriceList, + } = useContext(GroupData); + return ( +
setHover(true)} + onMouseLeave={() => setHover(false)} + > + {/* for PC */} +
+
+
{ + e.preventDefault(); + e.stopPropagation(); + goDetailV2(); + }} + > + {/* 1/3 */} +
+
- - {getTokenFeeAmount('l') || '-'} - - - {getTokenFeeAmount('r') || '-'} +
+
+ + {tokens[0]?.['symbol']}-{tokens[1]?.['symbol']} -
- } - /> + +
+
+ DCL +
+
+ + + + {+fee / 10000}% +
+ {farm_icon ? ( +
+ +
+ ) : null}
-
-
-
- {/* for Mobile */} -
-
-
-
- - - NFT ID #{getLpt_id()} - -
-
-
-
- - -
- - {tokens[0]?.['symbol']}-{tokens[1]?.['symbol']} + {/* 2/3 */} + {/* Price Range */} +
+
+ {intersectionRangeList.map((range: string[], i: number) => { + return ( +
+ + {displayNumberToAppropriateDecimals(range[0])} + + - + + + {displayNumberToAppropriateDecimals(range[1])} + + {intersectionRangeList.length > 1 && + i < intersectionRangeList.length - 1 && ( + , + )} +
+ ); + })} + + {ratedMapTokens} - {Liquidity_icon ? ( -
{ - e.stopPropagation(); - if (liquidity_link) { - openUrl(liquidity_link); - } - }} - className={`flex items-center justify-center border border-greenColor rounded-lg px-1 ml-2 ${ - is_in_farming || liquidity_inRange ? '' : 'opacity-40' - }`} - > - - - {' '} - -
- ) : null}
-
- +
- {isInrange ? ( + {isInRange ? ( ) : ( @@ -1006,386 +1717,653 @@ function UserLiquidityLineStyle1() {
-
-
-
- - - - {+fee / 10000}% + {/* APR(24h) */} +
+ {accountAPR || '-'} + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { seed_apr, seed_status } = joined_seed_info; + if (seed_status == 'ended') return null; + if (length == 1) { + return ( +
+ + + + {seed_apr} +
+ ); + } else { + return ( +
+ + ({seed_status == 'run' ? 'new' : 'pre.'}) APR + + {seed_apr} +
+ ); + } + })} +
+ ) : tip_seed ? ( +
+ + + + 0% +
+ ) : null}
-
- - (1{' '} - {mobile_ReferenceToken('left')}) - - - {getRate(rate_need_to_reverse_display ? 'right' : 'left')}  - {mobile_ReferenceToken('right')} - + {/* Your Liquidity */} +
+ {your_liquidity} + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { + seed_status, + value_of_investment, + go_farm_url_link, + } = joined_seed_info; + if (length == 1) { + return ( + + ); + } else { + return ( + + ); + } + })} +
+ ) : tip_seed ? ( + + ) : null}
-
- - (1{' '} - {mobile_ReferenceToken('left')}) - - - {getRate(rate_need_to_reverse_display ? 'left' : 'right')}  - {mobile_ReferenceToken('right')} - +
+ {/* tip */} + {tip_seed ? ( +
+
+ + + {' '} + {tip_seed.seed_apr} + +
{ + e.stopPropagation(); + openUrl(tip_seed.go_farm_url_link); + }} + > + + + + +
+
-
- - - -
+ ) : null} + +
+
+ {/* unclaimed fees */} +
+ + + - - {getTokenFeeAmount('l') || '-'} + + {tokenFeeLeft || '-'} - - {getTokenFeeAmount('r') || '-'} + + {tokenFeeRight || '-'} +
+ +
} />
+ { + e.stopPropagation(); + const pool_name = get_pool_name(poolDetail.pool_id); + history.push(`/addLiquidityV2#${pool_name}`); + }} + color="#fff" + minWidth="5rem" + borderRadius="8px" + className={`px-3 w-24 h-8 text-center text-sm text-white focus:outline-none mr-2.5`} + > + + +
{ + if (!!joined_seeds) { + setRemoveButtonTip(true); + } + }} + onMouseLeave={() => { + if (!!joined_seeds) { + setRemoveButtonTip(false); + } + }} + > + { + e.stopPropagation(); + setShowRemoveBox(true); + }} + rounded="rounded-lg" + disabled={!!joined_seeds} + px="px-0" + py="py-1" + style={{ minWidth: '5rem' }} + className={`flex-grow w-24 text-sm text-greenColor h-8 ${ + !!joined_seeds ? 'opacity-40 pointer-events-none' : '' + }`} + > + + +
+ You have liquidity in farm, please unstake from{' '} + { + localStorage.setItem('BOOST_FARM_TAB', 'yours'); + openUrl('/v2farms'); + }} + > + Your Farm + {' '} + first. +
+
-
-
- - +
+ {showRemoveBox ? ( + { + setShowRemoveBox(false); + }} + listLiquidities={liquidities_list} + tokenMetadata_x_y={tokenMetadata_x_y} + poolDetail={poolDetail} + tokenPriceList={tokenPriceList} + userLiquidity={list_liquidities[0]} + style={{ + overlay: { + backdropFilter: 'blur(15px)', + WebkitBackdropFilter: 'blur(15px)', + }, + content: { + outline: 'none', + transform: 'translate(-50%, -50%)', + }, + }} + > + ) : null} +
+ ); +} +function UserLiquidityLineStyleGroupStyle2() { + if (is_mobile) { + return ( + + ); + } else { + return ( + + ); + } +} +function UserLiquidityLineStyleGroupStyle2Mobile() { + const { + tip_seed, + tokens, + fee, + intersectionRangeList, + ratedMapTokens, + isInRange, + accountAPR, + joined_seeds, + sort_joined_seeds, + your_liquidity, + tokenMetadata_x_y, + tokenFeeLeft, + tokenFeeRight, + poolDetail, + tokenFeeValue, + } = useContext(GroupData); + const [switch_off, set_switch_off] = useState(true); + function goPoolDetailPage() { + const params_str = get_pool_name(poolDetail.pool_id); + openUrl(`/poolV2/${params_str}`); + } + return ( + <> +
+
+
+
+
+ + +
+ + {tokens[0]?.['symbol']}-{tokens[1]?.['symbol']} + +
+ + {your_liquidity || '-'} - ${your_liquidity || '-'} -
-
- { - e.stopPropagation(); - setShowAddBox(true); - }} - disabled={is_in_farming ? true : false} - color="#fff" - className={`w-1 flex-grow h-8 text-center text-sm text-white focus:outline-none mr-3 ${ - is_in_farming ? 'opacity-40' : '' - }`} - > - - - { - e.stopPropagation(); - setShowRemoveBox(true); - }} - disabled={is_in_farming ? true : false} - rounded="rounded-md" - px="px-0" - py="py-1" - className={`w-1 flex-grow text-sm text-greenColor h-8 ${ - is_in_farming ? 'opacity-40' : '' - }`} - > - -
- {is_in_farming ? ( -
- +
+
+ + {+fee / 10000}% + { - e.stopPropagation(); - go_farm(); + onClick={() => { + goPoolDetailPage(); }} + className="flex items-center justify-center text-xs text-v3SwapGray bg-selectTokenV3BgColor rounded-md px-1.5 cursor-pointer hover:text-white py-0.5 mr-1.5" > - {liquidity_staked_farm_status == 'end' ? ( - - ) : ( - - )} + {' '} + -
- ) : null} - {liquidity_your_apr && - (!is_in_farming || liquidity_staked_farm_status == 'end') ? ( -
-
-
- -
- -
{ - openUrl(liquidity_link); - }} - > - - - - -
-
-
-
- - {liquidity_your_apr} -
+
+
+ + + {tokenFeeValue} +
-
-
- - - + { + set_switch_off(!switch_off); + }} + switch_off={switch_off} + > +
+
+
+ +
+
+
+
+ + + +
+ + + {isInRange ? ( + + ) : ( + + )}
-
-
{ - openUrl(liquidity_link); - }} - > - - +
+
+ {intersectionRangeList.map((range: string[], i: number) => { + return ( +
+ + {displayNumberToAppropriateDecimals(range[0])} + + - + + + {displayNumberToAppropriateDecimals(range[1])} + + {intersectionRangeList.length > 1 && + i < intersectionRangeList.length - 1 && ,} +
+ ); + })} + + {ratedMapTokens} + +
+
+
+ APR(24h) +
+ {accountAPR || '-'} + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { seed_apr, seed_status } = joined_seed_info; + if (seed_status == 'ended') return null; + if (length == 1) { + return ( +
+ + + + {seed_apr} +
+ ); + } else { + return ( +
+ + ({seed_status == 'run' ? 'new' : 'pre.'}) APR + + {seed_apr} +
+ ); + } + })} +
+ ) : tip_seed ? ( +
+ + - + 0% +
+ ) : null} +
+
+ {joined_seeds || tip_seed ? ( +
+ + + + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { + seed_status, + value_of_investment, + go_farm_url_link, + } = joined_seed_info; + if (length == 1) { + return ( + + ); + } else { + return ( + + ); + } + })} +
+ ) : tip_seed ? ( + - , - - {liquidity_your_apr} -
+ ) : null} +
+ ) : null} + +
+ + + +
+ + + {tokenFeeLeft || '-'} + + + + {tokenFeeRight || '-'} +
- ) : null} +
- {showRemoveBox ? ( - { - setShowRemoveBox(false); - }} - tokenMetadata_x_y={tokenMetadata_x_y} - poolDetail={poolDetail} - tokenPriceList={tokenPriceList} - userLiquidity={liquidityDetail} - style={{ - overlay: { - backdropFilter: 'blur(15px)', - WebkitBackdropFilter: 'blur(15px)', - }, - content: { - outline: 'none', - transform: 'translate(-50%, -50%)', - }, - }} - > - ) : null} - { - setShowAddBox(false); - }} - tokenMetadata_x_y={tokenMetadata_x_y} - poolDetail={poolDetail} - tokenPriceList={tokenPriceList} - userLiquidity={liquidityDetail} - style={{ - overlay: { - backdropFilter: 'blur(15px)', - WebkitBackdropFilter: 'blur(15px)', - }, - content: { - outline: 'none', - transform: 'translate(-50%, -50%)', - }, - }} - > -
+ ); } -function UserLiquidityLineStyle2() { +function UserLiquidityLineStyleGroupStyle2Pc() { const { - getLpt_id, - goYourLiquidityDetailPage, - goPoolDetailPage, - tokenMetadata_x_y, + tip_seed, + tokens, fee, - liquidity_link, - is_in_farming, - getRate, - rate_need_to_reverse_display, - getRateMapTokens, - isInrange, + intersectionRangeList, + ratedMapTokens, + isInRange, + accountAPR, + joined_seeds, + sort_joined_seeds, your_liquidity, - getTokenFeeAmount, - go_farm, - } = useContext(LiquidityContext); + tokenMetadata_x_y, + tokenFeeLeft, + tokenFeeRight, + poolDetail, + tokenFeeValue, + } = useContext(GroupData); const [switch_off, set_switch_off] = useState(true); - - function getUsageDiv() { - let div; - if (is_in_farming) { - div = ( -
- {' '} - { - e.stopPropagation(); - go_farm(); - }} - className="text-primaryText hover:text-white cursor-pointer ml-1.5" - > -
- ); - } else if (liquidity_link) { - div = ( -
- - { - e.stopPropagation(); - openUrl(liquidity_link); - }} - className="text-primaryText hover:text-white cursor-pointer ml-1.5" - > -
- ); - } else { - div = ( - - - - ); - } - return div; + function goPoolDetailPage() { + const params_str = get_pool_name(poolDetail.pool_id); + openUrl(`/poolV2/${params_str}`); } return ( <> - {is_mobile ? ( - - ) : ( - - )} - - ); -} -function UserLiquidityLineStyle2Mobile({ - switch_off, - set_switch_off, - getUsageDiv, -}: { - switch_off: boolean; - set_switch_off: any; - getUsageDiv: any; -}) { - const { - getLpt_id, - goYourLiquidityDetailPage, - goPoolDetailPage, - tokenMetadata_x_y, - fee, - getRate, - rate_need_to_reverse_display, - getRateMapTokens, - isInrange, - your_liquidity, - getTokenFeeAmount, - } = useContext(LiquidityContext); - const tokens = sort_tokens_by_base(tokenMetadata_x_y); - return ( -
-
-
+
+
-
+
- + {tokens[0]?.['symbol']}-{tokens[1]?.['symbol']} -
- - ${your_liquidity || '-'} - -
-
-
- { - goYourLiquidityDetailPage('new window'); - }} - className="flex items-center bg-portfolioRainbowColor rounded-md gotham_bold text-xs text-white cursor-pointer px-1.5 py-0.5" - > - NFT #{getLpt_id()} + + {+fee / 10000}% { goPoolDetailPage(); }} - className="flex items-center justify-center text-xs text-v3SwapGray bg-selectTokenV3BgColor rounded-md cursor-pointer whitespace-nowrap py-0.5 px-1.5 ml-1.5" + className="flex items-center justify-center text-xs text-v3SwapGray bg-selectTokenV3BgColor rounded-md px-1.5 cursor-pointer hover:text-white py-0.5 mr-1.5" > - DCL + {' '} +
- - - {getTokenFeeAmount('p')} - +
+ + {your_liquidity || '-'} + +
+ + + {tokenFeeValue} + +
+
{ set_switch_off(!switch_off); @@ -1394,250 +2372,225 @@ function UserLiquidityLineStyle2Mobile({ >
-
- -
-
- - - - - ${your_liquidity || '-'} - -
-
-
- - - -
- - - {isInrange ? ( - - ) : ( - - )} +
+
+
+ + +
+
+ + + {isInRange ? ( + + ) : ( + + )} + +
+
+ {intersectionRangeList.map((range: string[], i: number) => { + return ( +
+ + {displayNumberToAppropriateDecimals(range[0])} + + - + + + {displayNumberToAppropriateDecimals(range[1])} + + {intersectionRangeList.length > 1 && + i < intersectionRangeList.length - 1 && ( + , + )} +
+ ); + })} + + {ratedMapTokens} + +
+
-
-
- - {getRate(rate_need_to_reverse_display ? 'right' : 'left')} -{' '} - {getRate(rate_need_to_reverse_display ? 'left' : 'right')} - - - {getRateMapTokens()} - -
-
-
- - - - {getUsageDiv()} -
-
- - - -
- - - {getTokenFeeAmount('l') || '-'} - - - - {getTokenFeeAmount('r') || '-'} - -
-
-
-
- ); -} -function UserLiquidityLineStyle2Pc({ - switch_off, - set_switch_off, - getUsageDiv, -}: { - switch_off: boolean; - set_switch_off: any; - getUsageDiv: any; -}) { - const { - getLpt_id, - goYourLiquidityDetailPage, - goPoolDetailPage, - tokenMetadata_x_y, - fee, - getRate, - rate_need_to_reverse_display, - getRateMapTokens, - isInrange, - your_liquidity, - getTokenFeeAmount, - } = useContext(LiquidityContext); - const tokens = sort_tokens_by_base(tokenMetadata_x_y); - return ( -
-
-
-
- - -
- - {tokens[0]?.['symbol']}-{tokens[1]?.['symbol']} - - - {+fee / 10000}% - - { - goPoolDetailPage(); - }} - className="flex items-center justify-center text-xs text-v3SwapGray bg-selectTokenV3BgColor rounded-md px-1.5 cursor-pointer hover:text-white py-0.5 mr-1.5" - > - {' '} - - - { - goYourLiquidityDetailPage('new window'); - }} - className="flex items-center bg-portfolioRainbowColor rounded-md gotham_bold text-xs text-white cursor-pointer px-1.5 py-0.5" - > - NFT #{getLpt_id()} - -
-
-
- - ${your_liquidity || '-'} - -
- - - {getTokenFeeAmount('p')} - +
+ APR(24h) +
+ {accountAPR || '-'} + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { seed_apr, seed_status } = joined_seed_info; + if (seed_status == 'ended') return null; + if (length == 1) { + return ( +
+ + + + {seed_apr} +
+ ); + } else { + return ( +
+ + ({seed_status == 'run' ? 'new' : 'pre.'}) APR + + {seed_apr} +
+ ); + } + })} +
+ ) : tip_seed ? ( +
+ + + + 0% +
+ ) : null} +
-
- { - set_switch_off(!switch_off); - }} - switch_off={switch_off} - > -
-
-
-
-
-
- - - - ${your_liquidity || '-'} -
-
- - - -
-
- - - {isInrange ? ( - - ) : ( - - )} + {joined_seeds || tip_seed ? ( +
+ + + {joined_seeds ? ( +
+ {Object.values(joined_seeds) + .sort(sort_joined_seeds) + .map((joined_seed_info: IUserJoinedSeedDetail) => { + const length = Object.values(joined_seeds).length; + const { + seed_status, + value_of_investment, + go_farm_url_link, + } = joined_seed_info; + if (length == 1) { + return ( + + ); + } else { + return ( + + ); + } + })} +
+ ) : tip_seed ? ( + + ) : null}
+ ) : null} + +
+ + +
- - {getRate(rate_need_to_reverse_display ? 'right' : 'left')} -{' '} - {getRate(rate_need_to_reverse_display ? 'left' : 'right')} + + + {tokenFeeLeft || '-'} + + + + {tokenFeeRight || '-'} - - {getRateMapTokens()} + + {tokenFeeValue}
-
- - - - {getUsageDiv()} -
-
- - - -
- - - {getTokenFeeAmount('l') || '-'} - - - - {getTokenFeeAmount('r') || '-'} - - - {getTokenFeeAmount('p')} - -
-
-
+ ); } +interface IUserJoinedSeed { + seed_id?: IUserJoinedSeedDetail; +} +interface IUserJoinedSeedDetail { + seed: Seed; + liquidities: UserLiquidityInfo[]; + seed_apr?: string; + value_of_investment?: string; + seed_status?: 'run' | 'would_ended' | 'ended'; + seed_status_num?: number; + go_farm_url_link?: string; +} +interface ILatestSeedTip { + seed: Seed; + seed_apr: string; + go_farm_url_link: string; +} diff --git a/src/components/pool/utils.ts b/src/components/pool/utils.ts new file mode 100644 index 000000000..9a28a058d --- /dev/null +++ b/src/components/pool/utils.ts @@ -0,0 +1,97 @@ +import Big from 'big.js'; +import { + formatWithCommas, + toInternationalCurrencySystem, +} from '../../utils/numbers'; +export const formatWithCommas_usd = (v: string | number) => { + if (isInvalid(v)) return '$-'; + const big = Big(v); + if (big.eq(0)) { + return '$0'; + } else if (big.lt(0.01)) { + return '<$0.01'; + } else if (big.lt(10000)) { + return '$' + formatWithCommas(big.toFixed(2, 1)); + } else { + return '$' + formatWithCommas(big.toFixed(0, 1)); + } +}; +export const formatWithCommas_usd_down = (v: string | number) => { + if (isInvalid(v)) return '$-'; + const big = Big(v); + if (big.eq(0)) { + return '$0'; + } else if (big.lt(0.01)) { + return '<$0.01'; + } else if (big.lt(10000)) { + return '$' + formatWithCommas(big.toFixed(2, 0)); + } else { + return '$' + formatWithCommas(big.toFixed(0, 0)); + } +}; + +export const formatPercentage = (v: string | number) => { + if (isInvalid(v)) return '-%'; + const big = Big(v); + if (big.eq(0)) { + return '0%'; + } else if (big.lt(0.01)) { + return '<0.01%'; + } else { + return big.toFixed(2, 1) + '%'; + } +}; +export const formatNumber = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.01'; + } else { + return big.toFixed(2, 1); + } +}; +export const formatWithCommas_number = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.01'; + } else { + return formatWithCommas(big.toFixed(2, 1)); + } +}; +export const formatPrice = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.0001)) { + return '<0.0001'; + } else { + return big.toFixed(4, 0); + } +}; +export const formatPriceWithCommas = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.0001)) { + return '<0.0001'; + } else { + const p = big.toFixed(4, 0); + const [whole, decimal] = p.split('.'); + return `${formatWithCommas(whole)}${decimal ? '.' + decimal : ''}`; + } +}; +export const formatToInternationalCurrencySystem$ = (v: string | number) => { + if (isInvalid(v)) return '$-'; + return '$' + toInternationalCurrencySystem(Big(v || 0).toFixed(), 2); +}; +export const isInvalid = function (v: any) { + if (v === '' || v === undefined || v === null) return true; + return false; +}; diff --git a/src/components/portfolio/Farms.tsx b/src/components/portfolio/Farms.tsx index e2ed2316a..e24b29aee 100644 --- a/src/components/portfolio/Farms.tsx +++ b/src/components/portfolio/Farms.tsx @@ -708,7 +708,8 @@ function DclFarmRowMobile() { switch_off ? 'hidden' : '' }`} > -
+ {/* border-b border-limitOrderFeeTiersBorderColor */} +
@@ -763,23 +764,6 @@ function DclFarmRowMobile() {
-
- {listLiquidities_inFarimg.length > 0 ? ( - <> - {listLiquidities_inFarimg.map((liquidity: UserLiquidityInfo) => { - return ( - - ); - })} - - ) : null} -
); @@ -845,7 +829,8 @@ function DclFarmRowPc() {
-
+ {/* border-b border-gray1 */} +
@@ -897,26 +882,6 @@ function DclFarmRowPc() {
-
-

- -

- {listLiquidities_inFarimg.length > 0 ? ( - <> - {listLiquidities_inFarimg.map((liquidity: UserLiquidityInfo) => { - return ( - - ); - })} - - ) : null} -
); diff --git a/src/components/portfolio/Orders.tsx b/src/components/portfolio/Orders.tsx index 0b503ecd9..81b9c6425 100644 --- a/src/components/portfolio/Orders.tsx +++ b/src/components/portfolio/Orders.tsx @@ -46,12 +46,10 @@ import { useTotalOrderData, getAccountId, } from './Tool'; -import { - WalletContext, - getCurrentWallet, -} from '../../utils/wallets-integration'; +import { WalletContext } from '../../utils/wallets-integration'; import getConfig from 'src/services/config'; import { isMobile } from 'src/utils/device'; +import { SWAP_MODE_KEY, SWAP_MODE } from '../../pages/SwapPage'; const is_mobile = isMobile(); const { explorerUrl } = getConfig(); @@ -205,7 +203,8 @@ function OrderCard({ .div(p) .toFixed(tokensMap[order.buy_token].decimals); - return scientificNotationToString(sell_amount); + // return scientificNotationToString(sell_amount); + return display_amount(sell_amount); }; function display_amount(amount: string) { if (new Big(amount).eq(0)) { @@ -437,7 +436,6 @@ function OrderCard({ displayPercents[1] == '100' ? display_amount(orderIn) : buyAmountToSellAmount(order.unclaimed_amount || '0', order, price); - const sellTokenAmount = (
@@ -1055,7 +1053,8 @@ function OrderCard({ { - openUrl('/myOrder'); + localStorage.setItem(SWAP_MODE_KEY, SWAP_MODE.LIMIT); + openUrl('/'); }} className="flex items-center justify-center text-xs text-v3SwapGray bg-selectTokenV3BgColor rounded-md px-1.5 cursor-pointer hover:text-white py-0.5" > @@ -1109,7 +1108,8 @@ function OrderCard({ > { - openUrl('/myOrder'); + localStorage.setItem(SWAP_MODE_KEY, SWAP_MODE.LIMIT); + openUrl('/'); }} className="flex items-center justify-center text-xs text-v3SwapGray relative -top-3 " > diff --git a/src/components/swap/SwapLimitOrderChart.tsx b/src/components/swap/SwapLimitOrderChart.tsx new file mode 100644 index 000000000..337aedad4 --- /dev/null +++ b/src/components/swap/SwapLimitOrderChart.tsx @@ -0,0 +1,1506 @@ +import React, { + useContext, + useEffect, + useMemo, + useState, + createContext, + useRef, +} from 'react'; +import { get_pointorder_range } from '../../services/swapV3'; +import { get_pool, PoolInfo } from '../../services/swapV3'; +import { ftGetTokenMetadata } from '../../services/ft-contract'; +import { getPriceByPoint, sort_tokens_by_base } from '../../services/commonV3'; +import SwapProTab from './SwapProTab'; +import { + toReadableNumber, + toInternationalCurrencySystem, + formatWithCommas, +} from '../../utils/numbers'; +import { toRealSymbol } from '../../utils/token'; +import { SwapProContext } from '../../pages/SwapPage'; +import Big from 'big.js'; +import * as d3 from 'd3'; +import { isMobile } from '../../utils/device'; +const LimitOrderChartData = createContext(null); +export default function SwapLimitOrderChart() { + // CONST start + const limitOrderContainerHeight = '150'; + // CONST end + const [pool, setPool] = useState(); + const [orders, setOrders] = useState(); + const [switch_token, set_switch_token] = useState(); + const [buy_token_x_list, set_buy_token_x_list] = + useState(); + const [sell_token_x_list, set_sell_token_x_list] = + useState(); + const [buy_token_y_list, set_buy_token_y_list] = + useState(); + const [sell_token_y_list, set_sell_token_y_list] = + useState(); + const [fetch_data_done, set_fetch_data_done] = useState(false); + const [buy_list, set_buy_list] = useState(); + const [sell_list, set_sell_list] = useState(); + const [market_loading, set_market_loading] = useState(false); + const [pair_is_reverse, set_pair_is_reverse] = useState(false); + const [show_view_all, set_show_view_all] = useState(false); + const { dcl_pool_id } = useContext(SwapProContext); + const GEARS = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1]; + const [zoom, setZoom] = useState(GEARS[0]); + const pool_id = dcl_pool_id; + const left_point = -800000; + const right_point = 800000; + const sellBoxRef = useRef(null); + const is_mobile = isMobile(); + useEffect(() => { + if (pool_id) { + set_fetch_data_done(false); + setZoom(GEARS[0]); + fetch_data(); + } + }, [pool_id]); + useEffect(() => { + if (pool_id && market_loading) { + refresh(); + } + }, [market_loading]); + useEffect(() => { + if (pool && orders && !market_loading) { + process_orders(); + set_fetch_data_done(true); + } + }, [pool, orders, market_loading]); + useEffect(() => { + if (switch_token == 'X' && buy_token_x_list && sell_token_x_list) { + set_buy_list(buy_token_x_list); + set_sell_list(sell_token_x_list); + } else if (switch_token == 'Y' && buy_token_y_list && sell_token_y_list) { + set_buy_list(buy_token_y_list); + set_sell_list(sell_token_y_list); + } + }, [ + switch_token, + buy_token_x_list, + sell_token_x_list, + buy_token_y_list, + sell_token_y_list, + ]); + useEffect(() => { + if (sellBoxRef.current && sell_list?.length) { + sellBoxRef.current.scrollTop = 10000; + } + }, [sellBoxRef, sell_list]); + useEffect(() => { + if (show_view_all && is_mobile) { + document.documentElement.style.overflow = 'hidden'; + } else { + document.documentElement.style.overflow = 'auto'; + } + }, [show_view_all]); + const [cur_pairs, cur_pairs_price_mode, cur_token_symbol, cur_pair_icons] = + useMemo(() => { + if (pool) { + const classStr = 'w-6 h-6 rounded-full border border-gradientFromHover'; + const { token_x_metadata, token_y_metadata } = pool; + const x_symbol = toRealSymbol(token_x_metadata.symbol); + const y_symbol = toRealSymbol(token_y_metadata.symbol); + if (switch_token == 'X') { + const y_icons = ( + <> + + + + ); + return [ + `${y_symbol}/${x_symbol}`, + `${x_symbol}-${y_symbol}`, + `${x_symbol}`, + y_icons, + ]; + } else if (switch_token == 'Y') { + const x_icons = ( + <> + + + + ); + return [ + `${x_symbol}/${y_symbol}`, + `${y_symbol}-${x_symbol}`, + `${y_symbol}`, + x_icons, + ]; + } + } + return []; + }, [switch_token, pool]); + async function refresh() { + await fetch_data(); + set_market_loading(false); + } + async function fetch_data() { + const orders = await get_points_of_orders(); + const [switch_token, is_reverse, p] = (await get_pool_detail()) as any; + set_switch_token(switch_token); + set_pair_is_reverse(is_reverse); + setPool(p); + setOrders(orders); + } + async function get_points_of_orders() { + const result = await get_pointorder_range({ + pool_id, + left_point, + right_point, + }); + return result; + } + async function get_pool_detail() { + const p: PoolInfo = await get_pool(pool_id); + const { token_x, token_y } = p; + p.token_x_metadata = await ftGetTokenMetadata(token_x); + p.token_y_metadata = await ftGetTokenMetadata(token_y); + const tokens = sort_tokens_by_base([ + p.token_x_metadata, + p.token_y_metadata, + ]); + if (tokens[0].id == p.token_x_metadata.id) { + return ['X', false, p]; + } else { + return ['Y', true, p]; + } + } + function process_orders() { + const list = Object.values(orders); + const sell_token_x_list: IOrderPointItem[] = []; + const sell_token_y_list: IOrderPointItem[] = []; + const buy_token_x_list: IOrderPointItem[] = []; + const buy_token_y_list: IOrderPointItem[] = []; + const list_x = list.filter((item: IOrderPointItem) => + Big(item.amount_x).gt(0) + ); + list_x.sort((b: IOrderPointItem, a: IOrderPointItem) => { + return b.point - a.point; + }); + const list_y = list + .filter((item: IOrderPointItem) => Big(item.amount_y).gt(0)) + .reverse(); + const { token_x_metadata, token_y_metadata } = pool; + list_y.sort((b: IOrderPointItem, a: IOrderPointItem) => { + return a.point - b.point; + }); + // accumulate + list_x.forEach((item: IOrderPointItem) => { + const { point, amount_x } = item; + const price_x_base = get_price_by_point(point); + const price_y_base = Big(price_x_base).eq(0) + ? '0' + : Big(1).div(price_x_base).toFixed(); + const sell_x_readable = toReadableNumber( + token_x_metadata.decimals, + amount_x + ); + const buy_y_readable = Big(price_x_base).mul(sell_x_readable).toFixed(); + const length_sell_token_x_list = sell_token_x_list.length; + const length_buy_token_y_list = sell_token_x_list.length; + sell_token_x_list.push({ + ...item, + price: price_x_base, + amount_x_readable: sell_x_readable, + accumulated_x_readable: + length_sell_token_x_list == 0 + ? sell_x_readable + : Big( + sell_token_x_list[length_sell_token_x_list - 1] + .accumulated_x_readable + ) + .plus(sell_x_readable) + .toFixed(), + }); + buy_token_y_list.push({ + ...item, + price: price_y_base, + amount_y_readable: buy_y_readable, + accumulated_y_readable: + length_buy_token_y_list == 0 + ? buy_y_readable + : Big( + buy_token_y_list[length_buy_token_y_list - 1] + .accumulated_y_readable + ) + .plus(buy_y_readable) + .toFixed(), + }); + }); + list_y.forEach((item: IOrderPointItem) => { + const { point, amount_y } = item; + const price_x_base = get_price_by_point(point); + const price_y_base = Big(price_x_base).eq(0) + ? '0' + : Big(1).div(price_x_base).toFixed(); + const sell_y_readable = toReadableNumber( + token_y_metadata.decimals, + amount_y + ); + const buy_x_readable = Big(price_y_base).mul(sell_y_readable).toFixed(); + const length_sell_token_y_list = sell_token_y_list.length; + const length_buy_token_x_list = buy_token_x_list.length; + sell_token_y_list.push({ + ...item, + price: price_y_base, + amount_y_readable: sell_y_readable, + accumulated_y_readable: + length_sell_token_y_list == 0 + ? sell_y_readable + : Big( + sell_token_y_list[length_sell_token_y_list - 1] + .accumulated_y_readable + ) + .plus(sell_y_readable) + .toFixed(), + }); + buy_token_x_list.push({ + ...item, + price: price_x_base, + amount_x_readable: buy_x_readable, + accumulated_x_readable: + length_buy_token_x_list == 0 + ? buy_x_readable + : Big( + buy_token_x_list[length_buy_token_x_list - 1] + .accumulated_x_readable + ) + .plus(buy_x_readable) + .toFixed(), + }); + }); + const sell_token_x_list_reverse = sell_token_x_list.reverse(); + const sell_token_y_list_reverse = sell_token_y_list.reverse(); + set_buy_token_x_list(buy_token_x_list); + set_sell_token_x_list(sell_token_x_list_reverse); + set_buy_token_y_list(buy_token_y_list); + set_sell_token_y_list(sell_token_y_list_reverse); + } + function get_price_by_point(point: number) { + const { token_x_metadata, token_y_metadata } = pool; + const decimalRate_price = + Math.pow(10, token_x_metadata.decimals) / + Math.pow(10, token_y_metadata.decimals); + return getPriceByPoint(point, decimalRate_price); + } + function get_rate_element() { + if (pool) { + const { current_point, token_x_metadata, token_y_metadata } = pool; + const current_price_x = get_price_by_point(current_point); + const current_price_y = Big(current_price_x).gt(0) + ? Big(1).div(current_price_x).toFixed() + : '0'; + return ( + <> + + 1{' '} + {switch_token == 'X' + ? token_x_metadata.symbol + : token_y_metadata.symbol}{' '} + = + + + {switch_token == 'X' + ? formatPriceWithCommas(current_price_x) + : formatPriceWithCommas(current_price_y)} + + + {switch_token == 'X' + ? token_y_metadata.symbol + : token_x_metadata.symbol} + + + ); + } + } + function get_rate_element_mobile() { + if (pool) { + const { current_point, token_x_metadata, token_y_metadata } = pool; + const current_price_x = get_price_by_point(current_point); + const current_price_y = Big(current_price_x).gt(0) + ? Big(1).div(current_price_x).toFixed() + : '0'; + return ( +
+ + {switch_token == 'X' + ? formatPriceWithCommas(current_price_x) + : formatPriceWithCommas(current_price_y)} + + + {switch_token == 'X' + ? token_y_metadata.symbol + '/' + token_x_metadata.symbol + : token_x_metadata.symbol + '/' + token_y_metadata.symbol} + +
+ ); + } + } + function marketRefresh() { + set_market_loading(true); + } + // 缩小坐标轴区间范围 + function zoomOut() { + if (is_empty) return; + const targetPercent = GEARS.find((item) => item < zoom); + if (targetPercent) { + setZoom(targetPercent); + } + } + // 放大坐标轴区间范围 + function zoomIn() { + if (is_empty) return; + const GEARSCOPY: number[] = JSON.parse(JSON.stringify(GEARS)).reverse(); + const targetPercent = GEARSCOPY.find((item) => item > zoom); + if (targetPercent) { + setZoom(targetPercent); + } + } + const is_empty = fetch_data_done && !sell_list?.length && !buy_list?.length; + return ( + +
+
+
{cur_pair_icons}
+ + {cur_pairs_price_mode} + +
+ +
+
+ {/* chart area */} +
+ {/* base data */} +
+
+ {get_rate_element()} +
+
+ {get_rate_element_mobile()} +
+
+
+
+
+ { + set_switch_token('X'); + }} + className={`flex items-center justify-center cursor-pointer rounded-md px-1.5 py-0.5 text-xs ${ + switch_token == 'X' + ? 'bg-proTabBgColor text-white' + : 'text-primaryText' + }`} + > + {pool?.token_x_metadata?.symbol} + + { + set_switch_token('Y'); + }} + className={`flex items-center justify-center cursor-pointer rounded-md px-1.5 py-0.5 text-xs ${ + switch_token == 'Y' + ? 'bg-proTabBgColor text-white' + : 'text-primaryText' + }`} + > + {pool?.token_y_metadata?.symbol} + +
+
+ {/* control button*/} +
+ {/*
+ +
*/} +
+ +
+
+ +
+ {/*
+ +
*/} +
+
+
{ + set_show_view_all(true); + }} + className="text-xs text-white px-2 py-1 border border-v3SwapGray border-opacity-20 rounded-md lg:hidden" + > + View All +
+
+
+ {/* chart */} + {is_empty ? ( +
+ + + Not enough data for the chart right now. + +
+ ) : ( + + )} +
+ {/* table area */} +
+ {is_mobile && show_view_all && ( +
{ + set_show_view_all(false); + }} + >
+ )} +
+
+
+ Limit Orders +
+
+
+ { + set_switch_token('X'); + }} + className={`flex items-center justify-center cursor-pointer rounded-md px-1.5 py-0.5 text-xs ${ + switch_token == 'X' + ? 'bg-proTabBgColor text-white' + : 'text-primaryText' + }`} + > + {pool?.token_x_metadata?.symbol} + + { + set_switch_token('Y'); + }} + className={`flex items-center justify-center cursor-pointer rounded-md px-1.5 py-0.5 text-xs ${ + switch_token == 'Y' + ? 'bg-proTabBgColor text-white' + : 'text-primaryText' + }`} + > + {pool?.token_y_metadata?.symbol} + +
+
+
+
+
+ Price + + {cur_pairs} + +
+
+ Qty + + {cur_token_symbol} + +
+
+ + Total Qty + + + {cur_token_symbol} + +
+
+ {is_empty ? ( +
+ No order yet +
+ ) : ( +
+
+ {sell_list?.map((item: IOrderPointItem, index) => { + return ( +
+ + {formatPriceWithCommas(item.price)} + + + {formatNumber( + item.amount_x_readable || item.amount_y_readable + )} + + + {formatNumber( + item.accumulated_x_readable || + item.accumulated_y_readable + )} + +
+ ); + })} +
+
+
+ + Market Pirce + + +
+ + Refresh Market Price + +
+
+ {buy_list?.map((item: IOrderPointItem, index) => { + return ( +
+ + {formatPriceWithCommas(item.price)} + + + {formatNumber( + item.amount_x_readable || item.amount_y_readable + )} + + + {formatNumber( + item.accumulated_x_readable || + item.accumulated_y_readable + )} + +
+ ); + })} +
+
+ )} +
+
+
+
+ ); +} +function OrderChart() { + const { + buy_list, + sell_list, + cur_pairs, + cur_token_symbol, + pool, + get_price_by_point, + switch_token, + zoom, + GEARS, + }: { + buy_list: IOrderPointItem[]; + sell_list: IOrderPointItem[]; + cur_pairs: string; + cur_token_symbol: string; + pool: PoolInfo; + get_price_by_point: Function; + switch_token: ISwitchToken; + zoom: number; + GEARS: number[]; + } = useContext(LimitOrderChartData); + + const [foucsOrderPoint, setFoucsOrderPoint] = useState(); + const [side, setSide] = useState(); + // CONST start + const svg_width = isMobile() ? 360 : 600; + const svg_height = 400; + const svg_padding = 40; + const axisRightWidth = 60; + const disFromHoverBoxToPointer = 20; + const is_mobile = isMobile(); + // CONST end + useEffect(() => { + if (sell_list?.length || buy_list?.length) { + drawChart(); + } else { + clearChart(); + } + }, [buy_list, sell_list, zoom]); + function drawChart() { + clearChart(); + const { price_range, amount_range, buy_list_new, sell_list_new } = + get_data_for_drawing(); + // 创建一个横坐标轴 + const scaleBottom = d3 + .scaleLinear() + .domain(price_range) + .range([0, svg_width - svg_padding - axisRightWidth]) + .clamp(true); + const axisBottom: any = d3.axisTop(scaleBottom).tickSize(0).tickPadding(10); + d3.select('.axisBottom') + .transition() + .attr('transform', `translate(0, ${svg_height - svg_padding})`) + .call(axisBottom) + .selectAll('text') + .attr('fill', '#7E8A93'); + d3.select('.axisBottom').select('.domain').attr('stroke', 'transparent'); + + // 创建一个纵坐标 + const scaleRight = d3 + .scaleLinear() + .domain(amount_range) + .range([0, svg_height - svg_padding * 2]) + .clamp(true); + const axisRight: any = d3.axisLeft(scaleRight).tickSize(0).tickPadding(10); + d3.select('.axisRight') + .transition() + .attr('transform', `translate(${svg_width - svg_padding}, 0)`) + .call(axisRight) + .selectAll('text') + .attr('fill', '#7E8A93') + .select('.domain'); + d3.select('.axisRight').select('.domain').attr('stroke', 'transparent'); + + // 面积 path data 生成器 + const areaGenerator = d3 + .area() + .x((d: any) => { + return +Big(scaleBottom(+d.price)).toFixed(0); + }) + .y0(() => { + return svg_height - svg_padding * 2; + }) + .y1((d: any) => { + return +Big( + scaleRight(+(d.accumulated_x_readable || d.accumulated_y_readable)) + ).toFixed(0); + }); + + // 折线path data生成器 + const lineGenerator = d3 + .line() + .x((d: any) => { + return +Big(scaleBottom(+d.price)).toFixed(0); + }) + .y((d: any) => { + return +Big( + scaleRight(+(d.accumulated_x_readable || d.accumulated_y_readable)) + ).toFixed(0); + }); + + // 虚线 path data 生成器 + const dashLineGenerator = d3.line(); + + /** 创建左侧区域 */ + // 面积 + if (buy_list?.length) { + const area_path_data_left = areaGenerator(buy_list_new as any); + d3.select('.areaLeft') + .append('path') + .attr('opacity', '0.3') + .attr('d', area_path_data_left) + .attr('fill', 'url(#paint0_linear_7545_2924)'); + + // 渐变色 + const max_y = buy_list_new[buy_list_new.length - 1]; + const y = +Big( + scaleRight( + +(max_y.accumulated_x_readable || max_y.accumulated_y_readable) + ) + ).toNumber(); + d3.select('.greenLinearGradient') + .attr('y1', y) + .attr('y2', svg_height - svg_padding * 2); + + // 折线 + var line_path_data_left = lineGenerator(buy_list_new as any); + d3.select('.areaLeft') + .append('path') + .attr('d', line_path_data_left) + .attr('stroke', '#00FFD1') + .attr('strokeWidth', '2') + .attr('fill', 'none'); + + // 触发鼠标事件的矩形区域 + const buy_list_first = buy_list_new[0]; + const buy_list_last = buy_list_new[buy_list_new.length - 1]; + d3.select('.rectLeft') + .append('rect') + .attr('width', () => { + return ( + scaleBottom(+buy_list_first.price) - + scaleBottom(+buy_list_last.price) + + svg_padding + ); + }) + .attr('height', () => { + return svg_height; + }) + .attr('x', () => { + return scaleBottom(+buy_list_last.price) - svg_padding; + }) + .attr('y', `${-svg_padding}`) + .attr('fill', 'transparent') + .on('mousemove', function (e) { + const { offsetX, offsetY } = e; + const list = buy_list.concat([]).reverse(); + const [targetX, targetY, targetItem] = searchNearCoordinate( + list, + e, + scaleBottom, + scaleRight + ); + if (!isInvalid(targetX) && !isInvalid(targetY)) { + showCrossDot({ + dashLineGenerator, + targetX, + targetY, + offsetX, + offsetY, + dotFillColor: '#00FFD1', + }); + setSide('buy'); + setFoucsOrderPoint(targetItem); + } + }) + .on('mouseleave', function (e, d) { + hideCrossDot(); + }); + } + + /** 创建右侧区域 */ + // 面积 + if (sell_list?.length) { + const area_path_data_right = areaGenerator(sell_list_new as any); + d3.select('.areaRight') + .append('path') + .attr('opacity', '0.3') + .attr('d', area_path_data_right) + .attr('fill', 'url(#paint0_linear_7545_2926)'); + + // 渐变色 + const max_y = sell_list_new[0]; + const y = +Big( + scaleRight( + +(max_y.accumulated_x_readable || max_y.accumulated_y_readable) + ) + ).toNumber(); + d3.select('.redLinearGradient') + .attr('y1', y) + .attr('y2', svg_height - svg_padding * 2); + + // 折线 + const line_path_data_right = lineGenerator(sell_list_new as any); + d3.select('.areaRight') + .append('path') + .attr('d', line_path_data_right) + .attr('stroke', '#FF6A8E') + .attr('strokeWidth', '2') + .attr('fill', 'none'); + // 触发鼠标事件的矩形区域 + const sell_list_first = sell_list_new[0]; + const sell_list_last = sell_list_new[sell_list_new.length - 1]; + d3.select('.rectRight') + .append('rect') + .attr('width', () => { + return ( + scaleBottom(+sell_list_first.price) - + scaleBottom(+sell_list_last.price) + + svg_padding + ); + }) + .attr('height', () => { + return svg_height; + }) + .attr('x', () => { + return scaleBottom(+sell_list_last.price); + }) + .attr('y', `${-svg_padding}`) + .attr('fill', 'transparent') + .on('mousemove', function (e) { + const { offsetX, offsetY } = e; + const list = sell_list.concat([]).reverse(); + const [targetX, targetY, targetItem] = searchNearCoordinate( + list, + e, + scaleBottom, + scaleRight + ); + if (!isInvalid(targetX) && !isInvalid(targetY)) { + showCrossDot({ + dashLineGenerator, + targetX, + targetY, + offsetX, + offsetY, + dotFillColor: '#FF6A8E', + }); + setSide('sell'); + setFoucsOrderPoint(targetItem); + } + }) + .on('mouseleave', function (e, d) { + hideCrossDot(); + }); + } + } + function gte_price_range_by_zoom() { + // 获取价格区间 + let min_price: any; + let max_price: any; + if (buy_list.length == 0) { + min_price = Big(sell_list[sell_list.length - 1].price || 0) + .mul(0.9) + .toFixed(); + } else if (buy_list.length == 1) { + min_price = Big(buy_list[0].price).mul(0.9).toFixed(); + } else { + min_price = Big(buy_list[buy_list.length - 1].price) + .mul(0.9) + .toFixed(); + } + if (sell_list.length == 0) { + max_price = Big(buy_list[0].price || 0) + .mul(1.1) + .toFixed(); + } else if (sell_list.length == 1) { + max_price = Big(sell_list[0].price).mul(1.1).toFixed(); + } else { + max_price = Big(sell_list[0].price).mul(1.1).toFixed(); + } + let new_min_price: any; + let new_max_price: any; + const each_step_range = Big(max_price) + .minus(min_price) + .div(GEARS[0] * 2); + const total_step_range = each_step_range.mul(GEARS[0] - zoom); + new_min_price = Big(min_price).plus(total_step_range).toFixed(); + new_max_price = Big(max_price).minus(total_step_range).toFixed(); + return [new_min_price, new_max_price]; + } + function get_data_for_drawing() { + // 获取价格区间 + const [min_price, max_price] = gte_price_range_by_zoom(); + // 获取 数量区间 + const amounts: string[] = []; + buy_list.concat(sell_list).forEach((item: IOrderPointItem) => { + amounts.push( + Big(item.accumulated_x_readable || item.accumulated_y_readable).toFixed( + 0 + ) + ); + }); + amounts.sort((b, a) => { + return Big(b).minus(a).toNumber(); + }); + + // 给绘制的图 添加辅助点 + const buy_list_new: IOrderPointItem[] = []; + if (buy_list.length == 1) { + const ele = buy_list[0]; + buy_list_new.push( + { + price: ele.price, + accumulated_x_readable: '0', + accumulated_y_readable: '0', + }, + ele, + { + price: min_price, + accumulated_x_readable: ele.accumulated_x_readable, + accumulated_y_readable: ele.accumulated_y_readable, + } + ); + } else { + buy_list.forEach((item: IOrderPointItem, index) => { + if (index == 0) { + buy_list_new.push({ + price: item.price, + accumulated_x_readable: '0', + accumulated_y_readable: '0', + }); + } + buy_list_new.push(item); + const nextItem = buy_list[index + 1]; + if (index < buy_list.length - 1) { + buy_list_new.push({ + price: nextItem.price, + accumulated_x_readable: item.accumulated_x_readable, + accumulated_y_readable: item.accumulated_y_readable, + }); + } + if (index == buy_list.length - 1) { + buy_list_new.push({ + price: min_price, + accumulated_x_readable: item.accumulated_x_readable, + accumulated_y_readable: item.accumulated_y_readable, + }); + } + }); + } + const sell_list_new: IOrderPointItem[] = []; + if (sell_list.length == 1) { + const ele = sell_list[0]; + sell_list_new.push( + { + price: max_price, + accumulated_x_readable: ele.accumulated_x_readable, + accumulated_y_readable: ele.accumulated_y_readable, + }, + ele, + { + price: ele.price, + accumulated_x_readable: '0', + accumulated_y_readable: '0', + } + ); + } else { + sell_list.forEach((item: IOrderPointItem, index) => { + if (index == 0) { + sell_list_new.push({ + price: max_price, + accumulated_x_readable: item.accumulated_x_readable, + accumulated_y_readable: item.accumulated_y_readable, + }); + } + if (index < sell_list.length - 1) { + const nextItem = sell_list[index + 1]; + sell_list_new.push(item, { + price: item.price, + accumulated_x_readable: nextItem.accumulated_x_readable, + accumulated_y_readable: nextItem.accumulated_y_readable, + }); + } + if (index == sell_list.length - 1) { + sell_list_new.push(item, { + price: item.price, + accumulated_x_readable: '0', + accumulated_y_readable: '0', + }); + } + }); + } + const price_range: number[] = [+min_price, +max_price]; + const amount_range: number[] = [+amounts[amounts.length - 1], 0]; + return { + price_range, + amount_range, + buy_list_new, + sell_list_new, + }; + } + function clearChart() { + d3.selectAll('.axisBottom *').remove(); + d3.selectAll('.axisRight *').remove(); + d3.selectAll('.areaLeft *').remove(); + d3.selectAll('.areaRight *').remove(); + d3.selectAll('.rectLeft *').remove(); + d3.selectAll('.rectRight *').remove(); + d3.select('.verticalDashLine').attr('d', ''); + d3.select('.horizontalDashLine').attr('d', ''); + } + // 找到离这个点最近的一个数据 中文 + function searchNearCoordinate( + list: IOrderPointItem[], + e: any, + scaleBottom: Function, + scaleRight: Function + ) { + const { offsetX } = e; + const x = offsetX - svg_padding; + let targetX; + let targetY; + let targetItem; + let gtIndex = list.findIndex((item: IOrderPointItem) => { + return scaleBottom(+item.price) >= x; + }); + if (gtIndex == -1) { + gtIndex = list.length - 1; + } + const gtItem = list[gtIndex]; + const x1 = scaleBottom(+gtItem.price); + if (gtIndex == 0) { + targetY = scaleRight( + +(gtItem.accumulated_x_readable || gtItem.accumulated_y_readable) + ); + targetX = x1; + targetItem = gtItem; + } else { + const ltIndex = gtIndex - 1; + const ltItem = list[ltIndex]; + const x0 = scaleBottom(+ltItem.price); + if (x1 - x > x - x0) { + targetX = x0; + targetY = scaleRight( + +(ltItem.accumulated_x_readable || ltItem.accumulated_y_readable) + ); + targetItem = ltItem; + } else { + targetX = x1; + targetY = scaleRight( + +(gtItem.accumulated_x_readable || gtItem.accumulated_y_readable) + ); + targetItem = gtItem; + } + } + return [targetX, targetY, targetItem]; + } + function showCrossDot({ + dashLineGenerator, + targetX, + targetY, + offsetX, + offsetY, + dotFillColor, + }: { + dashLineGenerator: Function; + targetX: number; + targetY: number; + offsetX: number; + offsetY: number; + dotFillColor: string; + }) { + const pathDataX = dashLineGenerator([ + [targetX, -40], + [targetX, 360], + ]); + const pathDataY = dashLineGenerator([ + [0, targetY], + [520, targetY], + ]); + d3.select('.verticalDashLine').attr('d', pathDataX).attr('opacity', '1'); + d3.select('.horizontalDashLine').attr('d', pathDataY).attr('opacity', '1'); + d3.select('.dot') + .attr('cx', targetX) + .attr('cy', targetY) + .attr('opacity', '1') + .attr('fill', dotFillColor); + let translate_x = offsetX + disFromHoverBoxToPointer; + let translate_y = is_mobile + ? offsetY + disFromHoverBoxToPointer + : offsetY - disFromHoverBoxToPointer; + if (offsetX > 380) { + translate_x = offsetX - 235; + } + if (is_mobile) { + translate_x = Math.min(140, translate_x); + } + d3.select('.hoverBox').attr( + 'style', + `visibility:visible;transform:translate(${translate_x}px, ${translate_y}px)` + ); + } + function hideCrossDot() { + d3.select('.verticalDashLine').attr('opacity', '0'); + d3.select('.horizontalDashLine').attr('opacity', '0'); + d3.select('.dot').attr('opacity', '0'); + d3.select('.hoverBox').attr('style', `visibility:invisible`); + } + return ( +
+ + + {/* 横坐标 */} + + {/* 纵坐标 */} + + {/* 左侧面积图 */} + + {/* 右侧面积图 */} + + {/* 左侧触发鼠标事件区域 */} + + {/* 右侧触发鼠标事件区域 */} + + {/* 垂直 虚线 */} + + {/* 水平 虚线 */} + + {/* 折线上的点 */} + + + {/* 渐变色绿色 */} + + + + + + + {/* 渐变色红色 */} + + + + + + + + {/* hover上去的悬浮框 */} + {/* lg:invisible xsm:hidden */} +
+
+ Side + + {side} + +
+
+ Price({cur_pairs}) + + {formatPriceWithCommas(foucsOrderPoint?.price)} + +
+
+ + Qty({cur_token_symbol}) + + + {formatNumber( + foucsOrderPoint?.amount_x_readable || + foucsOrderPoint?.amount_y_readable + )} + +
+
+ + Total Qty({cur_token_symbol}) + + + {formatNumber( + foucsOrderPoint?.accumulated_x_readable || + foucsOrderPoint?.accumulated_y_readable + )} + +
+
+
+ ); +} +function RefreshIcon(props: any) { + return ( + + + + + + + + + + + ); +} +function EmptyIcon(props: any) { + return ( + + + + + ); +} +function LeftArrowIcon(props: any) { + return ( + + + + ); +} +function RightArrowIcon(props: any) { + return ( + + + + ); +} + +function AddIcon(props: any) { + return ( + + + + + ); +} + +function SubIcon(props: any) { + return ( + + + + ); +} + +const formatNumber = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.01'; + } else { + return toInternationalCurrencySystem(big.toFixed(2, 1)); + } +}; +const formatPrice = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.01)) { + return '<0.0001'; + } else { + return big.toFixed(4, 1); + } +}; +export const formatPriceWithCommas = (v: string | number) => { + if (isInvalid(v)) return '-'; + const big = Big(v); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.0001)) { + return '<0.0001'; + } else { + const p = big.toFixed(4, 0); + const [whole, decimal] = p.split('.'); + return `${formatWithCommas(whole)}${decimal ? '.' + decimal : ''}`; + } +}; + +const isInvalid = function (v: any) { + if (v === '' || v === undefined || v === null) return true; + return false; +}; + +interface IOrderPoint { + [point: string]: IOrderPointItem; +} +interface IOrderPointItem { + point?: number; + amount_x?: string; + amount_y?: string; + price?: string; + amount_x_readable?: string; + amount_y_readable?: string; + accumulated_x_readable?: string; + accumulated_y_readable?: string; +} + +type ISwitchToken = 'X' | 'Y'; +type ISide = 'buy' | 'sell'; diff --git a/src/components/swap/SwapProTab.tsx b/src/components/swap/SwapProTab.tsx new file mode 100644 index 000000000..8439389d1 --- /dev/null +++ b/src/components/swap/SwapProTab.tsx @@ -0,0 +1,32 @@ +import React, { useContext, useEffect, useState } from 'react'; +import { SwapProContext, SWAP_MODE } from '../../pages/SwapPage'; +export default function ProTab() { + const { proTab, setProTab, dcl_pool_id, swapMode } = + useContext(SwapProContext); + if (!dcl_pool_id || swapMode == SWAP_MODE.NORMAL) return null; + return ( +
+
{ + setProTab('PRICE'); + }} + className={`flex items-center justify-center px-4 py-0.5 rounded-md text-sm cursor-pointer ${ + proTab == 'PRICE' ? 'bg-proTabBgColor text-white' : 'text-primaryText' + }`} + > + Price +
+
{ + setProTab('ORDER'); + }} + className={`flex items-center justify-center px-4 py-0.5 rounded-md text-sm cursor-pointer ${ + proTab == 'ORDER' ? 'bg-proTabBgColor text-white' : 'text-primaryText' + }`} + > + Orders +
+
+ ); +} +export type IProTab = 'PRICE' | 'ORDER'; diff --git a/src/components/swap/SwapRateChart.tsx b/src/components/swap/SwapRateChart.tsx index ccbc6729b..1d1a105ec 100644 --- a/src/components/swap/SwapRateChart.tsx +++ b/src/components/swap/SwapRateChart.tsx @@ -35,6 +35,7 @@ import { SwapProContext } from '../../pages/SwapPage'; import { scientificNotationToString, toPrecision } from '../../utils/numbers'; import Big from 'big.js'; import { toRealSymbol } from '../../utils/token'; +import SwapProTab from './SwapProTab'; export interface SwapRateChartProps { tokenIn: TokenMetadata; tokenOut: TokenMetadata; @@ -349,35 +350,15 @@ export default function SwapRateChart(props: SwapRateChartProps) { tokens={[displayTokenIn, displayTokenOut]} separator="/" /> - - { - setReverseToken(!reverseToken); - }} - /> -
- -
- {dimensionList.map((d) => { - return ( -
{ - e.preventDefault(); - e.stopPropagation(); - - changeDisplayDimension(d); - }} - > - {d} -
- ); - })} +
+ { + setReverseToken(!reverseToken); + }} + /> +
+
@@ -402,93 +383,122 @@ export default function SwapRateChart(props: SwapRateChartProps) { })}
-
-
- - {diff ? priceFormatter(diff.curPrice) : '-'} - - - {diff && ( - - {displayTokenOut && toRealSymbol(displayTokenOut.symbol)} - - )} - {diff && ( - - {diff.direction !== 'unChange' && ( - - )} - - {diff.percent} - - )} -
- - {diff && ( -
- - +
+
+
+ + {diff ? priceFormatter(diff.curPrice) : '-'} - {diff.lastUpdate} -
- )} - - {diff && ( - <> -
- - - {`(${displayDimension})`} + {diff && ( + + {displayTokenOut && toRealSymbol(displayTokenOut.symbol)} + )} + {diff && ( + + {diff.direction !== 'unChange' && ( + + )} - - {priceList && - priceFormatter( - minBy(priceList.price_list, (p) => p.price)?.price || 0 - )} + {diff.percent} + )} +
+ { + setReverseToken(!reverseToken); + }} + />
- -
- +
+ {diff && ( +
+ - {`(${displayDimension})`} - - {priceList && - priceFormatter( - maxBy(priceList.price_list, (p) => p.price)?.price || 0 - )} - + {diff.lastUpdate}
- - )} + )} + + {diff && ( + <> +
+ + + {`(${displayDimension})`} + + + + {priceList && + priceFormatter( + minBy(priceList.price_list, (p) => p.price)?.price || 0 + )} + +
+ +
+ + + {`(${displayDimension})`} + + + + {priceList && + priceFormatter( + maxBy(priceList.price_list, (p) => p.price)?.price || 0 + )} + +
+ + )} +
+
+ {dimensionList.map((d) => { + return ( +
{ + e.preventDefault(); + e.stopPropagation(); + + changeDisplayDimension(d); + }} + > + {d} +
+ ); + })} +
{diff && ( diff --git a/src/components/swap/swap.ts b/src/components/swap/swap.ts new file mode 100644 index 000000000..4b0c87ac6 --- /dev/null +++ b/src/components/swap/swap.ts @@ -0,0 +1,33 @@ +import { WRAP_NEAR_CONTRACT_ID, unwrapedNear } from '../../services/wrap-near'; + +const SWAP_IN_KEY = 'REF_FI_SWAP_IN'; +const SWAP_OUT_KEY = 'REF_FI_SWAP_OUT'; +const SWAP_IN_KEY_SYMBOL = 'REF_FI_SWAP_IN_SYMBOL'; +const SWAP_OUT_KEY_SYMBOL = 'REF_FI_SWAP_OUT_SYMBOL'; + +export function getStorageTokenId() { + const in_key = localStorage.getItem(SWAP_IN_KEY); + const in_key_symbol = localStorage.getItem(SWAP_IN_KEY_SYMBOL); + const out_key = localStorage.getItem(SWAP_OUT_KEY); + const out_key_symbol = localStorage.getItem(SWAP_OUT_KEY_SYMBOL); + const result = []; + if (in_key == WRAP_NEAR_CONTRACT_ID) { + if (in_key_symbol == 'NEAR') { + result.push('near'); + } else { + result.push(WRAP_NEAR_CONTRACT_ID); + } + } else { + result.push(in_key); + } + if (out_key == WRAP_NEAR_CONTRACT_ID) { + if (out_key_symbol == 'NEAR') { + result.push('near'); + } else { + result.push(WRAP_NEAR_CONTRACT_ID); + } + } else { + result.push(out_key); + } + return result; +} diff --git a/src/components/tokens/Token.tsx b/src/components/tokens/Token.tsx index ed44c717d..994af77e5 100644 --- a/src/components/tokens/Token.tsx +++ b/src/components/tokens/Token.tsx @@ -150,3 +150,47 @@ export default function Token({
); } + +export const TokenLinks = { + NEAR: 'https://awesomenear.com/near-protocol', + wNEAR: 'https://awesomenear.com/near-protocol', + REF: 'https://awesomenear.com/ref-finance', + OCT: 'https://awesomenear.com/octopus-network', + PARAS: 'https://awesomenear.com/paras', + SKYWARD: 'https://awesomenear.com/skyward-finance', + FLX: 'https://awesomenear.com/flux', + PULSE: 'https://awesomenear.com/pulse', + DBIO: 'https://awesomenear.com/debio-network', + MYRIA: 'https://awesomenear.com/myriad-social', + PXT: 'https://awesomenear.com/cryptoheroes', + HAPI: 'https://awesomenear.com/hapi', + OIN: 'https://awesomenear.com/oin-finance', + ABR: 'https://awesomenear.com/allbridge', + '1MIL': 'https://awesomenear.com/1millionnfts', + MARMAJ: 'https://awesomenear.com/marmaj-foundation', + marmaj: 'https://awesomenear.com/marmaj-foundation', + USN: 'https://awesomenear.com/decentral-bank', + '1INCH': 'https://awesomenear.com/1inch-network', + GRT: 'https://awesomenear.com/the-graph', + LINK: 'https://awesomenear.com/chainlink', + Cheddar: 'https://awesomenear.com/cheddar-farm', + AURORA: 'https://awesomenear.com/aurora-dev', + $META: 'https://awesomenear.com/meta-pool', + UMINT: 'https://awesomenear.com/youminter', + UTO: 'https://awesomenear.com/secret-skellies-society', + DEIP: 'https://awesomenear.com/deip', + WOO: 'https://awesomenear.com/woo-dex', + LINEAR: 'https://awesomenear.com/linear-protocol', + PEM: 'https://awesomenear.com/pembrock-finance', + ATO: 'https://awesomenear.com/atocha-protocol', + SEAT: 'https://awesomenear.com/seatlab-nft', + FAR: 'https://awesomenear.com/few-and-far', + BSTN: 'https://awesomenear.com/bastion', + BRRR: 'https://awesomenear.com/burrow', + XNL: 'https://awesomenear.com/chronicle', + KSW: 'https://awesomenear.com/killswitch-finance', + STNEAR: 'https://awesomenear.com/meta-pool', + NearX: 'https://awesomenear.com/stader', + SD: 'https://awesomenear.com/stader', + DISC: 'https://awesomenear.com/discovol', +}; diff --git a/src/context/WalletSelectorContext.tsx b/src/context/WalletSelectorContext.tsx index a3f54f689..1ab0e0b75 100644 --- a/src/context/WalletSelectorContext.tsx +++ b/src/context/WalletSelectorContext.tsx @@ -1,18 +1,13 @@ import React, { useCallback, useContext, useEffect, useState } from 'react'; -import { map, distinctUntilChanged, windowWhen } from 'rxjs'; +import { map, distinctUntilChanged } from 'rxjs'; -import { - NetworkId, - setupWalletSelector, - waitFor, -} from '@near-wallet-selector/core'; +import { NetworkId, setupWalletSelector } from '@near-wallet-selector/core'; import type { WalletSelector, AccountState } from '@near-wallet-selector/core'; -import { setupModal } from './modal-ui'; -import type { WalletSelectorModal } from './modal-ui'; +import { setupModal } from '@near-wallet-selector/modal-ui'; +import type { WalletSelectorModal } from '@near-wallet-selector/modal-ui'; import { setupNearWallet } from '@near-wallet-selector/near-wallet'; import { setupMyNearWallet } from '@near-wallet-selector/my-near-wallet'; import { setupSender } from '@near-wallet-selector/sender'; -import { setupMathWallet } from '@near-wallet-selector/math-wallet'; import { setupLedger } from '@near-wallet-selector/ledger'; import { setupHereWallet } from '@near-wallet-selector/here-wallet'; @@ -24,28 +19,20 @@ import { setupNightly } from '@near-wallet-selector/nightly'; import getConfig from '../services/config'; -import './modal-ui/components/styles.css'; -import { - REF_FARM_CONTRACT_ID, - wallet, - REF_FARM_BOOST_CONTRACT_ID, - near, -} from '../services/near'; +import '@near-wallet-selector/modal-ui/styles.css'; +import { near } from '../services/near'; import { walletIcons } from './walletIcons'; import { getOrderlyConfig } from '../pages/Orderly/config'; import { REF_ORDERLY_ACCOUNT_VALID } from '../pages/Orderly/components/UserBoard/index'; import { REF_FI_SENDER_WALLET_ACCESS_KEY, REF_ORDERLY_NORMALIZED_KEY, - generateTradingKeyPair, } from '../pages/Orderly/orderly/utils'; import { get_orderly_private_key_path, get_orderly_public_key_path, } from '../pages/Orderly/orderly/utils'; import { isMobile } from '../utils/device'; -import { AccountView } from 'near-api-js/lib/providers/provider'; -import { Account, providers } from 'near-api-js'; const CONTRACT_ID = getOrderlyConfig().ORDERLY_ASSET_MANAGER; @@ -110,55 +97,33 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { network: getConfig().networkId as NetworkId, debug: false, modules: [ - setupNearWallet({ - iconUrl: walletIcons['near-wallet'], - }), setupMyNearWallet({ - iconUrl: walletIcons['my-near-wallet'], + // iconUrl: walletIcons['my-near-wallet'], + }), + // @ts-ignore + setupHereWallet(), + setupNearWallet({ + // iconUrl: walletIcons['near-wallet'], }), setupSender({ - iconUrl: walletIcons['sender'], + // iconUrl: walletIcons['sender'], }), // @ts-ignore setupMeteorWallet({ - iconUrl: walletIcons['meteor-wallet'], + // iconUrl: walletIcons['meteor-wallet'], }), setupNeth({ - iconUrl: walletIcons['neth'], + // iconUrl: walletIcons['neth'], gas: '300000000000000', bundle: false, - }), + }) as any, // @ts-ignore setupNightly({ - iconUrl: walletIcons['nightly'], + // iconUrl: walletIcons['nightly'], }), setupLedger({ - iconUrl: walletIcons['ledger'], + // iconUrl: walletIcons['ledger'], }), - // @ts-ignore - setupHereWallet(), - // setupNightlyConnect({ - // url: 'wss://ncproxy.nightly.app/app', - // appMetadata: { - // additionalInfo: '', - // application: 'ref fiannce', - // description: 'Example dApp used by NEAR Wallet Selector', - // icon: 'https://near.org/wp-content/uploads/2020/09/cropped-favicon-192x192.png', - // }, - // iconUrl: walletIcons['nightly-connect'], - // }), - // setupWalletConnect({ - // projectId: '423baa464ffaeca9d7165ab4222d534f', - // relayUrl: 'wss://relay.walletconnect.com', - // metadata: { - // name: 'ref_finance', - // description: 'Example dApp used by NEAR Wallet Selector', - // url: 'https://github.com/near/wallet-selector', - // icons: walletIcons['wallet-connect'], - // }, - // chainId: `near:${getConfig().networkId}}`, - // iconUrl: walletIcons['wallet-connect'], - // }), ], }); const _modal = setupModal(_selector, { @@ -234,36 +199,6 @@ export const WalletSelectorContextProvider: React.FC = ({ children }) => { getAllKeys(accountId); }, [accountId, selector]); - // const getAccount = useCallback(async (): Promise => { - // if (!accountId) { - // return null; - // } - - // const provider = new providers.JsonRpcProvider({ - // url: getConfig().nodeUrl, - // }); - - // return provider - // .query({ - // request_type: 'view_account', - // finality: 'final', - // account_id: accountId, - // }) - // .then((data: any) => ({ - // ...data, - // account_id: accountId, - // })); - // }, [accountId]); - - // useEffect(() => { - // if (!selector || !accountId) return; - - // getAccount().catch((e) => { - // alert(e?.message); - // selector.wallet().then((wallet) => wallet.signOut()); - // }); - // }, [selector, accountId]); - if (!selector || !modal || (!!accountId && isLedger === undefined)) { return null; } diff --git a/src/global.css b/src/global.css index e38777f08..d04c16054 100644 --- a/src/global.css +++ b/src/global.css @@ -524,7 +524,7 @@ input[type='range']::-webkit-slider-runnable-track { } .swiper-container { width: 100%; - height: 98px; + /* height: 98px; */ } .swiper-wrapper { position: relative; @@ -1310,3 +1310,301 @@ input[type='range']::-webkit-slider-runnable-track { .hideScroll::-webkit-scrollbar { display: none; } + +/* slider start*/ +.multi-slider { + height: 22px; +} + +.multi-slider .thumb { + width: 22px; + height: 22px; + cursor: pointer; + border-radius: 100%; + border: 3px solid #1d2932; + outline: none; + background: #00d6af; +} +.multi-slider.disabled .thumb-0, +.multi-slider.disabled .track-1 { + visibility: hidden; +} +.multi-slider .track-0 { + height: 10px; + top: 6px; + border-radius: 6px; + background-color: #00c6a2; +} +.multi-slider .track-1 { + height: 10px; + top: 6px; + border-radius: 6px; + background-color: #121e27; +} + +.multi-slider-double { + height: 22px; +} +.multi-slider-double .thumb { + width: 22px; + height: 22px; + cursor: pointer; + border-radius: 100%; + border: 3px solid #1d2932; + outline: none; + background: #00d6af; +} + +.multi-slider-double .track-0 { + height: 10px; + top: 6px; + border-radius: 6px; + background-color: #121e27; +} +.multi-slider-double .track-1 { + height: 10px; + top: 6px; + border-radius: 6px; + background-color: #00c6a2; +} +.multi-slider-double .track-2 { + height: 10px; + top: 6px; + border-radius: 6px; + background-color: #121e27; +} +/* slider end*/ + +.refresh-loader { + animation: spin 1s linear infinite; + transform-origin: center; +} + +/* wallet select modal ui start */ +.nws-modal-wrapper { + z-index: 9999; +} +.nws-modal-wrapper .nws-modal-overlay { + backdrop-filter: blur(20px); +} +.nws-modal-wrapper .nws-modal { + border: 1px solid #414d55; +} + +::-webkit-scrollbar { + width: 4px; + border-radius: 2px; +} + +::-webkit-scrollbar-thumb { + background: #626486; + border-radius: 2px; +} + +:root { + --wallet-selector-backdrop-bg: rgba(0, 0, 0, 0.5); + --wallet-selector-content-bg: #313d46; + --wallet-selector-heading-color: #ffffff; + --wallet-selector-text-color: #fff; + --gradient-dark-icon: #42525c; + --wallet-selector-close-button-bg-color: transparent; + --wallet-selector-sidebar-border-color: #414d55; + --wallet-selector-selected-wallet-bg: #42525c; + --wallet-selector-mobile-bottom-section: #42525c; + --sidebar-border-color: #42525c; +} +.nws-modal-wrapper + .nws-modal + .modal-right + .wallet-what + .content-side + .nws-modal-wrapper + .nws-modal + .modal-right + .wallet-what + .content-side + p { + font-size: 14px; + color: #7e8a93; +} +.nws-modal-wrapper .nws-modal .modal-right .connecting-message { + color: #fff; +} +.nws-modal-wrapper + .nws-modal + .nws-modal-body + .alert-message + .connection + .error-wrapper + p { + color: #fff; +} + +#near-wallet-selector-modal { + --gradient-dark-icon: #42525c; + --what-wallet-icon-color: rgba(255, 255, 255, 0.65); + --get-wallet-option-bg-color-hover: rgba(255, 255, 255, 0.9); +} + +.nws-modal-wrapper .nws-modal .modal-left::-webkit-scrollbar, +options-list::-webkit-scrollbar { + width: 0px; + border-radius: 2px; +} + +.nws-modal-wrapper .nws-modal .modal-left::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 2px; +} + +.wallet-options-wrapper .options-list::-webkit-scrollbar-thumb { + background: #626486; + border-radius: 2px; +} + +.nws-modal-wrapper .nws-modal .nws-modal-body button.middleButton { + color: #000; + background-color: #d2ff3a; + border: none; +} + +.nws-modal-wrapper .nws-modal .nws-modal-body button.middleButton:hover { + opacity: 0.8; +} + +.nws-modal-wrapper .nws-modal .nws-modal-header .close-button svg { + fill: #fff; +} + +.nws-modal-wrapper + .nws-modal + .wallet-options-wrapper + .options-list + .single-wallet.sidebar:hover { + background-color: #42525c; +} + +.nws-modal-wrapper .nws-modal .connecting-details span { + color: #fff; +} +.nws-modal-wrapper .nws-modal .nws-modal-body button.middleButton { + background: linear-gradient(180deg, #00c6a2 0%, #008b72 100%); + color: #fff; +} +.options-list-section-header { + font-size: 14px; + color: #7e8a93; +} +#webpack-dev-server-client-overlay { + display: none; +} + +.nws-modal-wrapper .modal-overlay { + /* background: var(--wallet-selector-backdrop-bg, var(--backdrop-bg)); */ + background-color: rgba(0, 0, 0, 0.5); + height: 100%; + width: 100%; + position: absolute; + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); +} +.nws-modal-wrapper .modal { + /* background: var(--wallet-selector-content-bg, var(--content-bg)); */ + /* width: 470px; */ + max-width: 470px; + height: auto; + max-height: 90vh; + border-radius: 16px; + position: absolute; + left: 50%; + transform: translate(-50%, 0px); + transition: visibility 0s linear 0s, opacity 0.25s 0s, transform 0.25s; + background-color: var(--wallet-selector-content-bg, var(--content-bg)); + padding-bottom: 24px; + padding-top: 30px; + padding-left: 28px; + padding-right: 28px; + font-size: 16px; + line-height: 1.6; + border: solid 1px transparent; + background-image: linear-gradient(var(--content-bg), var(--content-bg)), + linear-gradient( + 135deg, + rgba(0, 255, 209, 0.2) 0%, + rgba(147, 62, 255, 0.2) 100% + ); + background-origin: border-box; + background-clip: content-box, border-box; +} + +.nws-modal-wrapper .modal { + box-sizing: content-box; +} +.nws-modal-wrapper .modal:before { + content: ''; + z-index: -1; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: linear-gradient(135deg, #00ffd1 0%, #933eff 100%); + filter: blur(30px); + opacity: 0.5; + transition: opacity 0.3s; + border-radius: inherit; +} + +.nws-modal-wrapper .modal::after { + content: ''; + z-index: -1; + position: absolute; + top: 0; + right: 0; + bottom: 0; + left: 0; + background: inherit; + border-radius: inherit; +} +.nws-form-control .account label { + display: block; + width: 100%; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.nws-form-control .account br { + display: none; +} +.nws-modal-wrapper + .nws-modal + .choose-ledger-account-form-wrapper + .nws-form-control + .account { + display: flex; + align-items: center; + gap: 4px; + border-bottom: 1px solid #42525c; +} +.nws-modal-wrapper + .nws-modal + .choose-ledger-account-form-wrapper + .nws-form-control + .account + input[type='checkbox'] { + transform: scale(1.2); +} +.nws-modal-wrapper .nws-modal .overview-wrapper .account { + border-bottom: 1px solid #42525c; +} +.nws-modal-wrapper .nws-modal .overview-wrapper .account span { + display: block; + overflow: hidden; + width: 100%; + text-overflow: ellipsis; + white-space: nowrap; +} +.nws-modal-wrapper .nws-modal .modal-right::-webkit-scrollbar { + width: 2px; +} +/* wallet select modal ui end */ diff --git a/src/locales/en_US.ts b/src/locales/en_US.ts index 991b59d9f..7ba32a88e 100644 --- a/src/locales/en_US.ts +++ b/src/locales/en_US.ts @@ -1242,7 +1242,7 @@ const en_US = { your: 'Your', max_apr: 'Max.APR', faming_positions: 'Farming Positions', - you_can_earn_tip: 'You can earn rewards by farming, est. APR is', + you_can_earn_tip: 'Farm available, farm APR up to', you_can_earn_current_tip: 'Your current staked farm ended, and new farm is coming, est. APR is', go_new_farm: 'Go New Farm', @@ -1381,6 +1381,7 @@ const en_US = { pool_refresh: 'Something wrong with the server, please try again later.', netWorthTip: 'Total value of investments in Ref (including claimable rewards) + Total value of Orderly assets + Total value of Burrow assets + Total assets in wallet', + top_bin_apr: 'Top Bin APR (24h)', mark_price: 'Mark Price', last_price: 'Last Price', diff --git a/src/locales/es.ts b/src/locales/es.ts index b7e98311b..c24571fa5 100644 --- a/src/locales/es.ts +++ b/src/locales/es.ts @@ -1408,6 +1408,7 @@ const es = { 'Hubo un problema con el servidor, por favor intenta de nuevo más tarde.', netWorthTip: 'Valor total de inversiones en Ref (incluyendo recompensas reclamables) + Valor total de activos en Orderly + Valor total de activos en Burrow + Activos totales en la billetera', + top_bin_apr: 'Top Bin APR (24h)', mark_price: 'Precio de Marca', last_price: 'Último Precio', mark_price_tip: diff --git a/src/locales/ja.ts b/src/locales/ja.ts index e4a3499a1..0f3025b4d 100644 --- a/src/locales/ja.ts +++ b/src/locales/ja.ts @@ -1239,7 +1239,7 @@ const ja = { your: 'Your', max_apr: 'Max.APR', faming_positions: 'Farming Positions', - you_can_earn_tip: 'You can earn rewards by farming, est. APR is', + you_can_earn_tip: 'Farm available, farm APR up to', you_can_earn_current_tip: 'Your current staked farm ended, and new farm is coming, est. APR is', go_new_farm: 'Go New Farm', @@ -1373,6 +1373,7 @@ const ja = { pool_refresh: 'Something wrong with the server, please try again later.', netWorthTip: 'Total value of investments in Ref (including claimable rewards) + Total value of Orderly assets + Total value of Burrow assets + Total assets in wallet', + top_bin_apr: 'Top Bin APR (24h)', mark_price: 'Mark Price', last_price: 'Last Price', mark_price_tip: 'Mark price is used for PnL calculating and liquidation.', diff --git a/src/locales/ru.ts b/src/locales/ru.ts index 6fa2eddf8..2b50e65e0 100644 --- a/src/locales/ru.ts +++ b/src/locales/ru.ts @@ -1264,7 +1264,7 @@ const ru = { your: 'Your', max_apr: 'Max.APR', faming_positions: 'Farming Positions', - you_can_earn_tip: 'You can earn rewards by farming, est. APR is', + you_can_earn_tip: 'Farm available, farm APR up to', you_can_earn_current_tip: 'Your current staked farm ended, and new farm is coming, est. APR is', go_new_farm: 'Go New Farm', diff --git a/src/locales/uk_UA.ts b/src/locales/uk_UA.ts index 6c4abc906..1d5e13ee4 100644 --- a/src/locales/uk_UA.ts +++ b/src/locales/uk_UA.ts @@ -1243,7 +1243,7 @@ const uk_UA = { your: 'Your', max_apr: 'Max.APR', faming_positions: 'Farming Positions', - you_can_earn_tip: 'You can earn rewards by farming, est. APR is', + you_can_earn_tip: 'Farm available, farm APR up to', you_can_earn_current_tip: 'Your current staked farm ended, and new farm is coming, est. APR is', go_new_farm: 'Go New Farm', @@ -1377,6 +1377,7 @@ const uk_UA = { pool_refresh: 'Something wrong with the server, please try again later.', netWorthTip: 'Total value of investments in Ref (including claimable rewards) + Total value of Orderly assets + Total value of Burrow assets + Total assets in wallet', + top_bin_apr: 'Top Bin APR (24h)', mark_price: 'Mark Price', last_price: 'Last Price', mark_price_tip: 'Mark price is used for PnL calculating and liquidation.', diff --git a/src/locales/vi.ts b/src/locales/vi.ts index bbad9acc2..3def56a82 100644 --- a/src/locales/vi.ts +++ b/src/locales/vi.ts @@ -1376,6 +1376,7 @@ const vi = { pool_refresh: 'Có sự cố với máy chủ, vui lòng thử lại sau.', netWorthTip: 'Tổng giá trị của đầu tư trong Ref (bao gồm phần thưởng có thể yêu cầu) + Tổng giá trị tài sản Orderly + Tổng giá trị tài sản Burrow + Tổng tài sản trong ví', + top_bin_apr: 'Top Bin APR (24h)', mark_price: 'Giá đánh dấu', last_price: 'Giá cuối cùng', mark_price_tip: 'Giá đánh dấu được sử dụng để tính toán PnL và thanh lý.', diff --git a/src/locales/zh_CN.ts b/src/locales/zh_CN.ts index 51baff3ca..bd91fe918 100644 --- a/src/locales/zh_CN.ts +++ b/src/locales/zh_CN.ts @@ -1227,6 +1227,7 @@ const zh_CN = { pool_refresh: '服务器出现问题,请稍后重试。', netWorthTip: 'Ref中的投资总价值(包括可领取奖励)+Orderly资产总价值+ Burrow资产总价值+钱包中的总资产', + top_bin_apr: 'Top Bin APR (24h)', mark_price: '标记价格', last_price: '最新价格', mark_price_tip: '标记价格用于盈亏计算和清算。', diff --git a/src/pages/Orderly/OrderlyTradingBoard.tsx b/src/pages/Orderly/OrderlyTradingBoard.tsx index 8d2aa654d..cbd8605df 100644 --- a/src/pages/Orderly/OrderlyTradingBoard.tsx +++ b/src/pages/Orderly/OrderlyTradingBoard.tsx @@ -266,7 +266,6 @@ function OrderlyTradingBoard() { {!isMobile && } {isMobile && } - {isMobile && }
); } diff --git a/src/pages/Orderly/components/Common/Icons.tsx b/src/pages/Orderly/components/Common/Icons.tsx index 7ecade04b..ebb63bc14 100644 --- a/src/pages/Orderly/components/Common/Icons.tsx +++ b/src/pages/Orderly/components/Common/Icons.tsx @@ -960,3 +960,419 @@ export const GoToOrderbookTipMobile = (props: any) => {
); }; +export function SpotShape() { + return ( + + + + + + + + + + + + + + + + + + + + + ); +} + +export function CurveShape() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} + +export function BidAskShape() { + return ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ); +} diff --git a/src/pages/Orderly/components/MyOrder/index.tsx b/src/pages/Orderly/components/MyOrder/index.tsx index 11bcca66a..772837ab7 100644 --- a/src/pages/Orderly/components/MyOrder/index.tsx +++ b/src/pages/Orderly/components/MyOrder/index.tsx @@ -66,7 +66,10 @@ import { ExclamationTip, } from '../../../../components/layout/TipWrapper'; import { MyOrderInstantSwapArrowRight } from '../../../../components/icon/swapV3'; -import { TOKEN_LIST_FOR_RATE } from '../../../../services/commonV3'; +import { + TOKEN_LIST_FOR_RATE, + sort_tokens_by_base, +} from '../../../../services/commonV3'; import BigNumber from 'bignumber.js'; import { isMobile } from '../../../../utils/device'; import { useWalletSelector } from '../../../../context/WalletSelectorContext'; @@ -78,6 +81,7 @@ import { HiOutlineExternalLink } from '../../../../components/reactIcons'; import getConfig from '../../../../services/config'; import _ from 'lodash'; import { HistoryOrderSwapInfo } from '../../../../services/indexer'; +import { useDclPoolIdByCondition } from '../../../../state/swapV3'; const ORDER_TYPE_KEY = 'REF_FI_ORDER_TYPE_VALUE'; @@ -95,44 +99,6 @@ const MobileInfoBanner = ({
); }; -function WarningTip() { - if (isMobile()) { - return ( - - - - ); - } - - return ( - - - - ); -} function NoOrderCard({ text }: { text: 'active' | 'history' }) { return ( @@ -431,7 +397,7 @@ function HistoryLine({ const claimTip = (
{actions} - - {/* {actions} */} {hoverOn === index && @@ -836,9 +800,6 @@ function HistoryLine({ )} - - {/* {hover && !ONLY_ZEROS.test(swapIn || '0') ? swapBanner : null} */} -
( sessionStorage.getItem(ORDER_TYPE_KEY) || @@ -2200,11 +2162,71 @@ function OrderCard({ const [historySortBy, setHistorySortBy] = useState<'claimed' | 'created'>( 'created' ); + const [select_type, set_select_type] = useState<'all' | 'current'>('all'); + const [activeOrderList, setActiveOrderList] = useState(); + const [historyOrderList, setHistoryOrderList] = useState(); + const [historySwapInfoList, setHistorySwapInfoList] = + useState(); + + const tokenIds = useMemo(() => { + if (pool_id_by_url) { + const [token_x, token_y, fee] = pool_id_by_url.split('|'); + return [token_x, token_y]; + } + return []; + }, [pool_id_by_url]); + + const tokens = useTokens(tokenIds) || []; + const current_pair_tokens_map = tokens.reduce((acc, cur) => { + return { + ...acc, + [cur.id]: cur, + }; + }, {}); + useEffect(() => { + if (activeOrder.length) { + if (select_type == 'all') { + setActiveOrderList(activeOrder); + } else { + setActiveOrderList(getCurrentPairOrders(activeOrder)); + } + } + }, [activeOrder, select_type, pool_id_by_url]); + + useEffect(() => { + if (historyOrder.length) { + if (select_type == 'all') { + setHistoryOrderList(historyOrder); + } else { + setHistoryOrderList(getCurrentPairOrders(historyOrder)); + } + } + }, [historyOrder, select_type, pool_id_by_url]); + useEffect(() => { + if (historySwapInfo.length) { + if (select_type == 'all') { + setHistorySwapInfoList(historySwapInfo); + } else { + setHistorySwapInfoList(getCurrentPairSwapOrders(historySwapInfo)); + } + } + }, [historySwapInfo, select_type, pool_id_by_url]); + + function getCurrentPairOrders(orders: UserOrderInfo[]) { + return orders.filter((order: UserOrderInfo) => { + return order.pool_id == pool_id_by_url; + }); + } + function getCurrentPairSwapOrders(orders: HistoryOrderSwapInfo[]) { + return orders.filter((order: HistoryOrderSwapInfo) => { + return order.pool_id == pool_id_by_url; + }); + } function OrderTab() { if (isMobile()) { return ( -
+
- {activeOrder && activeOrder.length > 0 - ? ` (${activeOrder.length})` + {activeOrderList && activeOrderList.length > 0 + ? ` (${activeOrderList.length})` : null} @@ -2256,8 +2278,8 @@ function OrderCard({ > - {historyOrder && historyOrder.length > 0 - ? ` (${historyOrder.length})` + {historyOrderList && historyOrderList.length > 0 + ? ` (${historyOrderList.length})` : null} @@ -2284,8 +2306,8 @@ function OrderCard({ id="active_orders" defaultMessage={'Active Orders'} /> - {activeOrder && activeOrder.length > 0 - ? ` (${activeOrder.length})` + {activeOrderList && activeOrderList.length > 0 + ? ` (${activeOrderList.length})` : null} @@ -2313,8 +2335,8 @@ function OrderCard({ > - {historyOrder && historyOrder.length > 0 - ? ` (${historyOrder.length})` + {historyOrderList && historyOrderList.length > 0 + ? ` (${historyOrderList.length})` : null} @@ -2403,10 +2425,41 @@ function OrderCard({
`; } - + function get_current_pairs() { + if (pool_id_by_url && current_pair_tokens_map) { + const [token_x, token_y, fee] = pool_id_by_url.split('|'); + const token_x_meta = current_pair_tokens_map[token_x]; + const token_y_meta = current_pair_tokens_map[token_y]; + if (token_x_meta?.symbol && token_y_meta?.symbol) { + const tokens = sort_tokens_by_base([token_x_meta, token_y_meta]); + return `${tokens[0].symbol}-${tokens[1].symbol}`; + } + } + return ''; + } return (
- {OrderTab()} +
+ {OrderTab()} +
+ { + set_select_type('current'); + }} + > + Current: {get_current_pairs()} + + { + set_select_type('all'); + }} + > + All + +
+
{orderType === 'history' && (!historyOrder || historyOrder.length === 0) && ( @@ -2623,8 +2676,8 @@ function OrderCard({ )} {orderType === 'active' && - activeOrder && - activeOrder.sort(activeOrderSorting).map((order, index) => { + activeOrderList && + activeOrderList.sort(activeOrderSorting).map((order, index) => { return ( { + historyOrderList && + historyOrderList.sort(historyOrderSorting).map((order, index) => { return ( 0 && - historySwapInfo + historySwapInfoList && + historySwapInfoList.length > 0 && + historySwapInfoList .sort((a, b) => Number(b.timestamp) - Number(a.timestamp)) .map((sf, i) => { return ( @@ -2744,943 +2797,21 @@ function OrderCard({ ); } -function OrderCardOld({ - activeOrder, - tokensMap, -}: { - activeOrder: UserOrderInfo[]; - tokensMap: { [key: string]: TokenMetadata }; -}) { - const intl = useIntl(); - - const [activeSortBy, setActiveSortBy] = useState<'unclaim' | 'created'>( - 'created' - ); - - const [sortOrderActive, setSorOrderActive] = useState<'asc' | 'desc'>('desc'); - - const sellAmountToBuyAmount = ( - undecimaled_amount: string, - order: UserOrderInfo, - price: string - ) => { - const buy_amount = new Big( - toReadableNumber( - tokensMap[order.sell_token].decimals, - undecimaled_amount || '0' - ) - ) - .times(price) - .toFixed(tokensMap[order.sell_token].decimals); - - return scientificNotationToString(buy_amount); - }; - - const buyAmountToSellAmount = ( - undecimaled_amount: string, - order: UserOrderInfo, - price: string - ) => { - const sell_amount = new Big( - toReadableNumber( - tokensMap[order.buy_token].decimals, - undecimaled_amount || '0' - ) - ) - .div(price) - .toString(); - return scientificNotationToString(sell_amount); - }; - function ActiveLine({ - order, - index, - }: { - order: UserOrderInfo; - index: number; - }) { - const [claimLoading, setClaimLoading] = useState(false); - - const [cancelLoading, setCancelLoading] = useState(false); - - const [hover, setHover] = useState(false); - - const buyToken = tokensMap[order.buy_token]; - - const sellToken = tokensMap[order.sell_token]; - - if (!buyToken || !sellToken) return null; - - const swapIn = toReadableNumber( - sellToken.decimals, - scientificNotationToString( - new Big(order.original_deposit_amount || '0') - .minus(order.original_amount || '0') - .toString() - ) - ); - - const swapOut = toReadableNumber( - buyToken.decimals, - order.swap_earn_amount || '0' - ); - - const orderIn = toReadableNumber( - sellToken.decimals, - order.original_amount || '0' - ); - - const totalIn = toReadableNumber( - sellToken.decimals, - order.original_deposit_amount || '0' - ); - - const calPoint = - sellToken.id === order.pool_id.split(V3_POOL_SPLITER)[0] - ? order.point - : -order.point; - - const price = pointToPrice({ - tokenA: sellToken, - tokenB: buyToken, - point: calPoint, - }); - - const unClaimedAmount = toReadableNumber( - buyToken.decimals, - order.unclaimed_amount || '0' - ); - - const claimedAmount = toReadableNumber( - buyToken.decimals, - scientificNotationToString( - new Big(order.bought_amount || '0') - .minus(order.unclaimed_amount || '0') - .toString() - ) - ); - - const buyAmountRaw = sellAmountToBuyAmount( - order.original_amount, - order, - price - ); - - const buyAmount = new Big(buyAmountRaw).gt( - toReadableNumber(buyToken.decimals, order.bought_amount || '0') - ) - ? buyAmountRaw - : toReadableNumber(buyToken.decimals, order.bought_amount || '0'); - - const totalOut = scientificNotationToString( - new Big(buyAmount).plus(swapOut).toString() - ); - - const pendingAmount = scientificNotationToString( - new Big(toPrecision(buyAmount || '0', 5, false, false) || 0) - .minus( - toPrecision( - toReadableNumber(buyToken.decimals, order.bought_amount || '0') || - '0', - 5, - false, - false - ) - ) - .toString() - ); - - const pUnClaimedAmount = new Big(unClaimedAmount) - .div(buyAmount) - .times(100) - .toNumber(); - - const pClaimedAmount = new Big(claimedAmount) - .div(buyAmount) - .times(100) - .toNumber(); - - const pPendingAmount = new Big(pendingAmount) - .div(buyAmount) - .times(100) - .toNumber(); - - const displayPercents = checkAllocations('100', [ - pClaimedAmount > 0 && pClaimedAmount < 5 - ? '5' - : scientificNotationToString(pClaimedAmount.toString()), - pUnClaimedAmount > 0 && pUnClaimedAmount < 5 - ? '5' - : scientificNotationToString(pUnClaimedAmount.toString()), - - pPendingAmount > 0 && pPendingAmount < 5 - ? '5' - : scientificNotationToString(pPendingAmount.toString()), - ]); - - const getUnclaimAmountTip = () => { - return ` -
- ${ - ONLY_ZEROS.test(claimedAmount) - ? '' - : ` -
- -
-
- - ${intl.formatMessage({ - id: 'claimed_upper', - defaultMessage: 'Claimed', - })} - -
- - - ${ - Number(claimedAmount) > 0 && Number(claimedAmount) < 0.001 - ? '< 0.001' - : toPrecision(claimedAmount, 3) - } - - -
- ` - } - - - ${ - ONLY_ZEROS.test(unClaimedAmount) - ? '' - : `
- -
-
- - ${intl.formatMessage({ - id: 'filled', - defaultMessage: 'Filled', - })} - -
- - - ${ - Number(unClaimedAmount) > 0 && Number(unClaimedAmount) < 0.001 - ? '< 0.001' - : toPrecision(unClaimedAmount, 3) - } - - -
` - } - - ${ - ONLY_ZEROS.test(pendingAmount) - ? '' - : `
- -
-
- - ${intl.formatMessage({ - id: 'open_my_order', - defaultMessage: 'Open', - })} - -
- - - ${ - Number(pendingAmount) > 0 && Number(pendingAmount) < 0.001 - ? '< 0.001' - : toPrecision(pendingAmount, 3) - } - - - -
` - } - -
- `; - }; - - const sellTokenAmount = ( -
- - - -
- - {Number(orderIn) > 0 && Number(orderIn) < 0.01 - ? '< 0.01' - : toPrecision(orderIn, 2)} - - - - {toRealSymbol(sellToken.symbol)} - -
-
- - - - -
- ); - - const buyTokenAmount = ( - - - -
- - {Number(buyAmount) > 0 && Number(buyAmount) < 0.01 - ? '< 0.01' - : toPrecision(buyAmount, 2)} - - - - {toRealSymbol(buyToken.symbol)} - -
-
- ); - - const fee = Number(order.pool_id.split(V3_POOL_SPLITER)[2]); - - const feeTier = ( - - {`${toPrecision(calculateFeePercent(fee / 100).toString(), 2)}% `} - - ); - - // const orderRate = ( - // - // - // {toPrecision(price, 2)} - // - // - // {`${toRealSymbol(buyToken.symbol)}/${toRealSymbol(sellToken.symbol)}`} - // - - // - // {`${toRealSymbol(buyToken.symbol)}`} - // - // - // ); - const sort = - TOKEN_LIST_FOR_RATE.indexOf(sellToken?.symbol) > -1 && +price !== 0; - const orderRate = useMemo(() => { - let p = price; - if (sort) { - p = new BigNumber(1).dividedBy(price).toFixed(); - } - return ( - - - {toPrecision(p, 2)} - - - {`${toRealSymbol( - sort ? sellToken?.symbol : buyToken.symbol - )}/${toRealSymbol(sort ? buyToken.symbol : sellToken.symbol)}`} - - - {`${toRealSymbol(sort ? sellToken.symbol : buyToken.symbol)}`} - - - ); - }, [buyToken, sellToken, price]); - - const unclaimTip = ( -
- - - -
- {displayPercents.map((p, i) => { - if (ONLY_ZEROS.test(p)) return null; - - const bgColor = - i === 0 - ? 'bg-gradientFrom' - : i === 1 - ? 'bg-deepBlue' - : 'bg-primaryText'; - - return ( -
- ); - })} -
- -
- ); - - const claimButton = ( -
-
- ${intl.formatMessage({ - id: 'v2_paused', - - defaultMessage: 'REF V2 has been paused for maintenance', - })} -
-
- `} - data-for="v2_paused_pool_tip_claim" - > - - - {/* */} -
- ); - - const unclaim = ( - -
-
- - - {Number( - toReadableNumber( - buyToken.decimals, - order.unclaimed_amount || '0' - ) - ) > 0 && - Number( - toReadableNumber( - buyToken.decimals, - order.unclaimed_amount || '0' - ) - ) < 0.001 - ? '< 0.001' - : toPrecision( - toReadableNumber( - buyToken.decimals, - order.unclaimed_amount || '0' - ), - 3 - )} - -
-
{unclaimTip}
-
- {claimButton} -
- ); - - const created = ( - - {moment( - Math.floor(Number(order.created_at) / TIMESTAMP_DIVISOR) * 1000 - ).format('YYYY-MM-DD HH:mm')} - - ); - - const actions = ( -
-
- Canceling will automatically claim your executed tokens. -
-
- `} - data-for="v2_paused_pool_tip_cancel_old" - > - - - -
- ); - - const tokenPrice = useContext(PriceContext); - - const sellTokenPrice = tokenPrice?.[sellToken.id]?.price || null; - const buyTokenPrice = tokenPrice?.[buyToken.id]?.price || null; - function instant_swap_tip() { - const token_sell_symbol = toRealSymbol(sellToken.symbol); - const token_buy_symbol = toRealSymbol(buyToken.symbol); - const sell_token_price = sellTokenPrice - ? `($${toPrecision(sellTokenPrice, 2)})` - : ''; - const buy_token_price = buyTokenPrice - ? `($${toPrecision(buyTokenPrice, 2)})` - : ''; - let rate = new Big(swapOut).div(ONLY_ZEROS.test(swapIn) ? 1 : swapIn); - if (sort) { - rate = new Big(1).div(rate.eq(0) ? '1' : rate); - } - const display_rate = rate.toFixed(3); - let result = ''; - if (sort) { - result = `1 ${token_buy_symbol} ${buy_token_price} = ${display_rate} ${token_sell_symbol}`; - } else { - result = `1 ${token_sell_symbol} ${sell_token_price} = ${display_rate} ${token_buy_symbol}`; - } - return result; - } - const swapBanner = ( -
-
- - - - - - - - {Number(totalIn) > 0 && Number(totalIn) < 0.01 - ? '< 0.01' - : toPrecision(totalIn, 2)} - - - {toRealSymbol(sellToken.symbol)} - - {isClientMobie() ? ( - - ) : ( - - )} - - - {Number(totalOut) > 0 && Number(totalOut) < 0.01 - ? '< 0.01' - : toPrecision(totalOut, 2)} - - - {toRealSymbol(buyToken.symbol)} - -
- -
- - - - - - - - - - {Number(swapIn) > 0 && Number(swapIn) < 0.01 - ? '< 0.01' - : toPrecision(swapIn, 2)} - - - {toRealSymbol(sellToken.symbol)} - - {isClientMobie() ? ( - - ) : ( - - )} - - - {Number(swapOut) > 0 && Number(swapOut) < 0.01 - ? '< 0.01' - : toPrecision(swapOut, 2)} - - - {toRealSymbol(buyToken.symbol)} - -
-
- ); - - return ( - <> -
{ - setHover(false); - }} - style={{ - zIndex: 20 - index, - }} - > -
{ - setHover(true); - }} - > - {sellTokenAmount} - {buyTokenAmount} - {feeTier} - {orderRate} - {created} - - {unclaim} - - {actions} -
- {hover && !ONLY_ZEROS.test(swapIn || '0') ? swapBanner : null} -
- -
- {/* title */} -
-
- {sellTokenAmount} - - {buyTokenAmount} -
- - {created} -
- {/* content */} -
- - } - value={feeTier} - /> - - - - - } - value={unclaim} - /> - - {unclaimTip} - -
- {actions} - {claimButton} -
-
- - {/* swap banner */} - {!ONLY_ZEROS.test(swapIn || '0') ? swapBanner : null} -
- - ); - } - - if (!activeOrder || activeOrder.length === 0) return null; - - const activeOrderSorting = (a: UserOrderInfo, b: UserOrderInfo) => { - if (activeSortBy === 'created') { - return sortOrderActive === 'desc' - ? Number(b.created_at) - Number(a.created_at) - : Number(a.created_at) - Number(b.created_at); - } else if (activeSortBy === 'unclaim') { - const unclaimA = toReadableNumber( - tokensMap[a.buy_token].decimals, - a.unclaimed_amount - ); - const unclaimB = toReadableNumber( - tokensMap[b.buy_token].decimals, - b.unclaimed_amount - ); - - return sortOrderActive === 'desc' - ? Number(unclaimB) - Number(unclaimA) - : Number(unclaimA) - Number(unclaimB); - } - }; - +function SwitchTabItem(props: any) { + const { active, clickEvent } = props; return (
-
- - - - - - {isMobile() && ( - - *Canceling will automatically claim your executed tokens. - - )} -
- -
- - - - - - - - - - - - - - - - - - - - - - - -
- - {activeOrder && - activeOrder.sort(activeOrderSorting).map((order, index) => { - return ( - - ); - })} + + + + {props.children}
); } @@ -3762,15 +2893,11 @@ function MyOrderComponent() { return (
- {/* */} -
diff --git a/src/pages/SwapPage.tsx b/src/pages/SwapPage.tsx index 98a08bb4a..b660d0100 100644 --- a/src/pages/SwapPage.tsx +++ b/src/pages/SwapPage.tsx @@ -2,6 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import SwapCard from '../components/swap/SwapCard'; import Loading from '../components/layout/Loading'; +import { IProTab } from '../components/swap/SwapProTab'; import { useTriTokens, useWhitelistTokens, @@ -20,12 +21,14 @@ import { import AdSwiper from '../components/layout/Swiper'; import LimitOrderCard from '../components/swap/LimitOrderCard'; import SwapRateChart from '../components/swap/SwapRateChart'; +import SwapLimitOrderChart from '../components/swap/SwapLimitOrderChart'; import { EstimateSwapView } from '../services/swap'; import { TradeRoute } from '../components/layout/SwapRoutes'; import { MarketList } from '../components/layout/SwapRoutes'; import MyOrderComponent from './Orderly/components/MyOrder'; import { useWalletSelector } from '../context/WalletSelectorContext'; import { useClientMobile } from '../utils/device'; +import { useDclPoolIdByCondition } from '../state/swapV3'; export const SWAP_MODE_KEY = 'SWAP_MODE_VALUE'; @@ -96,6 +99,9 @@ interface SwapProContextValue { swapMode: SWAP_MODE; forceEstimatePro?: boolean; setForceEstimatePro?: (e?: boolean) => void; + proTab?: IProTab; + setProTab?: Function; + dcl_pool_id?: string; } export const SwapProContext = createContext(null); @@ -224,13 +230,13 @@ function SwapPage() { ); const [forceEstimatePro, setForceEstimatePro] = useState(false); + const [proTab, setProTab] = useState('PRICE'); const changeSwapType = (type: SWAP_TYPE) => { setSwapType(type); sessionStorage.setItem(SWAP_TYPE_KEY, type); setForceEstimatePro(true); }; - useEffect(() => { const changeWindowCommonMenuCollapsed = (e: any) => { if (e?.[SWAP_TYPE_KEY]) { @@ -273,7 +279,7 @@ function SwapPage() { const [swapMode, setSwapMode] = useState( storageMode || SWAP_MODE.NORMAL ); - + const dcl_pool_id = useDclPoolIdByCondition('all'); useEffect(() => { if (swapMode === SWAP_MODE.LIMIT) { setLimitTokenTrigger(!limitTokenTrigger ? true : false); @@ -316,7 +322,6 @@ function SwapPage() { changeSwapType={changeSwapType} /> ); - return (
{swapType === SWAP_TYPE.Pro && (
- + {(swapMode === SWAP_MODE.NORMAL || + (SWAP_MODE.LIMIT && dcl_pool_id && proTab == 'PRICE')) && ( + + )} + {dcl_pool_id && + proTab == 'ORDER' && + swapMode === SWAP_MODE.LIMIT && ( + + )} {swapMode === SWAP_MODE.NORMAL ? ( <>
}
)} -
{swapMode === SWAP_MODE.NORMAL && ( diff --git a/src/pages/farms/FarmsBoostPageCopy.tsx b/src/pages/farms/FarmsBoostPageCopy.tsx new file mode 100644 index 000000000..1abda52d3 --- /dev/null +++ b/src/pages/farms/FarmsBoostPageCopy.tsx @@ -0,0 +1,100 @@ +import React, { useEffect, useRef, useState, useContext } from 'react'; +import FarmsHome from '~components/farm/FarmsHome'; +import FarmsDetail from '~components/farm/FarmsDetail'; +import FarmsDclDetail from '~components/farm/FarmsDclDetailCopy'; +import Loading, { BeatLoading } from '~components/layout/Loading'; +import { Seed, BoostConfig, UserSeedInfo } from '~services/farm'; +export default function FarmsBoosterPage(props: any) { + const [detailData, setDetailData] = useState(null); + const [tokenPriceList, setTokenPriceList] = useState(null); + const [loveSeed, serLoveSeed] = useState(null); + const [boostConfig, setBoostConfig] = useState(null); + const [user_data, set_user_data] = useState({}); + const [user_data_loading, set_user_data_loading] = useState(true); + const [dayVolumeMap, setDayVolumeMap] = useState({}); + const [all_seeds, set_all_seeds] = useState([]); + const paramId = decodeURIComponent(props.match.params.id || ''); + const is_dcl = paramId.indexOf('<>') > -1 || paramId.indexOf('|') > -1; + const getDetailData_user_data = (data: { + user_seeds_map: Record; + user_unclaimed_token_meta_map: Record; + user_unclaimed_map: Record; + }) => { + const { + user_seeds_map, + user_unclaimed_map, + user_unclaimed_token_meta_map, + } = data; + set_user_data({ + user_seeds_map, + user_unclaimed_map, + user_unclaimed_token_meta_map, + }); + set_user_data_loading(false); + }; + const getDayVolumeMap = (map: any) => { + setDayVolumeMap(map || {}); + }; + const getDetailData_boost_config = (boostConfig: BoostConfig) => { + setBoostConfig(boostConfig); + }; + const getDetailData = (data: { + detailData: Seed; + tokenPriceList: any; + loveSeed: Seed; + all_seeds: Seed[]; + }) => { + const { detailData, tokenPriceList, loveSeed, all_seeds } = data; + setDetailData(detailData); + setTokenPriceList(tokenPriceList); + serLoveSeed(loveSeed); + set_all_seeds(all_seeds); + }; + const emptyDetailData = () => { + setDetailData(null); + }; + const baseCondition = + paramId && + detailData && + tokenPriceList && + Object.keys(tokenPriceList).length > 0; + const showDetailPage = baseCondition && !is_dcl; + const showDclDetailPage = baseCondition && is_dcl; + const showLoading = paramId && !showDetailPage && !showDclDetailPage; + return ( + <> + + {showLoading ? : null} + {showDetailPage ? ( + + ) : null} + {showDclDetailPage ? ( + + ) : null} + + ); +} diff --git a/src/pages/pools/DetailsPage.tsx b/src/pages/pools/DetailsPage.tsx index 54609813b..c82271f26 100644 --- a/src/pages/pools/DetailsPage.tsx +++ b/src/pages/pools/DetailsPage.tsx @@ -13,6 +13,7 @@ import { TVLDataType, TVLType, useDayVolume, + useClassicPoolTransaction, useIndexerStatus, } from 'src/state/pool'; import { @@ -111,17 +112,12 @@ import { useWalletTokenBalances, useDepositableBalance, } from '../../state/token'; -import { SmallWallet } from '../../components/icon/SmallWallet'; import { scientificNotationToString, toInternationalCurrencySystemLongString, } from '../../utils/numbers'; import { isNotStablePool, canFarmV2, canFarmV1 } from '../../services/pool'; import { isStablePool, BLACKLIST_POOL_IDS } from '../../services/near'; -import { - getURLInfo, - checkAccountTip, -} from '../../components/layout/transactionTipPopUp'; export const REF_FI_PRE_LIQUIDITY_ID_KEY = 'REF_FI_PRE_LIQUIDITY_ID_VALUE'; @@ -167,6 +163,8 @@ import { } from 'src/services/commonV3'; import { openUrl } from '../../services/commonV3'; import { numberWithCommas } from '../Orderly/utiles'; +import { HiOutlineExternalLink, HiOutlineLink } from 'react-icons/hi'; +const STABLE_POOL_IDS = getConfig().STABLE_POOL_IDS; import { PoolRefreshModal } from './PoolRefreshModal'; interface ParamTypes { @@ -180,13 +178,8 @@ interface LocationTypes { export type ChartType = 'volume' | 'tvl' | 'liquidity'; const ONLY_ZEROS = /^0*\.?0*$/; -const getMax = function (id: string, max: string) { - return id !== WRAP_NEAR_CONTRACT_ID - ? max - : Number(max) <= 0.5 - ? '0' - : String(Number(max) - 0.5); -}; +const REF_FI_RECENT_TRANSACTION_TAB_KEY = 'REF_FI_RECENT_TRANSACTION_TAB_KEY'; + const formatDate = (rawDate: string) => { const date = rawDate .split('-') @@ -1391,6 +1384,327 @@ function MyShares({ ); } +type RencentTabKey = 'swap' | 'liquidity'; + +export function RecentTransactions({ + tokens, + pool_id, +}: { + tokens: TokenMetadata[]; + pool_id: string | number; +}) { + const { swapTransaction, liquidityTransactions } = useClassicPoolTransaction({ + pool_id, + }); + + const storedTab = sessionStorage.getItem( + REF_FI_RECENT_TRANSACTION_TAB_KEY + ) as RencentTabKey; + + const [tab, setTab] = useState(storedTab || 'swap'); + + const onChangeTab = (tab: RencentTabKey) => { + sessionStorage.setItem(REF_FI_RECENT_TRANSACTION_TAB_KEY, tab); + setTab(tab); + }; + + const renderSwapTransactions = swapTransaction.map((tx) => { + const swapIn = tokens.find((t) => t.id === tx.token_in); + + const swapOut = tokens.find((t) => t.id === tx.token_out); + + if (!swapIn || !swapOut) return null; + + const swapInAmount = toReadableNumber(swapIn.decimals, tx.swap_in); + const displayInAmount = + Number(swapInAmount) < 0.01 + ? '<0.01' + : numberWithCommas(toPrecision(swapInAmount, 6)); + + const swapOutAmount = toReadableNumber(swapOut.decimals, tx.swap_out); + + const displayOutAmount = + Number(swapOutAmount) < 0.01 + ? '<0.01' + : numberWithCommas(toPrecision(swapOutAmount, 6)); + + const txLink = ( + + + + ); + + return ( + + + + {displayInAmount} + + + + {toRealSymbol(swapIn.symbol)} + + + + + + {displayOutAmount} + + + + {toRealSymbol(swapOut.symbol)} + + + + + { + openUrl(`${getConfig().explorerUrl}/txns/${tx.tx_id}`); + }} + > + + {tx.timestamp} + + {txLink} + + + + ); + }); + + const renderLiquidityTransactions = liquidityTransactions.map((tx) => { + const { amounts } = tx; + const renderTokens: any[] = []; + const amountsObj: any[] = JSON.parse(amounts.replace(/\'/g, '"')); + amountsObj.forEach((amount: string, index) => { + if (Big(amount || 0).gt(0)) { + renderTokens.push({ + token: tokens[index], + amount: toReadableNumber(tokens[index].decimals, amountsObj[index]), + }); + } + }); + const txLink = ( + + + + ); + + return ( + + + + {tx.method_name.toLowerCase().indexOf('add') > -1 && 'Add'} + + {tx.method_name.toLowerCase().indexOf('remove') > -1 && 'Remove'} + + + + + {renderTokens.map((renderToken, index) => { + return ( + <> + + {formatNumber(renderToken.amount)} + + + + {toRealSymbol(renderToken.token.symbol)} + + {index !== renderTokens.length - 1 ? ( + + + ) : null} + + ); + })} + + + + { + openUrl(`${getConfig().explorerUrl}/txns/${tx.tx_id}`); + }} + > + + {tx.timestamp} + + {txLink} + + + + ); + }); + + const renderTx = + tab === 'swap' ? renderSwapTransactions : renderLiquidityTransactions; + + return ( + <> +
+
+ +
+ +
+
{ + e.preventDefault(); + e.stopPropagation(); + onChangeTab('swap'); + }} + > + +
+ +
{ + e.preventDefault(); + e.stopPropagation(); + onChangeTab('liquidity'); + }} + > + +
+
+
+ +
+
+
+ {tab === 'liquidity' && ( + + )} + {tab === 'swap' && ( + + )} +
+ +
+ {tab === 'liquidity' && ( + + )} + {tab === 'swap' && ( + + )} +
+ +
+ +
+
+ +
+ + + + + + + + + {renderTx} +
+ {tab === 'liquidity' && ( + + )} + {tab === 'swap' && ( + + )} + + {tab === 'liquidity' && ( + + )} + {tab === 'swap' && ( + + )} + + +
+
+
+ + ); +} + export const ChartChangeButton = ({ chartDisplay, setChartDisplay, @@ -1406,10 +1720,25 @@ export const ChartChangeButton = ({ }) => { return (
+ {showLiqudityButton ? ( + + ) : null} - {showLiqudityButton ? ( - - ) : null}
); }; @@ -1469,9 +1783,21 @@ export const MobileChartChangeButton = ({ return (
+ {showLiqudityButton ? ( + setChartDisplay('liquidity')} + className={`text-sm text-white text-opacity-60 pb-2.5 border-b-4 border-transparent px-2.5 ${ + chartDisplay === 'liquidity' + ? 'border-senderHot' + : 'border-transparent' + }`} + > + + + ) : null} setChartDisplay('tvl')} - className={`text-sm text-white text-opacity-60 pb-2.5 border-b-4 pr-2.5 ${ + className={`text-sm text-white text-opacity-60 pb-2.5 border-b-4 px-2.5 mx-3 ${ chartDisplay === 'tvl' ? 'border-senderHot' : 'border-transparent' }`} > @@ -1479,7 +1805,7 @@ export const MobileChartChangeButton = ({ setChartDisplay('volume')} - className={`text-sm text-white text-opacity-60 pb-2.5 border-b-4 px-2.5 ml-3 ${ + className={`text-sm text-white text-opacity-60 pb-2.5 border-b-4 px-2.5 ${ chartDisplay === 'volume' ? 'border-senderHot' : 'border-transparent' @@ -1487,18 +1813,6 @@ export const MobileChartChangeButton = ({ > - {showLiqudityButton ? ( - setChartDisplay('liquidity')} - className={`text-sm text-white text-opacity-60 pb-2.5 border-b-4 border-transparent px-2.5 ml-3 ${ - chartDisplay === 'liquidity' - ? 'border-senderHot' - : 'border-transparent' - }`} - > - - - ) : null}
@@ -2289,7 +2603,7 @@ export default function PoolDetailsPage() { width="w-full" className="relative rounded-2xl mr-4 mb-4 h-full flex flex-col justify-center items-center" padding="px-7 py-5" - bgcolor={isClientMobie() ? 'bg-transparent' : 'bg-cardBg'} + bgcolor={'bg-transparent'} style={{ height: isClientMobie() ? '370px' : '470px', }} @@ -2425,7 +2739,7 @@ export default function PoolDetailsPage() { />
-
+
+ +
); } + +export const formatNumber = (v: string | number) => { + const big = Big(v || 0); + if (big.eq(0)) { + return '0'; + } else if (big.lt(0.001)) { + return '<0.001'; + } else { + return big.toFixed(3, 1); + } +}; diff --git a/src/pages/pools/LiquidityPage.tsx b/src/pages/pools/LiquidityPage.tsx index b6c921183..1cf964dcc 100644 --- a/src/pages/pools/LiquidityPage.tsx +++ b/src/pages/pools/LiquidityPage.tsx @@ -3,33 +3,27 @@ import React, { useEffect, useRef, useCallback, - useMemo, useContext, createContext, } from 'react'; - -import db from '../../store/RefDatabase'; - import ReactTooltip from 'react-tooltip'; -import InfiniteScroll from 'react-infinite-scroll-component'; import { ShareInFarm } from '../../components/layout/ShareInFarm'; import { classificationOfCoins_key, classificationOfCoins, Seed, } from '../../services/farm'; -import { ArrowDown, ArrowDownLarge } from '../../components/icon'; +import { ArrowDownLarge } from '../../components/icon'; import { useHistory } from 'react-router'; import { Card } from '../../components/card/Card'; -import { find, isNumber, runInContext, values } from 'lodash'; +import { find } from 'lodash'; import { SelectModal } from '../../components/layout/SelectModal'; import { useAllPools, usePools, - useMorePoolIds, - useAllWatchList, useWatchPools, useV3VolumesPools, + useDCLTopBinFee, useIndexerStatus, } from '../../state/pool'; import Loading from '../../components/layout/Loading'; @@ -45,11 +39,9 @@ import { canFarm, Pool, isNotStablePool, canFarms } from '../../services/pool'; import { calculateFeePercent, toPrecision, - toReadableNumber, toInternationalCurrencySystem, } from '../../utils/numbers'; import { CheckedTick, CheckedEmpty } from '../../components/icon/CheckBox'; -import { toRealSymbol } from '../../utils/token'; import { FormattedMessage, useIntl } from 'react-intl'; import { DownArrowLight, @@ -57,30 +49,18 @@ import { UpArrowDeep, UpArrowLight, } from '../../components/icon'; -import { FarmStamp } from '../../components/icon/FarmStamp'; -import { - SolidButton, - FarmButton, - GradientButton, -} from '../../components/button/Button'; +import { SolidButton } from '../../components/button/Button'; import { NEAR_CLASS_STABLE_POOL_IDS, - wallet, REF_UNI_V3_SWAP_CONTRACT_ID, USDTT_USDCC_USDT_USDC_POOL_ID, } from '../../services/near'; import { WatchListStartFull } from '../../components/icon/WatchListStar'; -import { PolygonGrayDown } from '../../components/icon/Polygon'; import _, { orderBy, sortBy, filter } from 'lodash'; -import QuestionMark from '../../components/farm/QuestionMark'; import { useInView } from 'react-intersection-observer'; import { QuestionTip } from '../../components/layout/TipWrapper'; import { FilterIcon } from '../../components/icon/PoolFilter'; -import { - TokenMetadata, - REF_META_DATA, - ftGetTokenMetadata, -} from '../../services/ft-contract'; +import { TokenMetadata, REF_META_DATA } from '../../services/ft-contract'; import { scientificNotationToString, percent, @@ -112,10 +92,7 @@ import { VEARROW } from '../../components/icon/Referendum'; import getConfig from '../../services/config'; import { AddPoolModal } from './AddPoolPage'; import { useWalletSelector } from '../../context/WalletSelectorContext'; -import { - checkAccountTip, - getURLInfo, -} from '../../components/layout/transactionTipPopUp'; +import { getURLInfo } from '../../components/layout/transactionTipPopUp'; import { checkTransactionStatus } from '../../services/swap'; import { useCanFarmV2 } from '../../state/farm'; import { PoolData, useAllStablePoolData } from '../../state/sauce'; @@ -134,8 +111,9 @@ import { PoolInfo } from 'src/services/swapV3'; import { SelectModalV2 } from '../../components/layout/SelectModal'; import { FarmStampNew } from '../../components/icon/FarmStamp'; import { ALL_STABLE_POOL_IDS } from '../../services/near'; -import { BoostSeeds, WatchList } from '../../store/RefDatabase'; +import { WatchList } from '../../store/RefDatabase'; import { REF_FI_CONTRACT_ID } from '../../services/near'; +import { FarmBoost } from '../../services/farm'; import { get_all_seeds, @@ -146,11 +124,12 @@ import { openUrl, } from '../../services/commonV3'; +import { formatPercentage } from '../../components/d3Chart/utils'; import { AiFillStar, RiArrowRightSLine } from '../../components/reactIcons'; -import { useTokenPriceList } from '../../state/token'; import { useSeedFarmsByPools } from '../../state/pool'; import { PoolRefreshModal } from './PoolRefreshModal'; +import { useTokenPriceList } from '../../state/token'; import { REF_FI_POOL_ACTIVE_TAB, getPoolFeeApr, @@ -518,6 +497,11 @@ function MobilePoolRowV2({ const { ref } = useInView(); const curRowTokens = useTokens([pool.token_x, pool.token_y], tokens); + const displayOfTopBinApr = useDCLTopBinFee({ + pool, + way: 'value', + }); + pool.top_bin_apr = displayOfTopBinApr; const history = useHistory(); @@ -538,6 +522,8 @@ function MobilePoolRowV2({ else if (sortBy === 'fee') return `${calculateFeePercent(value / 100)}%`; else if (sortBy === 'volume_24h') { return geth24volume(); + } else if (sortBy === 'top_bin_apr' || (sortBy == 'apr' && mark)) { + return value?.toString() == '-' ? '-' : formatPercentage(value); } else return '/'; }; function goDetailV2() { @@ -554,6 +540,27 @@ function MobilePoolRowV2({ return '$' + toInternationalCurrencySystem(v.toString(), 2); } } + function getFarmApr() { + if (relatedSeed) { + const farms = relatedSeed.farmList; + let apr = 0; + const allPendingFarms = isPending(relatedSeed); + farms.forEach(function (item: FarmBoost) { + const pendingFarm = + item.status == 'Created' || item.status == 'Pending'; + if (allPendingFarms || (!allPendingFarms && !pendingFarm)) { + apr = +new BigNumber(apr).plus(item.apr).toFixed(); + } + }); + apr = apr * 100; + if (+apr == 0) { + return '-'; + } else { + return '+' + toPrecision(apr.toString(), 2) + '%'; + } + } + return ''; + } return (
-
{showSortedValue({ sortBy, value: pool[sortBy] })}
+
+ {showSortedValue({ + sortBy, + value: + sortBy == 'apr' && mark ? pool['top_bin_apr'] : pool[sortBy], + })} + + {relatedSeed && + (sortBy == 'top_bin_apr' || (sortBy == 'apr' && mark)) && ( + + {getFarmApr()} + + )} +
); } + function MobileWatchListCard({ watchPools, poolTokenMetas, @@ -723,7 +744,7 @@ function MobileWatchListCard({ {sortBy === 'apr' && (
- *Pool Fee APY + Farm Rewards APR + *Pool Fee APY/Top Bin APR + Farm Rewards APR
)}
@@ -731,7 +752,7 @@ function MobileWatchListCard({ {watchAllPools.map((pool: any, i: number) => { if (pool.id?.toString()) { return ( -
+
@@ -855,6 +876,8 @@ function MobileLiquidityPage({ const v2 = volumes[p2.pool_id] ? parseFloat(volumes[p2.pool_id]) : 0; + const top1 = +(p1.top_bin_apr == '-' ? '0' : p1.top_bin_apr); + const top2 = +(p2.top_bin_apr == '-' ? '0' : p2.top_bin_apr); if (v2Order === 'desc') { if (v2SortBy === 'tvl') { return tvl2 - tvl1; @@ -862,6 +885,8 @@ function MobileLiquidityPage({ return f2 - f1; } else if (v2SortBy === 'volume_24h') { return v2 - v1; + } else if (v2SortBy == 'top_bin_apr') { + return top2 - top1; } } else if (v2Order === 'asc') { if (v2SortBy === 'tvl') { @@ -870,6 +895,8 @@ function MobileLiquidityPage({ return f1 - f2; } else if (v2SortBy === 'volume_24h') { return v1 - v2; + } else if (v2SortBy == 'top_bin_apr') { + return top1 - top2; } } }; @@ -983,7 +1010,7 @@ function MobileLiquidityPage({ {!!getConfig().REF_VE_CONTRACT_ID ? (
- + {sortBy === 'apr' && (
- *Pool Fee APY + Farm Rewards APR + *Pool Fee APY/Top Bin APR + Farm Rewards APR
)}
@@ -1501,7 +1528,7 @@ function MobileLiquidityPage({ pool={pool} sortBy={v2SortBy} watched={!!find(watchV2Pools, { pool_id: pool.pool_id })} - key={i + '-mobile-pool-row-v2'} + key={pool.pool_id + '-mobile-pool-row-v2'} h24volume={volumes[pool.pool_id]} relatedSeed={do_farms_v2_poos[pool.pool_id]} /> @@ -1516,6 +1543,8 @@ function MobileLiquidityPage({ searchBy={tokenName} volumes={volumes} watchPools={watchPools} + farmCounts={farmCounts} + farmAprById={farmAprById} /> )}
@@ -1533,6 +1562,26 @@ function MobileLiquidityPage({ ); } +export const getPoolListV2FarmAprTip = () => { + return ` +
+
+ Top Bin APR +
+ +
+ + + Farm Rewards APR +
+ + + +
+`; +}; + const PoolIdNotExist = () => { const intl = useIntl(); return ( @@ -1624,9 +1673,8 @@ function PoolRow({
{calculateFeePercent(pool.fee)}%
-
; tokens = sort_tokens_by_base(tokens); @@ -1747,6 +1802,27 @@ function PoolRowV2({ return '$' + toInternationalCurrencySystem(v.toString(), 2); } } + function getFarmApr() { + if (relatedSeed) { + const farms = relatedSeed.farmList; + let apr = 0; + const allPendingFarms = isPending(relatedSeed); + farms.forEach(function (item: FarmBoost) { + const pendingFarm = + item.status == 'Created' || item.status == 'Pending'; + if (allPendingFarms || (!allPendingFarms && !pendingFarm)) { + apr = +new BigNumber(apr).plus(item.apr).toFixed(); + } + }); + apr = apr * 100; + if (+apr == 0) { + return '-'; + } else { + return '+' + toPrecision(apr.toString(), 2) + '%'; + } + } + return ''; + } return (
{calculateFeePercent(pool.fee / 100)}%
- - {mark && ( +
- / + {displayOfTopBinApr} + {relatedSeed && ( + {getFarmApr()} + )} + {relatedSeed && ( + + )}
- )} +
{geth24volume()}
); } - -function LiquidityPage_({ +function PcLiquidityPage({ pools, sortBy, tokenName, @@ -2032,7 +2132,7 @@ function LiquidityPage_({ const intl = useIntl(); const inputRef = useRef(null); - const allPoolsV2 = useAllPoolsV2(); + let allPoolsV2 = useAllPoolsV2(); const [tvlV2, setTvlV2] = useState(); @@ -2117,7 +2217,6 @@ function LiquidityPage_({ setFarmCountStar(count); }); }, []); - const tokensStar = [REF_META_DATA, unwrapedNear]; const poolReSortingFunc = (p1: Pool, p2: Pool) => { const v1 = volumes[p1.id] ? parseFloat(volumes[p1.id]) : 0; @@ -2158,6 +2257,8 @@ function LiquidityPage_({ const v2 = volumes[p2.pool_id] ? parseFloat(volumes[p2.pool_id]) : 0; + const top_bin_apr1 = p1.top_bin_apr; + const top_bin_apr2 = p2.top_bin_apr; if (v2Order === 'desc') { if (v2SortBy === 'tvl') { return tvl2 - tvl1; @@ -2165,6 +2266,8 @@ function LiquidityPage_({ return f2 - f1; } else if (v2SortBy === 'volume_24h') { return v2 - v1; + } else if (v2SortBy === 'top_bin_apr') { + return Number(top_bin_apr2) - Number(top_bin_apr1); } } else if (v2Order === 'asc') { if (v2SortBy === 'tvl') { @@ -2173,6 +2276,8 @@ function LiquidityPage_({ return f1 - f2; } else if (v2SortBy === 'volume_24h') { return v1 - v2; + } else if (v2SortBy === 'top_bin_apr') { + return Number(top_bin_apr1) - Number(top_bin_apr2); } } }; @@ -2239,7 +2344,7 @@ function LiquidityPage_({