From 71ef917ef24e0fa553b4cd6c2aa914acddb983e9 Mon Sep 17 00:00:00 2001 From: thiagoulloa Date: Thu, 14 Nov 2024 23:20:14 -0300 Subject: [PATCH 1/5] Sort token list update --- .../SearchModal/CurrencyList/index.tsx | 48 +++++++++++-------- src/hooks/tokens/useToken.ts | 27 ++++++----- src/hooks/tokens/utils.ts | 1 + src/interfaces/tokens.ts | 1 + 4 files changed, 47 insertions(+), 30 deletions(-) diff --git a/src/components/SearchModal/CurrencyList/index.tsx b/src/components/SearchModal/CurrencyList/index.tsx index 71788101..6c64a926 100644 --- a/src/components/SearchModal/CurrencyList/index.tsx +++ b/src/components/SearchModal/CurrencyList/index.tsx @@ -107,30 +107,17 @@ export function CurrencyRow({ otherSelected, style, showCurrencyAmount, + balance, }: { currency: TokenType; onSelect: (hasWarning: boolean) => void; isSelected: boolean; otherSelected: boolean; showCurrencyAmount?: boolean; + balance: number; eventProperties: Record; style?: CSSProperties; }) { - const sorobanContext = useSorobanReact(); - - const { address } = sorobanContext; - - const { tokenBalancesResponse } = useGetMyBalances(); - - const { account } = useHorizonLoadAccount(); - - const { data, isLoading } = useSWRImmutable( - sorobanContext.activeChain && sorobanContext.address && account - ? ['currencyBalance', tokenBalancesResponse, currency, sorobanContext, account] - : null, - ([_, tokenBalancesResponse, currency, sorobanContext, account]) => - getCurrencyBalance(tokenBalancesResponse, currency, sorobanContext, account), - ); const shortenSorobanClassicAsset = (currency: TokenType) => { if (!currency) return ''; if (currency?.name && currency.name.toString().length > 56) { @@ -186,7 +173,7 @@ export function CurrencyRow({ {showCurrencyAmount ? ( - {isLoading ? : address ? : null} + {} {/* Usando o saldo diretamente */} {isSelected && } ) : ( @@ -258,6 +245,12 @@ export default function CurrencyList({ isAddressSearch: string | false; isLoading?: boolean; }) { + const sorobanContext = useSorobanReact(); + + const { tokenBalancesResponse } = useGetMyBalances(); + + const { account } = useHorizonLoadAccount(); + const itemData: TokenType[] = useMemo(() => { if (otherListTokens && otherListTokens?.length > 0) { return [...currencies, ...otherListTokens]; @@ -265,6 +258,22 @@ export default function CurrencyList({ return currencies; }, [currencies, otherListTokens]); + const enhancedItemData = itemData.map((currency) => { + const { data: balance, error } = useSWRImmutable( + sorobanContext.activeChain && sorobanContext.address && account + ? ['currencyBalance', tokenBalancesResponse, currency, sorobanContext, account] + : null, + ([_, tokenBalancesResponse, currency, sorobanContext, account]) => + getCurrencyBalance(tokenBalancesResponse, currency, sorobanContext, account), + ); + return { + ...currency, + balance: Number(balance ?? 0), + }; + }); + + const sortedItemData = enhancedItemData.sort((a, b) => b.balance - a.balance); + const Row = useCallback( function TokenRow({ data, index, style }: TokenRowProps) { const row: TokenType = data[index]; @@ -286,6 +295,7 @@ export default function CurrencyList({ return ( { + const itemKey = useCallback((index: number, data: typeof sortedItemData) => { const currency = data[index]; return currencyKey(currency); }, []); @@ -330,8 +340,8 @@ export default function CurrencyList({ height={height} ref={fixedListRef as any} width="100%" - itemData={itemData} - itemCount={itemData.length} + itemData={sortedItemData} + itemCount={sortedItemData.length} itemSize={56} itemKey={itemKey} > diff --git a/src/hooks/tokens/useToken.ts b/src/hooks/tokens/useToken.ts index 645e95bb..d6171eb6 100644 --- a/src/hooks/tokens/useToken.ts +++ b/src/hooks/tokens/useToken.ts @@ -17,14 +17,14 @@ export const findToken = async ( if (!tokenAddress || tokenAddress === '') return undefined; const classicAssetSearch = getClassicAssetSorobanAddress(tokenAddress!, sorobanContext); - + const formattedAddress = isAddress(classicAssetSearch ? classicAssetSearch : tokenAddress); if (!formattedAddress) return undefined; - + const fromMap = tokensAsMap[formattedAddress]; - + if (fromMap) return fromMap; - + const token = await getToken(sorobanContext, formattedAddress); // const token: TokenType = { // contract: formattedAddress, @@ -33,7 +33,7 @@ export const findToken = async ( // decimals, // icon: logo, // }; - + if (!token?.name || !token?.code) return undefined; // Here from token.name we will try to understand if this is a classic asset (even if we got a soroban contracta as address). const stellarAsset = getClassicStellarAsset(token.name); @@ -44,6 +44,7 @@ export const findToken = async ( */ if (stellarAsset && typeof stellarAsset !== 'boolean') { return { + balance: 0, issuer: stellarAsset.issuer, contract: token.contract, name: stellarAsset.asset, @@ -51,10 +52,9 @@ export const findToken = async ( decimals: 7, icon: '', }; + } else { + return token; } - else { - return token - }; }; const revalidateKeysCondition = (key: any) => { @@ -96,8 +96,12 @@ export function useToken(tokenAddress: string | undefined) { const bothLoading = isLoading || isStellarClassicAssetLoading; const needsWrapping = !data && isStellarClassicAsset; - - const checkContractId = (contractId: string, code: string, issuer: string): boolean | undefined => { + + const checkContractId = ( + contractId: string, + code: string, + issuer: string, + ): boolean | undefined => { if (!issuer) { return undefined; } @@ -107,7 +111,7 @@ export function useToken(tokenAddress: string | undefined) { } else { return false; } - } + }; const isSafe = data ? checkContractId(data.contract, data.code, data.issuer!) : false; const needsWrappingOnAddLiquidity = (!data && isStellarClassicAsset) || !name; @@ -120,6 +124,7 @@ export function useToken(tokenAddress: string | undefined) { if (sorobanAddress || (stellarAsset && typeof sorobanAddress === 'string')) { if (stellarAsset && typeof stellarAsset !== 'boolean') { newTokenData = { + balance: 0, issuer: stellarAsset.issuer, contract: sorobanAddress, name: stellarAsset.asset, diff --git a/src/hooks/tokens/utils.ts b/src/hooks/tokens/utils.ts index ba233397..5a4aeb1e 100644 --- a/src/hooks/tokens/utils.ts +++ b/src/hooks/tokens/utils.ts @@ -67,6 +67,7 @@ export async function getToken( logo = await getTokenLogo(formattedAddress, sorobanContext); const token: TokenType = { + balance: 0, contract: formattedAddress, name: name as string, code: symbol as string, diff --git a/src/interfaces/tokens.ts b/src/interfaces/tokens.ts index 1e354acb..300dcd0e 100644 --- a/src/interfaces/tokens.ts +++ b/src/interfaces/tokens.ts @@ -1,4 +1,5 @@ export interface TokenType { + balance: number; code: string; issuer?: string; contract: string; From 9de86666e7e0b5e306f77e1cc0b419e722711bd2 Mon Sep 17 00:00:00 2001 From: thiagoulloa Date: Thu, 14 Nov 2024 23:23:23 -0300 Subject: [PATCH 2/5] Removing unwanted commented code --- src/components/SearchModal/CurrencyList/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/SearchModal/CurrencyList/index.tsx b/src/components/SearchModal/CurrencyList/index.tsx index 6c64a926..37cce73c 100644 --- a/src/components/SearchModal/CurrencyList/index.tsx +++ b/src/components/SearchModal/CurrencyList/index.tsx @@ -173,7 +173,7 @@ export function CurrencyRow({ {showCurrencyAmount ? ( - {} {/* Usando o saldo diretamente */} + {} {isSelected && } ) : ( From b2de96c940c8d5a5f33a5b15950f761161c73900 Mon Sep 17 00:00:00 2001 From: thiagoulloa Date: Fri, 15 Nov 2024 13:42:47 -0300 Subject: [PATCH 3/5] useSWRImmutable removed from CurrencyList --- .../SearchModal/CurrencyList/index.tsx | 82 ++++++++++++------- src/interfaces/tokens.ts | 2 +- 2 files changed, 52 insertions(+), 32 deletions(-) diff --git a/src/components/SearchModal/CurrencyList/index.tsx b/src/components/SearchModal/CurrencyList/index.tsx index 37cce73c..07468923 100644 --- a/src/components/SearchModal/CurrencyList/index.tsx +++ b/src/components/SearchModal/CurrencyList/index.tsx @@ -1,10 +1,9 @@ import { SorobanContextType, useSorobanReact } from '@soroban-react/core'; import BigNumber from 'bignumber.js'; -import { CSSProperties, MutableRefObject, useCallback, useMemo } from 'react'; +import { CSSProperties, MutableRefObject, useCallback, useEffect, useMemo, useState } from 'react'; import { Check } from 'react-feather'; import { FixedSizeList } from 'react-window'; import { AccountResponse } from '@stellar/stellar-sdk/lib/horizon'; -import useSWRImmutable from 'swr/immutable'; import { CircularProgress, Typography, styled } from 'soroswap-ui'; import Column, { AutoColumn } from 'components/Column'; @@ -114,7 +113,7 @@ export function CurrencyRow({ isSelected: boolean; otherSelected: boolean; showCurrencyAmount?: boolean; - balance: number; + balance?: number; eventProperties: Record; style?: CSSProperties; }) { @@ -171,7 +170,7 @@ export function CurrencyRow({ {currency.domain ? currency.domain : formattedCurrencyName(currency as TokenType)} - {showCurrencyAmount ? ( + {showCurrencyAmount && balance ? ( {} {isSelected && } @@ -246,51 +245,72 @@ export default function CurrencyList({ isLoading?: boolean; }) { const sorobanContext = useSorobanReact(); - const { tokenBalancesResponse } = useGetMyBalances(); - const { account } = useHorizonLoadAccount(); - const itemData: TokenType[] = useMemo(() => { - if (otherListTokens && otherListTokens?.length > 0) { - return [...currencies, ...otherListTokens]; - } - return currencies; + const [balances, setBalances] = useState>({}); + + const itemData = useMemo(() => { + return otherListTokens && otherListTokens.length > 0 + ? [...currencies, ...otherListTokens] + : currencies; }, [currencies, otherListTokens]); - const enhancedItemData = itemData.map((currency) => { - const { data: balance, error } = useSWRImmutable( - sorobanContext.activeChain && sorobanContext.address && account - ? ['currencyBalance', tokenBalancesResponse, currency, sorobanContext, account] - : null, - ([_, tokenBalancesResponse, currency, sorobanContext, account]) => - getCurrencyBalance(tokenBalancesResponse, currency, sorobanContext, account), - ); - return { - ...currency, - balance: Number(balance ?? 0), + useEffect(() => { + const fetchBalances = async () => { + const newBalances: Record = {}; + + if (account) { + for (const currency of itemData) { + const balance = await getCurrencyBalance( + tokenBalancesResponse, + currency, + sorobanContext, + account, + ); + newBalances[currencyKey(currency)] = Number(balance ?? 0); + } + setBalances(newBalances); + } }; - }); - const sortedItemData = enhancedItemData.sort((a, b) => b.balance - a.balance); + if (sorobanContext.activeChain && sorobanContext.address && account && itemData.length > 0) { + fetchBalances(); + } + }, [ + sorobanContext.activeChain, + sorobanContext.address, + account, + itemData, + tokenBalancesResponse, + ]); + + const sortedItemData = useMemo(() => { + if (!itemData || !Array.isArray(itemData)) { + return []; + } + return [...itemData] + .map((currency) => ({ + ...currency, + balance: balances[currencyKey(currency)] || 0, + })) + .sort((a, b) => b.balance - a.balance); + }, [itemData, balances]); const Row = useCallback( function TokenRow({ data, index, style }: TokenRowProps) { - const row: TokenType = data[index]; - + const row = data[index]; const currency = row; const isSelected = Boolean( - currency && selectedCurrency && selectedCurrency.contract == currency.contract, + currency && selectedCurrency && selectedCurrency.contract === currency.contract, ); const otherSelected = Boolean( - currency && otherCurrency && otherCurrency.contract == currency.contract, + currency && otherCurrency && otherCurrency.contract === currency.contract, ); const handleSelect = (hasWarning: boolean) => currency && onCurrencySelect(currency, hasWarning); - const token = currency; - if (currency) { return ( Date: Fri, 15 Nov 2024 18:42:05 -0300 Subject: [PATCH 4/5] Adding useSWRImmutable back to improve optimization --- .../SearchModal/CurrencyList/index.tsx | 58 ++++++++++--------- 1 file changed, 30 insertions(+), 28 deletions(-) diff --git a/src/components/SearchModal/CurrencyList/index.tsx b/src/components/SearchModal/CurrencyList/index.tsx index 07468923..e5305519 100644 --- a/src/components/SearchModal/CurrencyList/index.tsx +++ b/src/components/SearchModal/CurrencyList/index.tsx @@ -20,6 +20,7 @@ import useHorizonLoadAccount from 'hooks/useHorizonLoadAccount'; import { isAddress, shortenAddress } from 'helpers/address'; import { formatTokenAmount } from 'helpers/format'; +import useSWRImmutable from 'swr/immutable'; function currencyKey(currency: TokenType): string { return currency.contract ? currency.contract : 'ETHER'; @@ -248,42 +249,43 @@ export default function CurrencyList({ const { tokenBalancesResponse } = useGetMyBalances(); const { account } = useHorizonLoadAccount(); - const [balances, setBalances] = useState>({}); - const itemData = useMemo(() => { return otherListTokens && otherListTokens.length > 0 ? [...currencies, ...otherListTokens] : currencies; }, [currencies, otherListTokens]); - useEffect(() => { - const fetchBalances = async () => { - const newBalances: Record = {}; - - if (account) { - for (const currency of itemData) { - const balance = await getCurrencyBalance( - tokenBalancesResponse, - currency, - sorobanContext, - account, - ); - newBalances[currencyKey(currency)] = Number(balance ?? 0); - } - setBalances(newBalances); - } - }; + const fetchBalances = async () => { + if ( + !account || + !sorobanContext.activeChain || + !sorobanContext.address || + itemData.length === 0 + ) { + return {}; + } - if (sorobanContext.activeChain && sorobanContext.address && account && itemData.length > 0) { - fetchBalances(); + const newBalances: Record = {}; + for (const currency of itemData) { + const balance = await getCurrencyBalance( + tokenBalancesResponse, + currency, + sorobanContext, + account, + ); + newBalances[currencyKey(currency)] = Number(balance ?? 0); } - }, [ - sorobanContext.activeChain, - sorobanContext.address, - account, - itemData, - tokenBalancesResponse, - ]); + return newBalances; + }; + + const { data: balances = {}, error } = useSWRImmutable( + account ? ['balances', account, tokenBalancesResponse, itemData] : null, + fetchBalances, + ); + + if (error) { + console.error('Error loading balances:', error); + } const sortedItemData = useMemo(() => { if (!itemData || !Array.isArray(itemData)) { From 0129ba0248dc684a389689899d67a5ee2bba2dd1 Mon Sep 17 00:00:00 2001 From: thiagoulloa Date: Sat, 16 Nov 2024 18:33:19 -0300 Subject: [PATCH 5/5] Sort token by volume --- .../SearchModal/CurrencyList/index.tsx | 122 ++++++++++-------- src/hooks/tokens/useToken.ts | 2 - src/hooks/tokens/utils.ts | 1 - src/interfaces/tokens.ts | 12 +- 4 files changed, 77 insertions(+), 60 deletions(-) diff --git a/src/components/SearchModal/CurrencyList/index.tsx b/src/components/SearchModal/CurrencyList/index.tsx index e5305519..3177528a 100644 --- a/src/components/SearchModal/CurrencyList/index.tsx +++ b/src/components/SearchModal/CurrencyList/index.tsx @@ -4,13 +4,14 @@ import { CSSProperties, MutableRefObject, useCallback, useEffect, useMemo, useSt import { Check } from 'react-feather'; import { FixedSizeList } from 'react-window'; import { AccountResponse } from '@stellar/stellar-sdk/lib/horizon'; +import useSWRImmutable from 'swr/immutable'; import { CircularProgress, Typography, styled } from 'soroswap-ui'; import Column, { AutoColumn } from 'components/Column'; import Loader from 'components/Icons/LoadingSpinner'; import CurrencyLogo from 'components/Logo/CurrencyLogo'; import Row, { RowFixed } from 'components/Row'; -import { TokenType } from '../../../interfaces'; +import { TokenType, TokenVolumeData } from '../../../interfaces'; import StyledRow from '../../Row'; import { LoadingRows, MenuItem } from '../styleds'; @@ -20,7 +21,6 @@ import useHorizonLoadAccount from 'hooks/useHorizonLoadAccount'; import { isAddress, shortenAddress } from 'helpers/address'; import { formatTokenAmount } from 'helpers/format'; -import useSWRImmutable from 'swr/immutable'; function currencyKey(currency: TokenType): string { return currency.contract ? currency.contract : 'ETHER'; @@ -107,17 +107,30 @@ export function CurrencyRow({ otherSelected, style, showCurrencyAmount, - balance, }: { currency: TokenType; onSelect: (hasWarning: boolean) => void; isSelected: boolean; otherSelected: boolean; showCurrencyAmount?: boolean; - balance?: number; eventProperties: Record; style?: CSSProperties; }) { + const sorobanContext = useSorobanReact(); + + const { address } = sorobanContext; + + const { tokenBalancesResponse } = useGetMyBalances(); + + const { account } = useHorizonLoadAccount(); + + const { data, isLoading } = useSWRImmutable( + sorobanContext.activeChain && sorobanContext.address && account + ? ['currencyBalance', tokenBalancesResponse, currency, sorobanContext, account] + : null, + ([_, tokenBalancesResponse, currency, sorobanContext, account]) => + getCurrencyBalance(tokenBalancesResponse, currency, sorobanContext, account), + ); const shortenSorobanClassicAsset = (currency: TokenType) => { if (!currency) return ''; if (currency?.name && currency.name.toString().length > 56) { @@ -171,9 +184,9 @@ export function CurrencyRow({ {currency.domain ? currency.domain : formattedCurrencyName(currency as TokenType)} - {showCurrencyAmount && balance ? ( + {showCurrencyAmount ? ( - {} + {isLoading ? : address ? : null} {isSelected && } ) : ( @@ -245,86 +258,83 @@ export default function CurrencyList({ isAddressSearch: string | false; isLoading?: boolean; }) { - const sorobanContext = useSorobanReact(); - const { tokenBalancesResponse } = useGetMyBalances(); - const { account } = useHorizonLoadAccount(); + const [tokenVolumes, setTokenVolumes] = useState(null); - const itemData = useMemo(() => { - return otherListTokens && otherListTokens.length > 0 - ? [...currencies, ...otherListTokens] - : currencies; - }, [currencies, otherListTokens]); - - const fetchBalances = async () => { - if ( - !account || - !sorobanContext.activeChain || - !sorobanContext.address || - itemData.length === 0 - ) { - return {}; - } + const [sortedItemData, setSortedItemData] = useState([]); - const newBalances: Record = {}; - for (const currency of itemData) { - const balance = await getCurrencyBalance( - tokenBalancesResponse, - currency, - sorobanContext, - account, - ); - newBalances[currencyKey(currency)] = Number(balance ?? 0); + const fetchTokenVolumes = async (network: string): Promise => { + const response = await fetch(`https://info.soroswap.finance/api/tokens?network=${network}`); + if (!response.ok) { + throw new Error('Failed to fetch token volumes'); } - return newBalances; + const data = await response.json(); + return data.map((token: any) => ({ + asset: token.asset, + volume24h: token.volume24h, + })); }; - const { data: balances = {}, error } = useSWRImmutable( - account ? ['balances', account, tokenBalancesResponse, itemData] : null, - fetchBalances, - ); + useEffect(() => { + const getVolumes = async () => { + try { + const volumes = await fetchTokenVolumes('MAINNET'); + setTokenVolumes(volumes); + } catch (error) { + console.error(error); + } + }; + getVolumes(); + }, []); - if (error) { - console.error('Error loading balances:', error); - } + const itemData: TokenType[] = useMemo(() => { + if (otherListTokens && otherListTokens?.length > 0) { + return [...currencies, ...otherListTokens]; + } + return currencies; + }, [currencies, otherListTokens]); - const sortedItemData = useMemo(() => { - if (!itemData || !Array.isArray(itemData)) { - return []; + useEffect(() => { + if (tokenVolumes) { + const sorted = [...itemData]; + sorted.sort((a, b) => { + const volumeA = + tokenVolumes.find((item) => item.asset.contract === a.contract)?.volume24h || 0; + const volumeB = + tokenVolumes.find((item) => item.asset.contract === b.contract)?.volume24h || 0; + return volumeB - volumeA; + }); + setSortedItemData(sorted); } - return [...itemData] - .map((currency) => ({ - ...currency, - balance: balances[currencyKey(currency)] || 0, - })) - .sort((a, b) => b.balance - a.balance); - }, [itemData, balances]); + }, [itemData, tokenVolumes]); const Row = useCallback( function TokenRow({ data, index, style }: TokenRowProps) { - const row = data[index]; + const row: TokenType = data[index]; + const currency = row; const isSelected = Boolean( - currency && selectedCurrency && selectedCurrency.contract === currency.contract, + currency && selectedCurrency && selectedCurrency.contract == currency.contract, ); const otherSelected = Boolean( - currency && otherCurrency && otherCurrency.contract === currency.contract, + currency && otherCurrency && otherCurrency.contract == currency.contract, ); const handleSelect = (hasWarning: boolean) => currency && onCurrencySelect(currency, hasWarning); + const token = currency; + if (currency) { return (