diff --git a/lavamoat/build-webpack/policy.json b/lavamoat/build-webpack/policy.json index 19440ec708..69a0472426 100644 --- a/lavamoat/build-webpack/policy.json +++ b/lavamoat/build-webpack/policy.json @@ -1184,8 +1184,13 @@ "define": true }, "packages": { - "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/generator>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true, - "webpack>terser-webpack-plugin>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true + "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/generator>@jridgewell/trace-mapping>@jridgewell/resolve-uri": true, + "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/generator>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": true + } + }, + "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/generator>@jridgewell/trace-mapping>@jridgewell/resolve-uri": { + "globals": { + "define": true } }, "jest>@jest/core>jest-snapshot>@babel/traverse>@babel/generator>@jridgewell/trace-mapping>@jridgewell/sourcemap-codec": { diff --git a/src/core/graphql/__generated__/metadata.ts b/src/core/graphql/__generated__/metadata.ts index 0c8656d92a..472faa2aae 100644 --- a/src/core/graphql/__generated__/metadata.ts +++ b/src/core/graphql/__generated__/metadata.ts @@ -51,6 +51,31 @@ export type ContractFunction = { text: Scalars['String']; }; +export type CustomNetwork = { + __typename?: 'CustomNetwork'; + defaultExplorerURL: Scalars['String']; + defaultRPCURL: Scalars['String']; + iconURL: Scalars['String']; + id: Scalars['Int']; + name: Scalars['String']; + nativeAsset: CustomNetworkNativeAsset; + testnet: CustomNetworkTestnet; +}; + +export type CustomNetworkNativeAsset = { + __typename?: 'CustomNetworkNativeAsset'; + decimals: Scalars['Int']; + iconURL: Scalars['String']; + symbol: Scalars['String']; +}; + +export type CustomNetworkTestnet = { + __typename?: 'CustomNetworkTestnet'; + FaucetURL: Scalars['String']; + isTestnet: Scalars['Boolean']; + mainnetChainID: Scalars['Int']; +}; + export type DApp = { __typename?: 'DApp'; colors: DAppColors; @@ -172,6 +197,7 @@ export type Network = { gasUnits: NetworkGasUnits; icons: NetworkIcons; id: Scalars['ID']; + internal: Scalars['Boolean']; label: Scalars['String']; name: Scalars['String']; nativeAsset: NetworkAsset; @@ -184,6 +210,7 @@ export type NetworkAddys = { __typename?: 'NetworkAddys'; approvals: Scalars['Boolean']; assets: Scalars['Boolean']; + interactionsWith: Scalars['Boolean']; positions: Scalars['Boolean']; summary: Scalars['Boolean']; transactions: Scalars['Boolean']; @@ -211,6 +238,7 @@ export type NetworkEnabledServices = { addys: NetworkAddys; meteorology: NetworkMeteorology; nftProxy: NetworkNftProxy; + notifications: NetworkNotifications; swap: NetworkSwap; tokenSearch: NetworkTokenSearch; }; @@ -261,6 +289,12 @@ export type NetworkNftProxy = { enabled: Scalars['Boolean']; }; +export type NetworkNotifications = { + __typename?: 'NetworkNotifications'; + enabled: Scalars['Boolean']; + transactions: Scalars['Boolean']; +}; + export type NetworkRpc = { __typename?: 'NetworkRPC'; enabledDevices: Array>; @@ -270,8 +304,10 @@ export type NetworkRpc = { export type NetworkSwap = { __typename?: 'NetworkSwap'; bridge: Scalars['Boolean']; + bridgeExactOutput: Scalars['Boolean']; enabled: Scalars['Boolean']; swap: Scalars['Boolean']; + swapExactOutput: Scalars['Boolean']; }; export type NetworkTokenSearch = { @@ -474,6 +510,7 @@ export type Query = { contract?: Maybe; contractFunction?: Maybe; contracts?: Maybe>>; + customNetworks?: Maybe>>; dApp?: Maybe; dApps?: Maybe>>; ensMarquee?: Maybe; @@ -511,6 +548,11 @@ export type QueryContractFunctionArgs = { }; +export type QueryCustomNetworksArgs = { + includeTestnets?: InputMaybe; +}; + + export type QueryDAppArgs = { shortName?: InputMaybe; url?: InputMaybe; @@ -780,6 +822,7 @@ export type Token = { bridging: Scalars['TokenBridging']; circulatingSupply?: Maybe; colors: TokenColors; + creationDate?: Maybe; decimals: Scalars['Int']; description?: Maybe; fullyDilutedValuation?: Maybe; @@ -790,8 +833,10 @@ export type Token = { networks: Scalars['TokenNetworks']; price: TokenPrice; priceCharts: TokenPriceCharts; + status: TokenStatus; symbol: Scalars['String']; totalSupply?: Maybe; + transferable: Scalars['Boolean']; volume1d?: Maybe; }; @@ -838,6 +883,8 @@ export type TokenPriceChart = { * where the first element is the timestamp and the second is the price */ points?: Maybe>>>>; + /** pricePresentAt time when the price is present for the first time */ + pricePresentAt?: Maybe; timeEnd?: Maybe; timeStart?: Maybe; }; @@ -861,6 +908,13 @@ export type TokenPriceCharts = { year?: Maybe; }; +export enum TokenStatus { + Scam = 'SCAM', + Unknown = 'UNKNOWN', + Unverified = 'UNVERIFIED', + Verified = 'VERIFIED' +} + export type Transaction = { data: Scalars['String']; from: Scalars['String']; @@ -936,6 +990,7 @@ export type TransactionSimulationApproval = { export type TransactionSimulationAsset = { __typename?: 'TransactionSimulationAsset'; assetCode: Scalars['String']; + creationDate?: Maybe; decimals: Scalars['Int']; iconURL: Scalars['String']; interface: TransactionAssetInterface; @@ -1150,6 +1205,15 @@ export type ClaimUserRewardsMutationVariables = Exact<{ export type ClaimUserRewardsMutation = { __typename?: 'Mutation', claimUserRewards?: { __typename?: 'UserClaimTransaction', chainID: number, uoHash: string, txHash: string, error?: { __typename?: 'PointsError', type: PointsErrorType, message: string } | null } | null }; +export type ExternalTokenQueryVariables = Exact<{ + address: Scalars['String']; + chainId: Scalars['Int']; + currency?: InputMaybe; +}>; + + +export type ExternalTokenQuery = { __typename?: 'Query', token?: { __typename?: 'Token', decimals: number, iconUrl?: string | null, name: string, networks: any, symbol: string, price: { __typename?: 'TokenPrice', relativeChange24h?: number | null, value?: number | null } } | null }; + export const AssetFragmentDoc = gql` fragment asset on TransactionSimulationAsset { assetCode @@ -1636,6 +1700,21 @@ export const ClaimUserRewardsDocument = gql` } } `; +export const ExternalTokenDocument = gql` + query externalToken($address: String!, $chainId: Int!, $currency: String) { + token(address: $address, chainID: $chainId, currency: $currency) { + decimals + iconUrl + name + networks + price { + relativeChange24h + value + } + symbol + } +} + `; export type Requester = (doc: DocumentNode, vars?: V, options?: C) => Promise | AsyncIterable export function getSdk(requester: Requester) { return { @@ -1686,6 +1765,9 @@ export function getSdk(requester: Requester) { }, claimUserRewards(variables: ClaimUserRewardsMutationVariables, options?: C): Promise { return requester(ClaimUserRewardsDocument, variables, options) as Promise; + }, + externalToken(variables: ExternalTokenQueryVariables, options?: C): Promise { + return requester(ExternalTokenDocument, variables, options) as Promise; } }; } diff --git a/src/core/graphql/queries/metadata.graphql b/src/core/graphql/queries/metadata.graphql index 876fa58b46..77ae7795f3 100644 --- a/src/core/graphql/queries/metadata.graphql +++ b/src/core/graphql/queries/metadata.graphql @@ -490,3 +490,17 @@ mutation claimUserRewards($address: String!) { txHash } } + +query externalToken($address: String!, $chainId: Int!, $currency: String) { + token(address: $address, chainID: $chainId, currency: $currency) { + decimals + iconUrl + name + networks + price { + relativeChange24h + value + } + symbol + } +} \ No newline at end of file diff --git a/src/core/resources/assets/externalToken.ts b/src/core/resources/assets/externalToken.ts new file mode 100644 index 0000000000..e56fc6ac9d --- /dev/null +++ b/src/core/resources/assets/externalToken.ts @@ -0,0 +1,137 @@ +import { useQuery } from '@tanstack/react-query'; +import { Address } from 'viem'; + +import { metadataClient } from '~/core/graphql'; +import { Token } from '~/core/graphql/__generated__/metadata'; +import { + QueryConfig, + QueryFunctionArgs, + QueryFunctionResult, + createQueryKey, +} from '~/core/react-query'; +import { SupportedCurrencyKey } from '~/core/references'; +import { AddressOrEth, ParsedAsset } from '~/core/types/assets'; +import { ChainId, chainIdToNameMapping } from '~/core/types/chains'; +import { isNativeAsset } from '~/core/utils/chains'; +import { + convertAmountAndPriceToNativeDisplay, + convertAmountToPercentageDisplay, +} from '~/core/utils/numbers'; + +export const EXTERNAL_TOKEN_CACHE_TIME = 1000 * 60 * 60 * 24; // 24 hours +export const EXTERNAL_TOKEN_STALE_TIME = 1000 * 60; // 1 minute + +// Types +type ExternalToken = Pick< + Token, + 'decimals' | 'iconUrl' | 'name' | 'networks' | 'symbol' | 'price' +>; + +// Query Types for External Token +type ExternalTokenArgs = { + address: string; + chainId: ChainId; + currency: SupportedCurrencyKey; +}; + +// Query Key for Token Price +export const externalTokenQueryKey = ({ + address, + chainId, + currency, +}: ExternalTokenArgs) => + createQueryKey( + 'externalToken', + { address, chainId, currency }, + { persisterVersion: 1 }, + ); + +type externalTokenQueryKey = ReturnType; + +// Helpers +const formatExternalAsset = ( + address: string, + chainId: ChainId, + asset: ExternalToken, + nativeCurrency: SupportedCurrencyKey, +): ParsedAsset => { + return { + ...asset, + chainId, + chainName: chainIdToNameMapping[chainId], + uniqueId: `${address}_${chainId}`, + address: address as Address, + isNativeAsset: isNativeAsset(address as AddressOrEth, chainId), + native: { + price: { + change: asset?.price?.relativeChange24h + ? convertAmountToPercentageDisplay( + `${asset?.price?.relativeChange24h}`, + ) + : '', + amount: asset?.price?.value ?? 0, + display: convertAmountAndPriceToNativeDisplay( + 1, + asset?.price?.value ?? 0, + nativeCurrency, + ).display, + }, + }, + price: { + value: asset?.price?.value ?? 0, + relative_change_24h: asset?.price?.relativeChange24h ?? 0, + }, + icon_url: asset?.iconUrl || undefined, + }; +}; + +// Query Function for Token Price +export async function fetchExternalToken({ + address, + chainId, + currency, +}: ExternalTokenArgs) { + const response = await metadataClient.externalToken({ + address, + chainId, + currency, + }); + if (response?.token) { + return formatExternalAsset(address, chainId, response.token, currency); + } else { + return null; + } +} + +export async function externalTokenQueryFunction({ + queryKey: [{ address, chainId, currency }], +}: QueryFunctionArgs< + typeof externalTokenQueryKey +>): Promise { + if (!address || !chainId) return null; + return fetchExternalToken({ address, chainId, currency }); +} + +export type ExternalTokenQueryFunctionResult = QueryFunctionResult< + typeof externalTokenQueryFunction +>; + +// Query Hook for Token Price +export function useExternalToken( + { address, chainId, currency }: ExternalTokenArgs, + config: QueryConfig< + ExternalTokenQueryFunctionResult, + Error, + ParsedAsset, + externalTokenQueryKey + > = {}, +) { + return useQuery({ + queryKey: externalTokenQueryKey({ address, chainId, currency }), + queryFn: externalTokenQueryFunction, + staleTime: EXTERNAL_TOKEN_STALE_TIME, + gcTime: EXTERNAL_TOKEN_CACHE_TIME, + enabled: !!address && !!chainId, + ...config, + }); +} diff --git a/src/core/resources/assets/userTestnetNativeAsset.ts b/src/core/resources/assets/userTestnetNativeAsset.ts index 68fc372b3d..1a0507b1e4 100644 --- a/src/core/resources/assets/userTestnetNativeAsset.ts +++ b/src/core/resources/assets/userTestnetNativeAsset.ts @@ -113,7 +113,7 @@ export function useUserTestnetNativeAsset( config: QueryConfig< UserAssetsResult, Error, - UserAssetsResult, + ParsedUserAsset, UserTestnetNativeAssetQueryKey > = {}, ) { diff --git a/src/entries/popup/hooks/send/useSendValidations.ts b/src/entries/popup/hooks/send/useSendValidations.ts index 03794f5038..16632ac167 100644 --- a/src/entries/popup/hooks/send/useSendValidations.ts +++ b/src/entries/popup/hooks/send/useSendValidations.ts @@ -17,7 +17,7 @@ import { } from '~/core/utils/numbers'; import { getProvider } from '~/core/wagmi/clientToProvider'; -import { useNativeAsset } from '../useNativeAsset'; +import { useUserNativeAsset } from '../useUserNativeAsset'; export const useSendValidations = ({ asset, @@ -45,7 +45,7 @@ export const useSendValidations = ({ } return ChainId.mainnet; }; - const { nativeAsset } = useNativeAsset({ + const { nativeAsset } = useUserNativeAsset({ chainId: getNativeAssetChainId(), }); diff --git a/src/entries/popup/hooks/swap/useSwapReviewDetails.ts b/src/entries/popup/hooks/swap/useSwapReviewDetails.ts index 94cee00e77..e964092890 100644 --- a/src/entries/popup/hooks/swap/useSwapReviewDetails.ts +++ b/src/entries/popup/hooks/swap/useSwapReviewDetails.ts @@ -13,7 +13,7 @@ import { multiply, } from '~/core/utils/numbers'; -import { useNativeAssetForNetwork } from '../useNativeAssetForNetwork'; +import { useNativeAsset } from '../useNativeAsset'; const getExchangeIconUrl = (protocol: string): string | null => { if (!protocol) return null; @@ -46,7 +46,7 @@ export const useSwapReviewDetails = ({ quote: Quote | CrosschainQuote; }) => { const { currentCurrency } = useCurrentCurrencyStore(); - const nativeAsset = useNativeAssetForNetwork({ + const nativeAsset = useNativeAsset({ chainId: assetToSell.chainId, }); diff --git a/src/entries/popup/hooks/swap/useSwapValidations.ts b/src/entries/popup/hooks/swap/useSwapValidations.ts index 6c16f7b160..1f28b1beec 100644 --- a/src/entries/popup/hooks/swap/useSwapValidations.ts +++ b/src/entries/popup/hooks/swap/useSwapValidations.ts @@ -26,7 +26,7 @@ export const useSwapValidations = ({ selectedGas?: GasFeeParams | GasFeeLegacyParams; }) => { const nativeAssetUniqueId = getNetworkNativeAssetUniqueId({ - chainId: assetToSell?.chainId || ChainId.mainnet, + chainId: assetToSell?.chainId, }); const { data: userNativeAsset } = useUserAsset(nativeAssetUniqueId || ''); diff --git a/src/entries/popup/hooks/useGas.ts b/src/entries/popup/hooks/useGas.ts index b3882e9c20..9d09553d8d 100644 --- a/src/entries/popup/hooks/useGas.ts +++ b/src/entries/popup/hooks/useGas.ts @@ -36,8 +36,8 @@ import { } from '~/core/utils/gas'; import { useDebounce } from './useDebounce'; -import { useNativeAsset } from './useNativeAsset'; import usePrevious from './usePrevious'; +import { useUserNativeAsset } from './useUserNativeAsset'; const useGas = ({ chainId, @@ -60,7 +60,7 @@ const useGas = ({ }) => { const { currentCurrency } = useCurrentCurrencyStore(); const { data: gasData, isLoading } = useGasData({ chainId }); - const { nativeAsset } = useNativeAsset({ chainId, address }); + const { nativeAsset } = useUserNativeAsset({ chainId, address }); const prevDefaultSpeed = usePrevious(defaultSpeed); const [internalMaxPriorityFee, setInternalMaxPriorityFee] = useState(''); diff --git a/src/entries/popup/hooks/useNativeAsset.ts b/src/entries/popup/hooks/useNativeAsset.ts index 562ab4d4a4..0edbd6e5f0 100644 --- a/src/entries/popup/hooks/useNativeAsset.ts +++ b/src/entries/popup/hooks/useNativeAsset.ts @@ -1,84 +1,32 @@ -import { AddressZero } from '@ethersproject/constants'; -import { Address } from 'viem'; -import { useConfig } from 'wagmi'; - -import { useUserTestnetNativeAsset } from '~/core/resources/assets/userTestnetNativeAsset'; -import { useCurrentAddressStore, useCurrentCurrencyStore } from '~/core/state'; -import { ParsedUserAsset } from '~/core/types/assets'; -import { ChainId, chainIdToNameMapping } from '~/core/types/chains'; -import { isCustomChain } from '~/core/utils/chains'; - -import { useCustomNetworkAsset } from './useCustomNetworkAsset'; +import { ETH_ADDRESS } from '~/core/references'; +import { chainsNativeAsset } from '~/core/references/chains'; import { - getNetworkNativeAssetChainId, - getNetworkNativeAssetUniqueId, -} from './useNativeAssetForNetwork'; -import { useNativeAssets } from './useNativeAssets'; -import { useUserAsset } from './useUserAsset'; - -const useMockNativeAsset = ({ - chainId, -}: { - chainId: ChainId; -}): ParsedUserAsset | undefined | null => { - const nativeAssets = useNativeAssets(); - const { chains } = useConfig(); - const chain = chains.find((c) => c.id === chainId); - if (!nativeAssets || !chain) return null; - const nativeAssetMetadataChainId = getNetworkNativeAssetChainId({ chainId }); - const nativeAsset = nativeAssets?.[nativeAssetMetadataChainId]; - return { - ...nativeAsset, - chainId: chain.id, - chainName: chainIdToNameMapping[chain.id], - native: { - balance: { amount: '0', display: `0 ${nativeAsset?.symbol}` }, - }, - balance: { amount: '0', display: `0 ${nativeAsset?.symbol}` }, - }; -}; - -export const useNativeAsset = ({ - address, - chainId, -}: { - address?: Address; - chainId: ChainId; -}): { nativeAsset?: ParsedUserAsset | null } => { - const { currentAddress } = useCurrentAddressStore(); + fetchExternalToken, + useExternalToken, +} from '~/core/resources/assets/externalToken'; +import { currentCurrencyStore, useCurrentCurrencyStore } from '~/core/state'; +import { AddressOrEth } from '~/core/types/assets'; +import { ChainId } from '~/core/types/chains'; + +export const useNativeAsset = ({ chainId }: { chainId: ChainId }) => { + const address = (chainsNativeAsset[chainId] || ETH_ADDRESS) as AddressOrEth; const { currentCurrency } = useCurrentCurrencyStore(); - const { chains } = useConfig(); - const nativeAssetUniqueId = getNetworkNativeAssetUniqueId({ - chainId: chainId || ChainId.mainnet, - }); - const { data: userNativeAsset } = useUserAsset( - nativeAssetUniqueId || '', - address || currentAddress, - ); - const mockNativeAsset = useMockNativeAsset({ chainId }); - const { data: testnetNativeAsset } = useUserTestnetNativeAsset({ - address: address || currentAddress, - currency: currentCurrency, + const { data: nativeAsset } = useExternalToken({ + address, chainId, + currency: currentCurrency, }); - const { data: customNetworkNativeAsset } = useCustomNetworkAsset({ - address: address || currentAddress, - uniqueId: `${AddressZero}_${chainId}`, - filterZeroBalance: false, - }); - - const chain = chains.find((chain) => chain.id === chainId); - const isChainIdCustomNetwork = isCustomChain(chainId); + return nativeAsset; +}; - let nativeAsset: ParsedUserAsset | undefined | null; - if (isChainIdCustomNetwork) { - nativeAsset = customNetworkNativeAsset; - } else if (chain?.testnet) { - nativeAsset = testnetNativeAsset; - } else { - nativeAsset = userNativeAsset || mockNativeAsset; - } - return { nativeAsset }; +export const fetchNativeAsset = async ({ chainId }: { chainId: ChainId }) => { + const currentCurrency = currentCurrencyStore.getState().currentCurrency; + const address = (chainsNativeAsset[chainId] || ETH_ADDRESS) as AddressOrEth; + return await fetchExternalToken({ + address, + chainId, + currency: currentCurrency, + }); }; diff --git a/src/entries/popup/hooks/useNativeAssetForNetwork.ts b/src/entries/popup/hooks/useNativeAssetForNetwork.ts index 69825fe488..f59782b830 100644 --- a/src/entries/popup/hooks/useNativeAssetForNetwork.ts +++ b/src/entries/popup/hooks/useNativeAssetForNetwork.ts @@ -1,91 +1,9 @@ -import { Address } from 'viem'; - import { chainsNativeAsset } from '~/core/references/chains'; -import { ParsedAsset, UniqueId } from '~/core/types/assets'; -import { ChainId, ChainName, chainIdToNameMapping } from '~/core/types/chains'; - -import { getNativeAssets, useNativeAssets } from './useNativeAssets'; - -export const getNetworkNativeAssetChainId = ({ - chainId, -}: { - chainId: ChainId; -}): - | ChainId.mainnet - | ChainId.polygon - | ChainId.avalanche - | ChainId.degen - | ChainId.bsc => { - switch (chainId) { - case ChainId.avalanche: - return ChainId.avalanche; - case ChainId.bsc: - return ChainId.bsc; - case ChainId.polygon: - return ChainId.polygon; - case ChainId.degen: - return ChainId.degen; - case ChainId.arbitrum: - case ChainId.mainnet: - case ChainId.optimism: - case ChainId.base: - case ChainId.zora: - case ChainId.blast: - default: - return ChainId.mainnet; - } -}; +import { UniqueId } from '~/core/types/assets'; +import { ChainId } from '~/core/types/chains'; export const getNetworkNativeAssetUniqueId = ({ - chainId, + chainId = ChainId.mainnet, }: { - chainId: ChainId; + chainId?: ChainId; }): UniqueId => `${chainsNativeAsset[chainId]}_${chainId}` as UniqueId; - -export async function getNativeAssetForNetwork({ - chainId, -}: { - chainId: ChainId; -}) { - const nativeAssets = await getNativeAssets(); - const nativeAssetMetadataChainId = getNetworkNativeAssetChainId({ chainId }); - const nativeAsset = nativeAssets?.[nativeAssetMetadataChainId]; - if (nativeAsset) { - return { - ...nativeAsset, - chainId: chainId || nativeAsset?.chainId || ChainId.mainnet, - chainName: - chainIdToNameMapping[chainId] || - nativeAsset?.chainName || - ChainName.mainnet, - uniqueId: getNetworkNativeAssetUniqueId({ chainId }), - address: chainsNativeAsset[chainId] as Address, - isNativeAsset: true, - }; - } - return undefined; -} - -export function useNativeAssetForNetwork({ - chainId, -}: { - chainId: ChainId; -}): ParsedAsset | undefined { - const nativeAssets = useNativeAssets(); - const nativeAssetMetadataChainId = getNetworkNativeAssetChainId({ chainId }); - const nativeAsset = nativeAssets?.[nativeAssetMetadataChainId]; - if (nativeAsset) { - return { - ...nativeAsset, - chainId: chainId || nativeAsset?.chainId || ChainId.mainnet, - chainName: - chainIdToNameMapping[chainId] || - nativeAsset?.chainName || - ChainName.mainnet, - uniqueId: getNetworkNativeAssetUniqueId({ chainId }), - address: chainsNativeAsset[chainId] as Address, - isNativeAsset: true, - }; - } - return undefined; -} diff --git a/src/entries/popup/hooks/useUserNativeAsset.ts b/src/entries/popup/hooks/useUserNativeAsset.ts new file mode 100644 index 0000000000..f9cdcbbbbb --- /dev/null +++ b/src/entries/popup/hooks/useUserNativeAsset.ts @@ -0,0 +1,57 @@ +import { AddressZero } from '@ethersproject/constants'; +import { Address } from 'viem'; +import { useConfig } from 'wagmi'; + +import { useUserTestnetNativeAsset } from '~/core/resources/assets/userTestnetNativeAsset'; +import { useCurrentAddressStore, useCurrentCurrencyStore } from '~/core/state'; +import { ParsedUserAsset } from '~/core/types/assets'; +import { ChainId } from '~/core/types/chains'; +import { isCustomChain } from '~/core/utils/chains'; + +import { useCustomNetworkAsset } from './useCustomNetworkAsset'; +import { getNetworkNativeAssetUniqueId } from './useNativeAssetForNetwork'; +import { useUserAsset } from './useUserAsset'; + +export const useUserNativeAsset = ({ + address, + chainId, +}: { + address?: Address; + chainId: ChainId; +}): { nativeAsset?: ParsedUserAsset | null } => { + const { currentAddress } = useCurrentAddressStore(); + const { currentCurrency } = useCurrentCurrencyStore(); + const { chains } = useConfig(); + const nativeAssetUniqueId = getNetworkNativeAssetUniqueId({ + chainId, + }); + const { data: userNativeAsset } = useUserAsset( + nativeAssetUniqueId || '', + address || currentAddress, + ); + + const { data: testnetNativeAsset } = useUserTestnetNativeAsset({ + address: address || currentAddress, + currency: currentCurrency, + chainId, + }); + + const { data: customNetworkNativeAsset } = useCustomNetworkAsset({ + address: address || currentAddress, + uniqueId: `${AddressZero}_${chainId}`, + filterZeroBalance: false, + }); + + const chain = chains.find((chain) => chain.id === chainId); + const isChainIdCustomNetwork = isCustomChain(chainId); + + let nativeAsset: ParsedUserAsset | undefined | null; + if (isChainIdCustomNetwork) { + nativeAsset = customNetworkNativeAsset; + } else if (chain?.testnet) { + nativeAsset = testnetNativeAsset; + } else { + nativeAsset = userNativeAsset; + } + return { nativeAsset }; +}; diff --git a/src/entries/popup/pages/home/Points/ClaimSheet.tsx b/src/entries/popup/pages/home/Points/ClaimSheet.tsx index 171f2d7927..5bc76316d5 100644 --- a/src/entries/popup/pages/home/Points/ClaimSheet.tsx +++ b/src/entries/popup/pages/home/Points/ClaimSheet.tsx @@ -30,7 +30,7 @@ import { BottomSheet } from '~/design-system/components/BottomSheet/BottomSheet' import { rowTransparentAccentHighlight } from '~/design-system/styles/rowTransparentAccentHighlight.css'; import { ChainBadge } from '~/entries/popup/components/ChainBadge/ChainBadge'; import { useSwapGas } from '~/entries/popup/hooks/useGas'; -import { useNativeAssetForNetwork } from '~/entries/popup/hooks/useNativeAssetForNetwork'; +import { useNativeAsset } from '~/entries/popup/hooks/useNativeAsset'; import { useRainbowNavigate } from '~/entries/popup/hooks/useRainbowNavigate'; import { ROUTES } from '~/entries/popup/urls'; import { zIndexes } from '~/entries/popup/utils/zIndexes'; @@ -70,9 +70,9 @@ export function ClaimSheet() { const rewards = data?.user?.rewards; const { claimable } = rewards || {}; - const opEth = useNativeAssetForNetwork({ chainId: ChainId.optimism }); + const opEth = useNativeAsset({ chainId: ChainId.optimism }); const ethPrice = opEth?.native?.price?.amount; - const destinationEth = useNativeAssetForNetwork({ chainId: selectedChainId }); + const destinationEth = useNativeAsset({ chainId: selectedChainId }); const claimableBalance = convertRawAmountToBalance(claimable || '0', { decimals: 18, diff --git a/src/entries/popup/pages/home/Points/PointsDashboard.tsx b/src/entries/popup/pages/home/Points/PointsDashboard.tsx index ba9553c540..1f62cd7ac7 100644 --- a/src/entries/popup/pages/home/Points/PointsDashboard.tsx +++ b/src/entries/popup/pages/home/Points/PointsDashboard.tsx @@ -57,8 +57,8 @@ import { import { AddressOrEns } from '~/entries/popup/components/AddressOrEns/AddressorEns'; import ExternalImage from '~/entries/popup/components/ExternalImage/ExternalImage'; import { WalletAvatar } from '~/entries/popup/components/WalletAvatar/WalletAvatar'; -import { useNativeAsset } from '~/entries/popup/hooks/useNativeAsset'; import { useRainbowNavigate } from '~/entries/popup/hooks/useRainbowNavigate'; +import { useUserNativeAsset } from '~/entries/popup/hooks/useUserNativeAsset'; import { ROUTES } from '~/entries/popup/urls'; import { AirdropIcon } from './AirdropIcon'; @@ -705,7 +705,7 @@ function ClaimYourPoints({ claimableReward?: string; showClaimSheet: () => void; }) { - const eth = useNativeAsset({ chainId: ChainId.mainnet }); + const eth = useUserNativeAsset({ chainId: ChainId.mainnet }); const ethPrice = eth?.nativeAsset?.price?.value; const { currentCurrency: currency } = useCurrentCurrencyStore(); if (!claimableReward || claimableReward === '0' || !ethPrice) return null; @@ -963,7 +963,7 @@ function RainbowUserEarnings({ totalEarnings }: { totalEarnings: string }) { } function MyEarnings({ earnings = '0' }: { earnings?: string }) { - const eth = useNativeAsset({ chainId: ChainId.mainnet }); + const eth = useUserNativeAsset({ chainId: ChainId.mainnet }); const ethPrice = eth?.nativeAsset?.price?.value; const { currentCurrency: currency } = useCurrentCurrencyStore(); diff --git a/src/entries/popup/pages/messages/AccountSigningWith.tsx b/src/entries/popup/pages/messages/AccountSigningWith.tsx index 6d17b1c5bb..7e9d88c767 100644 --- a/src/entries/popup/pages/messages/AccountSigningWith.tsx +++ b/src/entries/popup/pages/messages/AccountSigningWith.tsx @@ -6,7 +6,7 @@ import { Inline, Stack, Text } from '~/design-system'; import { ChainBadge } from '~/entries/popup/components/ChainBadge/ChainBadge'; import { WalletAvatar } from '~/entries/popup/components/WalletAvatar/WalletAvatar'; -import { useNativeAsset } from '../../hooks/useNativeAsset'; +import { useUserNativeAsset } from '../../hooks/useUserNativeAsset'; import { WalletName } from './BottomActions'; import { useHasEnoughGas } from './useHasEnoughGas'; @@ -20,7 +20,7 @@ export interface SelectedNetwork { function WalletNativeBalance({ session }: { session: ActiveSession }) { const chainId = session?.chainId || ChainId.mainnet; const chainName = getChain({ chainId }).name; - const { nativeAsset } = useNativeAsset({ + const { nativeAsset } = useUserNativeAsset({ chainId, address: session?.address, }); diff --git a/src/entries/popup/pages/messages/SendTransaction/SendTransactionsInfo.tsx b/src/entries/popup/pages/messages/SendTransaction/SendTransactionsInfo.tsx index 38259d2081..1543276a6c 100644 --- a/src/entries/popup/pages/messages/SendTransaction/SendTransactionsInfo.tsx +++ b/src/entries/popup/pages/messages/SendTransaction/SendTransactionsInfo.tsx @@ -35,8 +35,8 @@ import { DappIcon } from '~/entries/popup/components/DappIcon/DappIcon'; import { Tag } from '~/entries/popup/components/Tag'; import { triggerToast } from '~/entries/popup/components/Toast/Toast'; import { useAppSession } from '~/entries/popup/hooks/useAppSession'; -import { useNativeAsset } from '~/entries/popup/hooks/useNativeAsset'; import { useRainbowNavigate } from '~/entries/popup/hooks/useRainbowNavigate'; +import { useUserNativeAsset } from '~/entries/popup/hooks/useUserNativeAsset'; import { ROUTES } from '~/entries/popup/urls'; import { @@ -382,7 +382,7 @@ function InsuficientGasFunds({ const { testnetMode } = useTestnetModeStore(); const isTestnet = testnetMode || getChain({ chainId }).testnet; - const { nativeAsset } = useNativeAsset({ chainId, address }); + const { nativeAsset } = useUserNativeAsset({ chainId, address }); const chainName = getChain({ chainId }).name; const { currentCurrency } = useCurrentCurrencyStore(); diff --git a/src/entries/popup/pages/messages/useHasEnoughGas.ts b/src/entries/popup/pages/messages/useHasEnoughGas.ts index 6e93d6cdde..74cfd37af2 100644 --- a/src/entries/popup/pages/messages/useHasEnoughGas.ts +++ b/src/entries/popup/pages/messages/useHasEnoughGas.ts @@ -4,12 +4,12 @@ import { ChainId } from '~/core/types/chains'; import { toWei } from '~/core/utils/ethereum'; import { lessThan } from '~/core/utils/numbers'; -import { useNativeAsset } from '../../hooks/useNativeAsset'; +import { useUserNativeAsset } from '../../hooks/useUserNativeAsset'; export const useHasEnoughGas = (session: ActiveSession) => { const chainId = session?.chainId || ChainId.mainnet; - const { nativeAsset } = useNativeAsset({ + const { nativeAsset } = useUserNativeAsset({ address: session?.address, chainId, }); diff --git a/src/entries/popup/pages/swap/SwapReviewSheet/SwapReviewSheet.tsx b/src/entries/popup/pages/swap/SwapReviewSheet/SwapReviewSheet.tsx index eab89bfb95..15f70ce2b5 100644 --- a/src/entries/popup/pages/swap/SwapReviewSheet/SwapReviewSheet.tsx +++ b/src/entries/popup/pages/swap/SwapReviewSheet/SwapReviewSheet.tsx @@ -220,7 +220,7 @@ const SwapReviewSheetWithQuote = ({ const isHardwareWallet = type === KeychainType.HardwareWalletKeychain; const nativeAssetUniqueId = getNetworkNativeAssetUniqueId({ - chainId: assetToSell?.chainId || ChainId.mainnet, + chainId: assetToSell?.chainId, }); const { data: nativeAsset } = useUserAsset(nativeAssetUniqueId || '');