diff --git a/README.md b/README.md index ebfc391d8..bd5b086e7 100644 --- a/README.md +++ b/README.md @@ -270,6 +270,7 @@ The file `common.ts` with type [`AppConfig`](src/config/types.ts) contains impor - `ui` - `priceChart`: use `tradingView` chart or `native` chart for token pair price history. You need to provide a backend with price history endpoint to support `native` view. - `useGradientBranding`: Flag to enable gradient styles for buttons. + - `tradeCount`: Display the amount of trades in the explorer page. #### Gas token different than native token diff --git a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png index 5933b7f7d..c95fddb9f 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png and b/e2e/screenshots/simulator/recurring/Recurring_limit_limit/form.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png index 89d0fece3..4d71f9226 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png and b/e2e/screenshots/simulator/recurring/Recurring_range_limit/simulator-input-price.png differ diff --git a/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png b/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png index 9ddf9407d..46cd73b76 100644 Binary files a/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png and b/e2e/screenshots/simulator/recurring/Recurring_range_range/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png index aba9d4f96..1ae8ad3b3 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/my-strategy.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/my-strategy.png index 90d4fd163..6416853ec 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/my-strategy.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/create/my-strategy.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png index eae377338..33c80455b 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/deposit/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png index 158fd6ccd..10455bcc5 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png index 639eb9a2d..a12037b84 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_limit/undercut/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png index 6c2356a6b..7e6f17b0d 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png index 985fda28e..dc6d8a227 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/create/my-strategy.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png index 2c96c5e0a..72ae10ce4 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/deposit/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png b/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png index 4bcc2cd3d..4c721a567 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png and b/e2e/screenshots/strategy/disposable/Disposable_buy_range/undercut/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png index 6490a1c0a..5d1cc9e1f 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/form.png differ diff --git a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/my-strategy.png b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/my-strategy.png index 7c0f1f649..b6dfe8dd0 100644 Binary files a/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/my-strategy.png and b/e2e/screenshots/strategy/disposable/Disposable_sell_limit/create/my-strategy.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png index 54ec6d79b..ff3631223 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/create/form.png differ diff --git a/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png b/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png index e62c7b947..e75a91ee4 100644 Binary files a/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png and b/e2e/screenshots/strategy/overlapping/Overlapping/editPrices/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png index 7421bdc3e..5c4baf4f9 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png index 437f81eaa..b7bfdd457 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_limit/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png index 70edeab14..63a7dbcaa 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png b/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png index 9a5bc5c3c..0fbf7c656 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png and b/e2e/screenshots/strategy/recurring/Recurring_limit_range/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png index bdd4978ba..78ff97956 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/create/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png index df22fedba..c09fe4d52 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png index 8ae573e37..10124885b 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/duplicate/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png index 24bb73f6d..82cc914f6 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_limit/undercut/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png index 581db6783..e2658025c 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/deposit/form.png differ diff --git a/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png b/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png index ef94d0860..c6085078d 100644 Binary files a/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png and b/e2e/screenshots/strategy/recurring/Recurring_range_range/undercut/form.png differ diff --git a/src/components/debug/DebugTransferNFT.tsx b/src/components/debug/DebugTransferNFT.tsx index 071aa5c67..c174db394 100644 --- a/src/components/debug/DebugTransferNFT.tsx +++ b/src/components/debug/DebugTransferNFT.tsx @@ -31,7 +31,9 @@ export const DebugTransferNFT = () => { inputId ); await tx.wait(); - await cache.invalidateQueries({ queryKey: QueryKey.strategies(user) }); + await cache.invalidateQueries({ + queryKey: QueryKey.strategiesByUser(user), + }); setIsSuccess(true); } catch (e) { console.error('failed to transfer NFT', e); diff --git a/src/components/explorer/ExplorerHeader.tsx b/src/components/explorer/ExplorerHeader.tsx new file mode 100644 index 000000000..38cb2276f --- /dev/null +++ b/src/components/explorer/ExplorerHeader.tsx @@ -0,0 +1,333 @@ +import { buttonStyles } from 'components/common/button/buttonStyles'; +import { TokensOverlap } from 'components/common/tokensOverlap'; +import { useTokens } from 'hooks/useTokens'; +import { Strategy, useGetStrategyList } from 'libs/queries'; +import { + PairTrade, + Trending, + useTrending, +} from 'libs/queries/extApi/tradeCount'; +import { Link } from 'libs/routing'; +import { CSSProperties, useEffect, useRef } from 'react'; +import { toPairSlug } from 'utils/pairSearch'; + +const useTrendingPairs = (trending?: Trending) => { + const { tokensMap } = useTokens(); + if (!trending) return { isLoading: true, data: [] }; + const pairs: Record = {}; + for (const trade of trending?.pairCount ?? []) { + pairs[trade.pairAddresses] ||= trade; + } + const list = Object.values(pairs) + .filter((pair) => !!pair.pairTrades_24h) + .sort((a, b) => b.pairTrades - a.pairTrades) + .splice(0, 3); + + // If there are less than 3, pick the remaining best + if (list.length < 3) { + const remaining = Object.values(pairs) + .filter((pair) => !!pair.pairTrades_24h) + .sort((a, b) => b.pairTrades - a.pairTrades) + .splice(0, 3 - list.length); + list.push(...remaining); + } + + // Sort again in case we had to add more + const data = list + .sort((a, b) => b.pairTrades - a.pairTrades) + .map((pair) => ({ + pairAddress: pair.pairAddresses, + base: tokensMap.get(pair.token0.toLowerCase())!, + quote: tokensMap.get(pair.token1.toLowerCase())!, + trades: pair.pairTrades, + })); + return { isLoading: false, data }; +}; + +interface StrategyWithTradeCount extends Strategy { + trades: number; +} +const useTrendStrategies = ( + trending?: Trending +): { isLoading: boolean; data: StrategyWithTradeCount[] } => { + const trades = trending?.tradeCount ?? []; + const list = trades + .filter((t) => !!t.strategyTrades_24h) + .sort((a, b) => b.strategyTrades - a.strategyTrades) + .splice(0, 3); + + // If there are less than 3, pick the remaining best + if (list.length < 3) { + const remaining = trades + .filter((t) => !!t.strategyTrades_24h) + .sort((a, b) => b.strategyTrades - a.strategyTrades) + .splice(0, 3 - list.length); + list.push(...remaining); + } + + const record: Record = {}; + for (const item of list) { + record[item.id] = item.strategyTrades; + } + const ids = list.map((s) => s.id); + const query = useGetStrategyList(ids); + if (query.isLoading) return { isLoading: true, data: [] }; + + const data = (query.data ?? []).map((strategy) => ({ + ...strategy, + trades: record[strategy.id], + })); + return { isLoading: false, data }; +}; + +export const ExplorerHeader = () => { + const { data: trending, isLoading, isError } = useTrending(); + const trendingStrategies = useTrendStrategies(trending); + const trendingPairs = useTrendingPairs(trending); + const strategies = trendingStrategies.data; + const pairs = trendingPairs.data; + + const strategiesLoading = trendingStrategies.isLoading || isLoading; + const pairLoading = trendingPairs.isLoading || isLoading; + if (isError) return; + return ( +
+
+

+ Total Trades +

+ +
+ + Create Strategy + + + Trade + +
+
+
+

Popular Pairs

+ + + + + + + + + {pairLoading && + [1, 2, 3].map((id) => ( + + + + + ))} + {pairs.map(({ pairAddress, base, quote, trades }) => ( + + + + + ))} + +
Token PairTrades
+ + + +
+ +
+ + {base?.symbol} + / + {quote?.symbol} +
+ +
+ + {formatter.format(trades)} + +
+
+
+

+ Trending Strategies +

+ + + + + + + + + {strategiesLoading && + [1, 2, 3].map((id) => ( + + + + + ))} + {strategies.map(({ id, idDisplay, base, quote, trades }) => ( + + + + + ))} + +
IDTrades
+ + + +
+ +
+ + {idDisplay} +
+ +
+ + {formatter.format(trades)} + +
+
+
+ ); +}; + +const Loading = (style: CSSProperties) => ( +
+
+
+); + +const formatter = new Intl.NumberFormat(undefined, { + maximumFractionDigits: 0, +}); + +interface TradesProps { + trades?: number; +} + +const Trades = ({ trades }: TradesProps) => { + const ref = useRef(null); + const anims = useRef[]>(); + const lastTrades = useRef(0); + const initDelta = 60; + + useEffect(() => { + if (typeof trades !== 'number') return; + let tradesChanged = false; + const start = async () => { + const from = lastTrades.current || trades - initDelta; + const to = trades; + const letters = ref.current!.children; + // Initial animation + if (!lastTrades.current) { + const initAnims: Promise[] = []; + const next = formatter.format(from).split(''); + for (let i = 0; i < next.length; i++) { + const v = next[i]; + if (!'0123456789'.includes(v)) continue; + const anim = letters[i]?.animate( + [{ transform: `translateY(-${v}0%)` }], + { + duration: 1000, + delay: i * 100, + fill: 'forwards', + easing: 'cubic-bezier(1,-0.54,.65,1.46)', + } + ); + if (anim) initAnims.push(anim.finished); + } + await Promise.allSettled(initAnims); + } + // Wait for lingering animations if any + await Promise.allSettled(anims.current ?? []); + anims.current = []; + let previous = formatter.format(from - 1).split(''); + for (let value = from; value <= to; value++) { + const next = formatter.format(value).split(''); + for (let i = 0; i < next.length; i++) { + if (tradesChanged) return; + const v = next[i]; + if (!'0123456789'.includes(v)) continue; + if (previous[i] === next[i]) continue; + const anim = letters[i].animate( + [{ transform: `translateY(-${v}0%)` }], + { + duration: 1000, + delay: 2000, + fill: 'forwards', + easing: 'cubic-bezier(1,.11,.55,.79)', + } + ); + anims.current.push(anim.finished); + } + previous = next; + await Promise.allSettled(anims.current ?? []); + lastTrades.current = value; + } + }; + start(); + return () => { + tradesChanged = true; + }; + }, [trades]); + + if (typeof trades !== 'number') { + return ; + } + + const initial = trades ? formatter.format(trades - initDelta) : '0'; + return ( +

+ {initial.split('').map((v, i) => { + if (!'0123456789'.includes(v)) return {v}; + return ( + + 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + + ); + })} +

+ ); +}; diff --git a/src/components/strategies/common/utils.ts b/src/components/strategies/common/utils.ts index ec9614337..5e94802d8 100644 --- a/src/components/strategies/common/utils.ts +++ b/src/components/strategies/common/utils.ts @@ -4,6 +4,7 @@ import { Token } from 'libs/tokens'; import { formatNumber } from 'utils/helpers'; import { BaseOrder } from './types'; import { endOfDay, getUnixTime, startOfDay, subDays } from 'date-fns'; +import { StrategyType } from 'libs/routing'; type StrategyOrderInput = | { min: string; max: string } @@ -35,7 +36,7 @@ export const isDisposableStrategy = (strategy: Strategy) => { return false; }; -export const getStrategyType = (strategy: Strategy) => { +export const getStrategyType = (strategy: Strategy): StrategyType => { if (isOverlappingStrategy(strategy)) return 'overlapping'; if (isDisposableStrategy(strategy)) return 'disposable'; return 'recurring'; diff --git a/src/components/strategies/edit/EditStrategyForm.tsx b/src/components/strategies/edit/EditStrategyForm.tsx index 1d099b4ae..0a30d02f2 100644 --- a/src/components/strategies/edit/EditStrategyForm.tsx +++ b/src/components/strategies/edit/EditStrategyForm.tsx @@ -158,7 +158,7 @@ export const EditStrategyForm: FC = (props) => { console.log('tx hash', tx.hash); await tx.wait(); cache.invalidateQueries({ - queryKey: QueryKey.strategies(user), + queryKey: QueryKey.strategiesByUser(user), }); carbonEvents.strategyEdit.strategyEditPrices({ ...strategyEventData, diff --git a/src/components/strategies/useDeleteStrategy.ts b/src/components/strategies/useDeleteStrategy.ts index de0a2bcb9..98c7da231 100644 --- a/src/components/strategies/useDeleteStrategy.ts +++ b/src/components/strategies/useDeleteStrategy.ts @@ -46,7 +46,7 @@ export const useDeleteStrategy = () => { await tx.wait(); void cache.invalidateQueries({ - queryKey: QueryKey.strategies(user), + queryKey: QueryKey.strategiesByUser(user), }); console.log('tx confirmed'); successEventsCb?.(); diff --git a/src/components/strategies/usePauseStrategy.ts b/src/components/strategies/usePauseStrategy.ts index 0cbcc520e..9b22e17cd 100644 --- a/src/components/strategies/usePauseStrategy.ts +++ b/src/components/strategies/usePauseStrategy.ts @@ -52,7 +52,7 @@ export const usePauseStrategy = () => { await tx.wait(); void cache.invalidateQueries({ - queryKey: QueryKey.strategies(user), + queryKey: QueryKey.strategiesByUser(user), }); console.log('tx confirmed'); successEventsCb?.(); diff --git a/src/config/blast/common.ts b/src/config/blast/common.ts index d14e23a92..6d0cf3742 100644 --- a/src/config/blast/common.ts +++ b/src/config/blast/common.ts @@ -130,5 +130,6 @@ export const commonConfig: AppConfig = { ui: { priceChart: 'tradingView', useGradientBranding: true, + tradeCount: false, }, }; diff --git a/src/config/celo/common.ts b/src/config/celo/common.ts index 32b9f8c1d..1bdb04ce7 100644 --- a/src/config/celo/common.ts +++ b/src/config/celo/common.ts @@ -141,5 +141,6 @@ export const commonConfig: AppConfig = { ui: { priceChart: 'native', useGradientBranding: true, + tradeCount: true, }, }; diff --git a/src/config/configSchema.ts b/src/config/configSchema.ts index 529778896..5177216be 100644 --- a/src/config/configSchema.ts +++ b/src/config/configSchema.ts @@ -107,5 +107,6 @@ export const AppConfigSchema = v.object({ ui: v.object({ priceChart: v.union([v.literal('native'), v.literal('tradingView')]), useGradientBranding: v.optional(v.boolean()), + tradeCount: v.optional(v.boolean()), }), }); diff --git a/src/config/ethereum/common.ts b/src/config/ethereum/common.ts index 4a09950f4..a770c705c 100644 --- a/src/config/ethereum/common.ts +++ b/src/config/ethereum/common.ts @@ -216,5 +216,6 @@ export const commonConfig: AppConfig = { ui: { priceChart: 'native', useGradientBranding: true, + tradeCount: false, }, }; diff --git a/src/config/sei/common.ts b/src/config/sei/common.ts index 340acfff7..befecdfee 100644 --- a/src/config/sei/common.ts +++ b/src/config/sei/common.ts @@ -117,5 +117,6 @@ export const commonConfig: AppConfig = { ui: { priceChart: 'native', useGradientBranding: true, + tradeCount: true, }, }; diff --git a/src/hooks/useStrategies.tsx b/src/hooks/useStrategies.tsx index a3ee61a9c..2770671c3 100644 --- a/src/hooks/useStrategies.tsx +++ b/src/hooks/useStrategies.tsx @@ -111,11 +111,7 @@ export const useStrategiesWithFiat = ( const price = priceQueries[i].data?.[selectedFiatCurrency]; prices[address] = price; } - const tradeCountQuery = useTradeCount(); - const tradeCount: Record = {}; - for (const item of tradeCountQuery.data ?? []) { - tradeCount[item.strategyId] = item.tradeCount; - } + const tradeCount = useTradeCount(); return strategies.map((strategy) => { const basePrice = new SafeDecimal(prices[strategy.base.address] ?? 0); const quotePrice = new SafeDecimal(prices[strategy.quote.address] ?? 0); diff --git a/src/libs/queries/extApi/tradeCount.ts b/src/libs/queries/extApi/tradeCount.ts index 2c44ee63e..1cf30770a 100644 --- a/src/libs/queries/extApi/tradeCount.ts +++ b/src/libs/queries/extApi/tradeCount.ts @@ -3,15 +3,49 @@ import { QueryKey } from 'libs/queries/queryKey'; import { ONE_HOUR_IN_MS } from 'utils/time'; import { carbonApi } from 'utils/carbonApi'; -export interface TradeCount { - strategyId: string; - tradeCount: number; +export interface StrategyTrade { + id: string; + strategyTrades: number; + strategyTrades_24h: number; + token0: string; + token1: string; + symbol0: string; + symbol1: string; + pairSymbol: string; + pairAddresses: string; +} +export interface PairTrade { + pairId: string; + pairTrades: number; + pairTrades_24h: number; + token0: string; + token1: string; + symbol0: string; + symbol1: string; + pairSymbol: string; + pairAddresses: string; } -export const useTradeCount = () => { +export interface Trending { + totalTradeCount: number; + tradeCount: StrategyTrade[]; + pairCount: PairTrade[]; +} + +export const useTrending = () => { return useQuery({ - queryKey: QueryKey.tradeCount(), - queryFn: carbonApi.getTradeCount, + queryKey: QueryKey.trending(), + queryFn: carbonApi.getTrending, staleTime: ONE_HOUR_IN_MS, + refetchInterval: 120_000, }); }; + +export const useTradeCount = () => { + const query = useTrending(); + const tradeCount: Record = {}; + for (const item of query.data?.tradeCount ?? []) { + tradeCount[item.id] = item.strategyTrades; + } + return tradeCount; +}; diff --git a/src/libs/queries/queryKey.ts b/src/libs/queries/queryKey.ts index f6502e975..a800d9475 100644 --- a/src/libs/queries/queryKey.ts +++ b/src/libs/queries/queryKey.ts @@ -39,10 +39,11 @@ export namespace QueryKey { 'token-price-history', params, ]; - export const tradeCount = () => [...extAPI, 'trade-count']; + export const trending = () => [...extAPI, 'trending']; export const strategy = (id: string) => [...sdk, 'strategy', id]; - export const strategies = (user?: string) => [ + export const strategyList = (ids: string[]) => [...sdk, 'strategy', ...ids]; + export const strategiesByUser = (user?: string) => [ ...sdk, 'strategies', 'user', diff --git a/src/libs/queries/sdk/strategy.ts b/src/libs/queries/sdk/strategy.ts index 4d9b46191..79f87c679 100644 --- a/src/libs/queries/sdk/strategy.ts +++ b/src/libs/queries/sdk/strategy.ts @@ -152,7 +152,7 @@ export const useGetUserStrategies = ({ user }: Props) => { const isZeroAddress = address === config.addresses.tokens.ZERO; return useQuery({ - queryKey: QueryKey.strategies(address), + queryKey: QueryKey.strategiesByUser(address), queryFn: async () => { if (!address || !isValidAddress || isZeroAddress) return []; @@ -170,6 +170,29 @@ export const useGetUserStrategies = ({ user }: Props) => { }); }; +export const useGetStrategyList = (ids: string[]) => { + const { isInitialized } = useCarbonInit(); + const { tokens, getTokenById, importToken } = useTokens(); + const { Token } = useContract(); + + return useQuery({ + queryKey: QueryKey.strategyList(ids), + queryFn: async () => { + const getStrategies = ids.map((id) => carbonSDK.getStrategy(id)); + const strategies = await Promise.all(getStrategies); + return buildStrategiesHelper({ + strategies, + getTokenById, + importToken, + Token, + }); + }, + enabled: tokens.length > 0 && isInitialized, + staleTime: ONE_DAY_IN_MS, + retry: false, + }); +}; + export const useGetStrategy = (id: string) => { const { isInitialized } = useCarbonInit(); const { tokens, getTokenById, importToken } = useTokens(); diff --git a/src/pages/explorer/index.tsx b/src/pages/explorer/index.tsx index 69230173a..50cf7f712 100644 --- a/src/pages/explorer/index.tsx +++ b/src/pages/explorer/index.tsx @@ -7,9 +7,11 @@ import { } from 'components/explorer'; import { StrategyProvider, useStrategyCtx } from 'hooks/useStrategies'; import { ExplorerTabs } from 'components/explorer/ExplorerTabs'; +import { ExplorerHeader } from 'components/explorer/ExplorerHeader'; import { useEffect, useState } from 'react'; import { explorerEvents } from 'services/events/explorerEvents'; import { lsService } from 'services/localeStorage'; +import config from 'config'; const url = '/explore/$type'; export const ExplorerPage = () => { @@ -34,6 +36,7 @@ export const ExplorerPage = () => { + {config.ui.tradeCount && }
{slug && } diff --git a/src/store/useTokensStore.ts b/src/store/useTokensStore.ts index bd1cee5bc..f3127aa04 100644 --- a/src/store/useTokensStore.ts +++ b/src/store/useTokensStore.ts @@ -2,6 +2,7 @@ import { uniqBy } from 'lodash'; import { Dispatch, SetStateAction, useMemo, useState } from 'react'; import { Token } from 'libs/tokens'; import { useTokensQuery } from 'libs/queries'; +import { lsService } from 'services/localeStorage'; export interface TokensStore { tokens: Token[]; @@ -15,7 +16,9 @@ export interface TokensStore { export const useTokensStore = (): TokensStore => { const tokensQuery = useTokensQuery(); - const [importedTokens, setImportedTokens] = useState([]); + const [importedTokens, setImportedTokens] = useState( + lsService.getItem('importedTokens') ?? [] + ); const tokens = useMemo(() => { if (tokensQuery.data && tokensQuery.data.length) { diff --git a/src/utils/carbonApi.ts b/src/utils/carbonApi.ts index e5ca2cbf9..e26ee46c1 100644 --- a/src/utils/carbonApi.ts +++ b/src/utils/carbonApi.ts @@ -13,7 +13,7 @@ import { ServerActivityMeta, } from 'libs/queries/extApi/activity'; import { lsService } from 'services/localeStorage'; -import { TradeCount } from 'libs/queries/extApi/tradeCount'; +import { Trending } from 'libs/queries/extApi/tradeCount'; // Only ETH is supported as network currency by the API const NETWORK_CURRENCY = @@ -93,7 +93,7 @@ const carbonApi = { getActivityMeta: async (params: QueryActivityParams) => { return get('activity/meta', params); }, - getTradeCount: () => get('analytics/trades_count'), + getTrending: () => get('analytics/trending'), }; export { carbonApi };