0xtrails 0.6.6 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aave.d.ts +10 -2
- package/dist/aave.d.ts.map +1 -1
- package/dist/analytics.d.ts +26 -0
- package/dist/analytics.d.ts.map +1 -1
- package/dist/{ccip-CbJrlK-L.js → ccip-fConRNoG.js} +21 -21
- package/dist/chains.d.ts +23 -8
- package/dist/chains.d.ts.map +1 -1
- package/dist/constants.d.ts +5 -5
- package/dist/constants.d.ts.map +1 -1
- package/dist/customTokens.d.ts +12 -0
- package/dist/customTokens.d.ts.map +1 -0
- package/dist/decoders.d.ts +2 -2
- package/dist/decoders.d.ts.map +1 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/fees.d.ts +37 -2
- package/dist/fees.d.ts.map +1 -1
- package/dist/gasless.d.ts +15 -36
- package/dist/gasless.d.ts.map +1 -1
- package/dist/{index-w7_dK4c5.js → index-BbajxCG_.js} +59269 -77146
- package/dist/index.d.ts +8 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +828 -359
- package/dist/indexerClient.d.ts.map +1 -1
- package/dist/intentReceiptMonitor.d.ts +1 -1
- package/dist/intentReceiptMonitor.d.ts.map +1 -1
- package/dist/intentReceiptPoller.d.ts +1 -1
- package/dist/intentReceiptPoller.d.ts.map +1 -1
- package/dist/intents.d.ts +3 -2
- package/dist/intents.d.ts.map +1 -1
- package/dist/mode.d.ts +1 -1
- package/dist/mode.d.ts.map +1 -1
- package/dist/mutations.d.ts +2 -2
- package/dist/mutations.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +2 -2
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +1 -1
- package/dist/prices.d.ts.map +1 -1
- package/dist/sequenceWallet.d.ts +2 -2
- package/dist/sequenceWallet.d.ts.map +1 -1
- package/dist/time.d.ts +6 -0
- package/dist/time.d.ts.map +1 -1
- package/dist/tokenBalances.d.ts +40 -25
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/tokens.d.ts +54 -14
- package/dist/tokens.d.ts.map +1 -1
- package/dist/trailsClient.d.ts +1 -1
- package/dist/trailsClient.d.ts.map +1 -1
- package/dist/trailsRouter.d.ts +2 -1
- package/dist/trailsRouter.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +3 -2
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +2 -1
- package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
- package/dist/transactionIntent/execution/transactionState.d.ts +1 -1
- package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/crossChain.d.ts +5 -3
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +5 -3
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/transactionIntent/quote/feeExtractors.d.ts +1 -6
- package/dist/transactionIntent/quote/feeExtractors.d.ts.map +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts +4 -2
- package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +28 -5
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/transactionIntent/utils/testnetHelpers.d.ts +0 -1
- package/dist/transactionIntent/utils/testnetHelpers.d.ts.map +1 -1
- package/dist/transactionIntent/validators.d.ts +0 -2
- package/dist/transactionIntent/validators.d.ts.map +1 -1
- package/dist/transactions.d.ts +2 -2
- package/dist/transactions.d.ts.map +1 -1
- package/dist/utils.d.ts +7 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/wallets.d.ts +1 -0
- package/dist/wallets.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/AddressWalletIcon.d.ts +6 -0
- package/dist/widget/components/AddressWalletIcon.d.ts.map +1 -0
- package/dist/widget/components/ChainFilterDropdown.d.ts +2 -6
- package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
- package/dist/widget/components/ChainImage.d.ts.map +1 -1
- package/dist/widget/components/ChainList.d.ts +0 -5
- package/dist/widget/components/ChainList.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +6 -6
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
- package/dist/widget/components/DebugMenu.d.ts +1 -1
- package/dist/widget/components/DebugMenu.d.ts.map +1 -1
- package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
- package/dist/widget/components/DepositTracker.d.ts.map +1 -1
- package/dist/widget/components/Earn.d.ts +5 -5
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/FeeOption.d.ts +1 -1
- package/dist/widget/components/FeeOption.d.ts.map +1 -1
- package/dist/widget/components/FeeOptions.d.ts +2 -2
- package/dist/widget/components/FeeOptions.d.ts.map +1 -1
- package/dist/widget/components/Footer.d.ts +1 -1
- package/dist/widget/components/Footer.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts +5 -5
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundMethods.d.ts +0 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/FundSwap.d.ts +6 -6
- package/dist/widget/components/FundSwap.d.ts.map +1 -1
- package/dist/widget/components/HookModalContent.d.ts +8 -0
- package/dist/widget/components/HookModalContent.d.ts.map +1 -0
- package/dist/widget/components/OriginSelectionAmount.d.ts +11 -0
- package/dist/widget/components/OriginSelectionAmount.d.ts.map +1 -0
- package/dist/widget/components/Pay.d.ts +5 -5
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PoolDeposit.d.ts +5 -5
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
- package/dist/widget/components/PoolWithdraw.d.ts +3 -3
- package/dist/widget/components/PoolWithdraw.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts +2 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/RecentTokens.d.ts +4 -4
- package/dist/widget/components/RecentTokens.d.ts.map +1 -1
- package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
- package/dist/widget/components/ShadowPortal.d.ts +6 -0
- package/dist/widget/components/ShadowPortal.d.ts.map +1 -0
- package/dist/widget/components/Swap.d.ts +6 -6
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/TokenList.d.ts +3 -4
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TokenSelector.d.ts +3 -4
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/Tooltip.d.ts +6 -1
- package/dist/widget/components/Tooltip.d.ts.map +1 -1
- package/dist/widget/components/TrailsHookModal.d.ts +10 -0
- package/dist/widget/components/TrailsHookModal.d.ts.map +1 -0
- package/dist/widget/components/WaasFeeOptions.d.ts +3 -0
- package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -1
- package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
- package/dist/widget/components/WalletConnect.d.ts.map +1 -1
- package/dist/widget/components/WidgetProviders.d.ts +14 -0
- package/dist/widget/components/WidgetProviders.d.ts.map +1 -0
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/hooks/useAddressWalletIcon.d.ts +10 -0
- package/dist/widget/hooks/useAddressWalletIcon.d.ts.map +1 -0
- package/dist/widget/hooks/useBalanceVisible.d.ts +1 -1
- package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
- package/dist/widget/hooks/useChainFilter.d.ts +1 -1
- package/dist/widget/hooks/useChainFilter.d.ts.map +1 -1
- package/dist/widget/hooks/useCheckout.d.ts +13 -1
- package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
- package/dist/widget/hooks/useConnector.d.ts +4 -0
- package/dist/widget/hooks/useConnector.d.ts.map +1 -0
- package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
- package/dist/widget/hooks/useCustomTokenFetch.d.ts +19 -0
- package/dist/widget/hooks/useCustomTokenFetch.d.ts.map +1 -0
- package/dist/widget/hooks/useCustomTokenSearch.d.ts +20 -0
- package/dist/widget/hooks/useCustomTokenSearch.d.ts.map +1 -0
- package/dist/widget/hooks/useDebounce.d.ts +10 -0
- package/dist/widget/hooks/useDebounce.d.ts.map +1 -0
- package/dist/widget/hooks/useDebugScreens.d.ts +7 -2
- package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -19
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
- package/dist/widget/hooks/useDestinationSelectedToken.d.ts +1 -14
- package/dist/widget/hooks/useDestinationSelectedToken.d.ts.map +1 -1
- package/dist/widget/hooks/useEarnPool.d.ts +1 -1
- package/dist/widget/hooks/useEarnPool.d.ts.map +1 -1
- package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/useIsMobile.d.ts +5 -0
- package/dist/widget/hooks/useIsMobile.d.ts.map +1 -0
- package/dist/widget/hooks/useMode.d.ts +2 -2
- package/dist/widget/hooks/useMode.d.ts.map +1 -1
- package/dist/widget/hooks/useOriginSelectedToken.d.ts +2 -15
- package/dist/widget/hooks/useOriginSelectedToken.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
- package/dist/widget/hooks/usePriceImpactWarning.d.ts +1 -1
- package/dist/widget/hooks/usePriceImpactWarning.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts +173 -4
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/hooks/useRecentTokens.d.ts +3 -3
- package/dist/widget/hooks/useRecentTokens.d.ts.map +1 -1
- package/dist/widget/hooks/useRecipients.d.ts +1 -1
- package/dist/widget/hooks/useRecipients.d.ts.map +1 -1
- package/dist/widget/hooks/useSelectedFeeOption.d.ts +2 -2
- package/dist/widget/hooks/useSelectedFeeOption.d.ts.map +1 -1
- package/dist/widget/hooks/useSelectedFundMethod.d.ts +1 -1
- package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -1
- package/dist/widget/hooks/useSelectedRecipient.d.ts +1 -1
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +9 -31
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useSwapAmount.d.ts +1 -1
- package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -1
- package/dist/widget/hooks/useTheme.d.ts +1 -1
- package/dist/widget/hooks/useTheme.d.ts.map +1 -1
- package/dist/widget/hooks/useTokenList.d.ts +7 -31
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/hooks/useTrailsSendTransaction.d.ts +83 -0
- package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -0
- package/dist/widget/hooks/useWalletConnectUri.d.ts +11 -0
- package/dist/widget/hooks/useWalletConnectUri.d.ts.map +1 -0
- package/dist/widget/hooks/useWidgetProps.d.ts +5 -0
- package/dist/widget/hooks/useWidgetProps.d.ts.map +1 -1
- package/dist/widget/index.d.ts +2 -0
- package/dist/widget/index.d.ts.map +1 -1
- package/dist/widget/index.js +8 -5
- package/dist/widget/providers/TrailsModalProvider.d.ts +65 -0
- package/dist/widget/providers/TrailsModalProvider.d.ts.map +1 -0
- package/dist/widget/providers/TrailsProvider.d.ts +1 -1
- package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
- package/dist/widget/types.d.ts +11 -18
- package/dist/widget/types.d.ts.map +1 -1
- package/dist/widget/widget.d.ts +20 -11
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +45 -49
- package/src/aave.ts +387 -138
- package/src/analytics.ts +208 -2
- package/src/chains.ts +65 -64
- package/src/constants.ts +18 -14
- package/src/customTokens.ts +151 -0
- package/src/decoders.ts +4 -7
- package/src/error.ts +7 -3
- package/src/fees.ts +239 -125
- package/src/gasless.ts +54 -108
- package/src/index.ts +29 -9
- package/src/indexerClient.ts +2 -0
- package/src/intentReceiptMonitor.ts +1 -4
- package/src/intentReceiptPoller.ts +2 -2
- package/src/intents.ts +16 -5
- package/src/mode.ts +1 -1
- package/src/mutations.ts +7 -3
- package/src/prepareSend.ts +19 -14
- package/src/prices.ts +1 -1
- package/src/sequenceWallet.ts +2 -2
- package/src/time.ts +28 -0
- package/src/tokenBalances.ts +348 -153
- package/src/tokens.ts +393 -142
- package/src/trailsClient.ts +1 -1
- package/src/trailsRouter.ts +4 -5
- package/src/transactionIntent/deposits/depositOrchestrator.ts +6 -2
- package/src/transactionIntent/deposits/gaslessDeposit.ts +13 -7
- package/src/transactionIntent/deposits/standardDeposit.ts +1 -1
- package/src/transactionIntent/execution/transactionState.ts +1 -1
- package/src/transactionIntent/handlers/crossChain.ts +75 -37
- package/src/transactionIntent/handlers/sameChainSameToken.ts +66 -20
- package/src/transactionIntent/quote/feeExtractors.ts +1 -29
- package/src/transactionIntent/quote/normalizeQuote.ts +99 -7
- package/src/transactionIntent/quote/quoteHelpers.ts +1 -1
- package/src/transactionIntent/types.ts +31 -6
- package/src/transactionIntent/utils/testnetHelpers.ts +0 -5
- package/src/transactionIntent/validators.ts +0 -30
- package/src/transactions.ts +3 -3
- package/src/utils.ts +18 -0
- package/src/wallets.ts +32 -10
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/AccountIntentTransactionHistory.tsx +2 -1
- package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +2 -2
- package/src/widget/components/AddressWalletIcon.tsx +29 -0
- package/src/widget/components/ChainFilterDropdown.tsx +2 -8
- package/src/widget/components/ChainImage.tsx +8 -5
- package/src/widget/components/ChainList.tsx +6 -8
- package/src/widget/components/ClassicSwap.tsx +93 -85
- package/src/widget/components/ConnectWallet.tsx +1 -2
- package/src/widget/components/ConnectedWallets.tsx +17 -4
- package/src/widget/components/DebugMenu.tsx +2 -2
- package/src/widget/components/DebugScreensList.tsx +0 -1
- package/src/widget/components/DepositTracker.tsx +20 -34
- package/src/widget/components/Earn.tsx +7 -6
- package/src/widget/components/FeeOption.tsx +4 -4
- package/src/widget/components/FeeOptions.tsx +19 -39
- package/src/widget/components/Footer.tsx +1 -1
- package/src/widget/components/Fund.tsx +23 -119
- package/src/widget/components/FundMethods.tsx +9 -6
- package/src/widget/components/FundSwap.tsx +6 -5
- package/src/widget/components/FundingMethodSelectorButton.tsx +2 -2
- package/src/widget/components/HookModalContent.tsx +306 -0
- package/src/widget/components/Modal.tsx +1 -1
- package/src/widget/components/OriginSelectionAmount.tsx +135 -0
- package/src/widget/components/Pay.tsx +66 -124
- package/src/widget/components/PoolDeposit.tsx +11 -55
- package/src/widget/components/PoolWithdraw.tsx +3 -3
- package/src/widget/components/QuoteDetails.tsx +473 -728
- package/src/widget/components/Receipt.tsx +74 -7
- package/src/widget/components/RecentTokens.tsx +8 -8
- package/src/widget/components/RecipientSelectorButton.tsx +4 -2
- package/src/widget/components/ScreenHeader.tsx +2 -2
- package/src/widget/components/SearchInputField.tsx +1 -1
- package/src/widget/components/ShadowPortal.tsx +58 -0
- package/src/widget/components/Swap.tsx +6 -5
- package/src/widget/components/ThemeProvider.tsx +1 -1
- package/src/widget/components/TokenList.tsx +3 -4
- package/src/widget/components/TokenSelector.tsx +211 -80
- package/src/widget/components/Tooltip.tsx +18 -7
- package/src/widget/components/TrailsHookModal.tsx +118 -0
- package/src/widget/components/WaasFeeOptions.tsx +333 -138
- package/src/widget/components/WalletConfirmation.tsx +7 -2
- package/src/widget/components/WalletConnect.tsx +197 -235
- package/src/widget/components/WidgetProviders.tsx +75 -0
- package/src/widget/hooks/useAddressWalletIcon.ts +53 -0
- package/src/widget/hooks/useBalanceVisible.tsx +1 -1
- package/src/widget/hooks/useChainFilter.tsx +1 -1
- package/src/widget/hooks/useCheckout.ts +13 -1
- package/src/widget/hooks/useConnector.tsx +18 -0
- package/src/widget/hooks/useCurrentScreen.tsx +3 -3
- package/src/widget/hooks/useCustomTokenFetch.tsx +72 -0
- package/src/widget/hooks/useCustomTokenSearch.tsx +402 -0
- package/src/widget/hooks/useDebounce.ts +25 -0
- package/src/widget/hooks/useDebugScreens.ts +26 -10
- package/src/widget/hooks/useDefaultTokenSelection.tsx +99 -143
- package/src/widget/hooks/useDestinationSelectedToken.tsx +1 -14
- package/src/widget/hooks/useEarnPool.tsx +1 -1
- package/src/widget/hooks/useIntentTransactionHistory.ts +20 -11
- package/src/widget/hooks/useIsMobile.tsx +50 -0
- package/src/widget/hooks/useMode.ts +2 -3
- package/src/widget/hooks/useOriginSelectedToken.tsx +2 -15
- package/src/widget/hooks/usePayMessage.tsx +31 -11
- package/src/widget/hooks/usePriceImpactWarning.ts +1 -1
- package/src/widget/hooks/useQuote.ts +189 -6
- package/src/widget/hooks/useRecentTokens.ts +6 -6
- package/src/widget/hooks/useRecipients.ts +1 -1
- package/src/widget/hooks/useSelectedFeeOption.tsx +2 -2
- package/src/widget/hooks/useSelectedFundMethod.tsx +1 -1
- package/src/widget/hooks/useSelectedRecipient.tsx +1 -1
- package/src/widget/hooks/useSendForm.ts +328 -152
- package/src/widget/hooks/useSwapAmount.tsx +1 -1
- package/src/widget/hooks/useTheme.tsx +1 -1
- package/src/widget/hooks/useTokenList.ts +672 -400
- package/src/widget/hooks/useTrailsSendTransaction.ts +949 -0
- package/src/widget/hooks/useWalletConnectUri.tsx +228 -0
- package/src/widget/hooks/useWidgetProps.tsx +3 -1
- package/src/widget/index.tsx +12 -0
- package/src/widget/providers/TrailsModalProvider.tsx +195 -0
- package/src/widget/providers/TrailsProvider.tsx +9 -3
- package/src/widget/types.ts +12 -20
- package/src/widget/widget.tsx +598 -385
- package/dist/cctp.d.ts +0 -3
- package/dist/cctp.d.ts.map +0 -1
- package/dist/lifi.d.ts +0 -4
- package/dist/lifi.d.ts.map +0 -1
- package/dist/meshconnect.d.ts +0 -171
- package/dist/meshconnect.d.ts.map +0 -1
- package/dist/relaySdk.d.ts +0 -87
- package/dist/relaySdk.d.ts.map +0 -1
- package/dist/widget/components/MeshConnectExchanges.d.ts +0 -7
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +0 -1
- package/dist/widget/components/MeshConnectFlow.d.ts +0 -13
- package/dist/widget/components/MeshConnectFlow.d.ts.map +0 -1
- package/dist/widget/components/MeshConnectIframe.d.ts +0 -15
- package/dist/widget/components/MeshConnectIframe.d.ts.map +0 -1
- package/dist/widget/components/Receive.d.ts +0 -12
- package/dist/widget/components/Receive.d.ts.map +0 -1
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts +0 -14
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +0 -1
- package/src/cctp.ts +0 -54
- package/src/lifi.ts +0 -108
- package/src/meshconnect.ts +0 -531
- package/src/relaySdk.ts +0 -703
- package/src/widget/components/MeshConnectExchanges.tsx +0 -290
- package/src/widget/components/MeshConnectFlow.tsx +0 -90
- package/src/widget/components/MeshConnectIframe.tsx +0 -500
- package/src/widget/components/Receive.tsx +0 -175
- package/src/widget/hooks/useSelectedMeshExchange.tsx +0 -46
|
@@ -1,29 +1,26 @@
|
|
|
1
|
-
import { ResourceStatus } from "@0xsequence/indexer"
|
|
2
1
|
import { Address } from "ox"
|
|
3
2
|
import { useEffect, useMemo, useState } from "react"
|
|
4
|
-
import { isAddressEqual, zeroAddress } from "viem"
|
|
3
|
+
import { isAddress, isAddressEqual, zeroAddress } from "viem"
|
|
5
4
|
import { useAccount } from "wagmi"
|
|
5
|
+
import { useQuery } from "@tanstack/react-query"
|
|
6
6
|
import { getChainInfo, useSupportedChains } from "../../chains.js"
|
|
7
|
-
import type {
|
|
8
|
-
|
|
9
|
-
TokenBalanceWithPrice,
|
|
10
|
-
NativeTokenBalanceWithPrice,
|
|
11
|
-
} from "../../tokenBalances.js"
|
|
7
|
+
import type { Token } from "../../tokens.js"
|
|
8
|
+
import { isNativeToken as isNativeTokenUtil } from "../../utils.js"
|
|
12
9
|
import {
|
|
13
|
-
formatRawAmount,
|
|
14
10
|
useAccountTotalBalanceUsd,
|
|
15
11
|
useHasSufficientBalanceUsd,
|
|
16
12
|
useTokenBalances,
|
|
13
|
+
formatPriceFields,
|
|
17
14
|
} from "../../tokenBalances.js"
|
|
18
|
-
import {
|
|
19
|
-
getFormatttedTokenName,
|
|
20
|
-
useSupportedTokens,
|
|
21
|
-
useGetTokenImageUrl,
|
|
22
|
-
} from "../../tokens.js"
|
|
15
|
+
import { useSupportedTokens, useGetTokenImageUrl } from "../../tokens.js"
|
|
23
16
|
import { useIndexerGatewayClient } from "../../indexerClient.js"
|
|
24
17
|
import { useTrailsClient } from "../../trailsClient.js"
|
|
25
18
|
import { useTokenPrices } from "../../prices.js"
|
|
26
19
|
import { logger } from "../../logger.js"
|
|
20
|
+
import { useTrails } from "../providers/TrailsProvider.js"
|
|
21
|
+
import { useCustomTokenFetch } from "./useCustomTokenFetch.js"
|
|
22
|
+
import { useChainFilter } from "./useChainFilter.js"
|
|
23
|
+
import { useDebounce } from "./useDebounce.js"
|
|
27
24
|
|
|
28
25
|
// Standard tokens for QR code and exchange modes
|
|
29
26
|
const STANDARD_TOKENS = [
|
|
@@ -36,6 +33,79 @@ const STANDARD_TOKENS = [
|
|
|
36
33
|
"WETH",
|
|
37
34
|
] as const
|
|
38
35
|
|
|
36
|
+
// Type for API token info from getTokenList endpoint
|
|
37
|
+
type ApiTokenInfo = {
|
|
38
|
+
chainId: number
|
|
39
|
+
address: string
|
|
40
|
+
name: string
|
|
41
|
+
symbol: string
|
|
42
|
+
decimals: number
|
|
43
|
+
supportsBridging?: boolean
|
|
44
|
+
logoUri?: string
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Helper function to validate and clamp decimals (1-18)
|
|
48
|
+
function validateDecimals(decimals: number): number {
|
|
49
|
+
return Math.max(1, Math.min(18, Math.floor(decimals || 18)))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Helper function to check if token is native
|
|
53
|
+
function isNativeToken(token: Token | ApiTokenInfo): boolean {
|
|
54
|
+
// Token has isNativeToken property
|
|
55
|
+
if ("isNativeToken" in token && typeof token.isNativeToken === "boolean") {
|
|
56
|
+
return token.isNativeToken
|
|
57
|
+
}
|
|
58
|
+
// ApiTokenInfo uses "address" field (intermediate type before conversion to Token)
|
|
59
|
+
if ("address" in token) {
|
|
60
|
+
return isNativeTokenUtil(token.address)
|
|
61
|
+
}
|
|
62
|
+
return false
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Helper function to get token key for mapping
|
|
66
|
+
function getTokenKey(chainId: number, contractAddress: string): string {
|
|
67
|
+
return `${chainId}:${contractAddress.toLowerCase()}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Helper function to convert API token info to Token
|
|
71
|
+
function convertApiTokenToToken(
|
|
72
|
+
tokenInfo: ApiTokenInfo,
|
|
73
|
+
getTokenImageUrl: (params: {
|
|
74
|
+
chainId: number
|
|
75
|
+
contractAddress: string
|
|
76
|
+
symbol: string
|
|
77
|
+
}) => string,
|
|
78
|
+
): Token {
|
|
79
|
+
const chainInfo = getChainInfo(tokenInfo.chainId)
|
|
80
|
+
const isNative = isNativeToken(tokenInfo)
|
|
81
|
+
const validatedDecimals = validateDecimals(tokenInfo.decimals)
|
|
82
|
+
|
|
83
|
+
return {
|
|
84
|
+
symbol: isNative
|
|
85
|
+
? chainInfo?.nativeCurrency.symbol || "ETH"
|
|
86
|
+
: tokenInfo.symbol,
|
|
87
|
+
name: isNative
|
|
88
|
+
? chainInfo?.nativeCurrency.name || "Native Token"
|
|
89
|
+
: tokenInfo.name,
|
|
90
|
+
decimals: isNative
|
|
91
|
+
? chainInfo?.nativeCurrency.decimals || 18
|
|
92
|
+
: validatedDecimals,
|
|
93
|
+
contractAddress: isNative ? zeroAddress : tokenInfo.address,
|
|
94
|
+
chainId: tokenInfo.chainId,
|
|
95
|
+
chainName: chainInfo?.name || `Chain ${tokenInfo.chainId}`,
|
|
96
|
+
imageUrl:
|
|
97
|
+
tokenInfo.logoUri ||
|
|
98
|
+
getTokenImageUrl({
|
|
99
|
+
chainId: tokenInfo.chainId,
|
|
100
|
+
contractAddress: isNative ? zeroAddress : tokenInfo.address,
|
|
101
|
+
symbol: isNative
|
|
102
|
+
? chainInfo?.nativeCurrency.symbol || "ETH"
|
|
103
|
+
: tokenInfo.symbol,
|
|
104
|
+
}),
|
|
105
|
+
isNativeToken: isNative,
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
39
109
|
// Helper function to calculate search relevance score
|
|
40
110
|
function calculateSearchRelevanceScore(
|
|
41
111
|
tokenSymbol: string,
|
|
@@ -74,35 +144,6 @@ function calculateSearchRelevanceScore(
|
|
|
74
144
|
return score
|
|
75
145
|
}
|
|
76
146
|
|
|
77
|
-
export interface Token {
|
|
78
|
-
id: number
|
|
79
|
-
name: string
|
|
80
|
-
symbol: string
|
|
81
|
-
balance: string
|
|
82
|
-
imageUrl: string
|
|
83
|
-
chainId: number
|
|
84
|
-
contractAddress: string
|
|
85
|
-
balanceUsdFormatted: string
|
|
86
|
-
tokenPriceUsd: number
|
|
87
|
-
contractInfo?: {
|
|
88
|
-
decimals: number
|
|
89
|
-
symbol: string
|
|
90
|
-
name: string
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
export type TokenFormatted = Token &
|
|
95
|
-
TokenBalanceExtended & {
|
|
96
|
-
balanceFormatted: string
|
|
97
|
-
tokenPriceUsd: number
|
|
98
|
-
balanceUsdFormatted: string
|
|
99
|
-
isNative: boolean
|
|
100
|
-
tokenName: string
|
|
101
|
-
priceUsd: number
|
|
102
|
-
isSufficientBalance: boolean
|
|
103
|
-
chainName: string
|
|
104
|
-
}
|
|
105
|
-
|
|
106
147
|
export type UseTokenListProps = {
|
|
107
148
|
onContinue: (selectedToken: Token) => void
|
|
108
149
|
targetAmountUsd?: number | null
|
|
@@ -114,14 +155,16 @@ export type UseTokenListProps = {
|
|
|
114
155
|
export type UseTokenListReturn = {
|
|
115
156
|
selectedToken: Token | null
|
|
116
157
|
searchQuery: string
|
|
117
|
-
handleTokenSelect: (token:
|
|
118
|
-
filteredTokens:
|
|
158
|
+
handleTokenSelect: (token: Token) => void
|
|
159
|
+
filteredTokens: Token[]
|
|
119
160
|
isLoadingSortedTokens: boolean
|
|
161
|
+
isLoadingSearchTokens?: boolean
|
|
162
|
+
isSearching: boolean // True when search is in progress (typing or fetching)
|
|
120
163
|
balanceError: Error | null
|
|
121
164
|
showContinueButton: boolean
|
|
122
|
-
isTokenSelected: (token:
|
|
165
|
+
isTokenSelected: (token: Token) => boolean
|
|
123
166
|
setSearchQuery: (query: string) => void
|
|
124
|
-
filteredTokensFormatted:
|
|
167
|
+
filteredTokensFormatted: Token[]
|
|
125
168
|
totalBalanceUsd: number
|
|
126
169
|
totalBalanceUsdFormatted: string
|
|
127
170
|
isLoadingTotalBalanceUsd: boolean
|
|
@@ -152,6 +195,145 @@ export function useTokenList({
|
|
|
152
195
|
const { supportedTokens, isLoadingTokens: isLoadingSupportedTokens } =
|
|
153
196
|
useSupportedTokens()
|
|
154
197
|
|
|
198
|
+
// Get trails config for API calls
|
|
199
|
+
const trailsConfig = useTrails()
|
|
200
|
+
|
|
201
|
+
// Get supported chains for getTokenList API
|
|
202
|
+
const { supportedChains } = useSupportedChains()
|
|
203
|
+
|
|
204
|
+
// Get chain filter for destination token list
|
|
205
|
+
const { selectedChainId: filterByChainId } = useChainFilter()
|
|
206
|
+
|
|
207
|
+
// Only use API search for exchange/qr-code modes or when allSupportedTokens is true
|
|
208
|
+
// (where we show all tokens, not just user balances)
|
|
209
|
+
// For other modes, we only filter user's token balances locally
|
|
210
|
+
const shouldUseApiSearch =
|
|
211
|
+
fundMethod === "exchange" || fundMethod === "qr-code" || allSupportedTokens
|
|
212
|
+
|
|
213
|
+
// Memoize chain IDs to avoid recalculating
|
|
214
|
+
const apiSearchChainIds = useMemo(
|
|
215
|
+
() => supportedChains.map((chain) => chain.id),
|
|
216
|
+
[supportedChains],
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
// Check if search query is an address
|
|
220
|
+
const trimmedSearchQuery = useMemo(() => searchQuery.trim(), [searchQuery])
|
|
221
|
+
// Debounce the search query to avoid excessive API calls (300ms delay)
|
|
222
|
+
const debouncedSearchQuery = useDebounce(trimmedSearchQuery, 300)
|
|
223
|
+
const isSearchQueryAddress = useMemo(
|
|
224
|
+
() => (debouncedSearchQuery ? isAddress(debouncedSearchQuery) : false),
|
|
225
|
+
[debouncedSearchQuery],
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
// Track if user is actively typing (search query changed but debounce hasn't fired yet)
|
|
229
|
+
const isSearchPending = trimmedSearchQuery !== debouncedSearchQuery
|
|
230
|
+
|
|
231
|
+
// Call getTokenList API when there's a search query
|
|
232
|
+
const { data: searchTokensResult, isLoading: isLoadingSearchTokens } =
|
|
233
|
+
useQuery({
|
|
234
|
+
queryKey: [
|
|
235
|
+
"getTokenList",
|
|
236
|
+
debouncedSearchQuery,
|
|
237
|
+
apiSearchChainIds,
|
|
238
|
+
trailsConfig.trailsApiUrl,
|
|
239
|
+
trailsConfig.trailsApiKey,
|
|
240
|
+
],
|
|
241
|
+
queryFn: async () => {
|
|
242
|
+
if (
|
|
243
|
+
!debouncedSearchQuery ||
|
|
244
|
+
!trailsClient ||
|
|
245
|
+
!trailsConfig.trailsApiKey
|
|
246
|
+
) {
|
|
247
|
+
return { tokens: [] }
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
try {
|
|
251
|
+
const response = await trailsClient.getTokenList({
|
|
252
|
+
searchQuery: debouncedSearchQuery,
|
|
253
|
+
chainIds: apiSearchChainIds,
|
|
254
|
+
includeAllListed: true,
|
|
255
|
+
})
|
|
256
|
+
return response
|
|
257
|
+
} catch (error) {
|
|
258
|
+
logger.console.warn("[trails-sdk] Error searching tokens:", error)
|
|
259
|
+
return { tokens: [] }
|
|
260
|
+
}
|
|
261
|
+
},
|
|
262
|
+
enabled:
|
|
263
|
+
shouldUseApiSearch &&
|
|
264
|
+
debouncedSearchQuery.length > 0 &&
|
|
265
|
+
!!trailsClient &&
|
|
266
|
+
!!trailsConfig.trailsApiKey,
|
|
267
|
+
staleTime: 30 * 1000, // 30 seconds
|
|
268
|
+
gcTime: 5 * 60 * 1000, // 5 minutes
|
|
269
|
+
})
|
|
270
|
+
|
|
271
|
+
// Fetch custom token info if searching by address and getTokenList returned no results
|
|
272
|
+
const hasNoSearchResults = searchTokensResult?.tokens?.length === 0
|
|
273
|
+
const shouldFetchCustomToken =
|
|
274
|
+
shouldUseApiSearch &&
|
|
275
|
+
isSearchQueryAddress &&
|
|
276
|
+
hasNoSearchResults &&
|
|
277
|
+
!isLoadingSearchTokens &&
|
|
278
|
+
trimmedSearchQuery.length > 0
|
|
279
|
+
const customTokenChainId = supportedChains[0]?.id ?? null
|
|
280
|
+
|
|
281
|
+
const { tokenInfo: customTokenInfo, isLoading: isLoadingCustomToken } =
|
|
282
|
+
useCustomTokenFetch({
|
|
283
|
+
address: shouldFetchCustomToken ? trimmedSearchQuery : null,
|
|
284
|
+
chainId: customTokenChainId,
|
|
285
|
+
enabled: shouldFetchCustomToken,
|
|
286
|
+
})
|
|
287
|
+
|
|
288
|
+
// Fetch tokens from getTokenList when a chain is selected (for destination token list only)
|
|
289
|
+
// This fetches all available tokens from that chain, not just filtering existing tokens
|
|
290
|
+
const {
|
|
291
|
+
data: chainFilteredTokenList,
|
|
292
|
+
isLoading: isLoadingChainFilteredTokens,
|
|
293
|
+
} = useQuery({
|
|
294
|
+
queryKey: [
|
|
295
|
+
"getTokenList",
|
|
296
|
+
filterByChainId,
|
|
297
|
+
trailsConfig.trailsApiUrl,
|
|
298
|
+
trailsConfig.trailsApiKey,
|
|
299
|
+
],
|
|
300
|
+
queryFn: async () => {
|
|
301
|
+
if (
|
|
302
|
+
!filterByChainId ||
|
|
303
|
+
!trailsClient ||
|
|
304
|
+
!trailsConfig.trailsApiKey ||
|
|
305
|
+
!shouldUseApiSearch
|
|
306
|
+
) {
|
|
307
|
+
return { tokens: [] }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
try {
|
|
311
|
+
// Fetch tokens from the selected chain with limit of 100 for single chain
|
|
312
|
+
const response = await trailsClient.getTokenList({
|
|
313
|
+
chainIds: [filterByChainId],
|
|
314
|
+
includeAllListed: true,
|
|
315
|
+
limit: 100,
|
|
316
|
+
})
|
|
317
|
+
return response
|
|
318
|
+
} catch (error) {
|
|
319
|
+
logger.console.warn(
|
|
320
|
+
"[trails-sdk] Error fetching tokens for chain filter:",
|
|
321
|
+
error,
|
|
322
|
+
)
|
|
323
|
+
return { tokens: [] }
|
|
324
|
+
}
|
|
325
|
+
},
|
|
326
|
+
enabled:
|
|
327
|
+
shouldUseApiSearch &&
|
|
328
|
+
filterByChainId !== null &&
|
|
329
|
+
!!trailsClient &&
|
|
330
|
+
!!trailsConfig.trailsApiKey,
|
|
331
|
+
staleTime: 60 * 60 * 1000, // 1 hour - tokens rarely change
|
|
332
|
+
gcTime: 24 * 60 * 60 * 1000, // 24 hours
|
|
333
|
+
refetchOnWindowFocus: false,
|
|
334
|
+
refetchOnReconnect: false,
|
|
335
|
+
})
|
|
336
|
+
|
|
155
337
|
// Fetch prices for supported tokens when allSupportedTokens is true OR for qr-code/exchange modes
|
|
156
338
|
const supportedTokensForPricing = useMemo(() => {
|
|
157
339
|
if (fundMethod === "qr-code" || fundMethod === "exchange") {
|
|
@@ -187,11 +369,16 @@ export function useTokenList({
|
|
|
187
369
|
// Determine loading state based on fund method and allSupportedTokens
|
|
188
370
|
const isLoadingTokens =
|
|
189
371
|
fundMethod === "qr-code" || fundMethod === "exchange"
|
|
190
|
-
? isLoadingSupportedTokens ||
|
|
372
|
+
? isLoadingSupportedTokens ||
|
|
373
|
+
isLoadingSupportedTokenPrices ||
|
|
374
|
+
isLoadingSearchTokens ||
|
|
375
|
+
isLoadingCustomToken ||
|
|
376
|
+
isLoadingChainFilteredTokens
|
|
191
377
|
: allSupportedTokens
|
|
192
378
|
? isLoadingSortedTokens ||
|
|
193
379
|
isLoadingSupportedTokens ||
|
|
194
|
-
isLoadingSupportedTokenPrices
|
|
380
|
+
isLoadingSupportedTokenPrices ||
|
|
381
|
+
isLoadingChainFilteredTokens
|
|
195
382
|
: isLoadingSortedTokens
|
|
196
383
|
|
|
197
384
|
const {
|
|
@@ -213,90 +400,194 @@ export function useTokenList({
|
|
|
213
400
|
return new Set(supportedToChains.map((c) => c.id))
|
|
214
401
|
}, [supportedToChains])
|
|
215
402
|
|
|
216
|
-
|
|
403
|
+
// Map chain-filtered token list results to Token format
|
|
404
|
+
// Used when a chain is selected for destination token list
|
|
405
|
+
const chainFilteredApiTokens = useMemo(() => {
|
|
406
|
+
if (
|
|
407
|
+
!shouldUseApiSearch ||
|
|
408
|
+
!chainFilteredTokenList?.tokens?.length ||
|
|
409
|
+
filterByChainId === null
|
|
410
|
+
) {
|
|
411
|
+
return []
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
return chainFilteredTokenList.tokens
|
|
415
|
+
.filter((tokenInfo: ApiTokenInfo) => {
|
|
416
|
+
// Validate decimals - must be between 1 and 18
|
|
417
|
+
const decimals = tokenInfo.decimals
|
|
418
|
+
if (!Number.isInteger(decimals) || decimals < 1 || decimals > 18) {
|
|
419
|
+
logger.console.warn(
|
|
420
|
+
"[trails-sdk] Skipping token with invalid decimals:",
|
|
421
|
+
{
|
|
422
|
+
symbol: tokenInfo.symbol,
|
|
423
|
+
address: tokenInfo.address,
|
|
424
|
+
decimals: tokenInfo.decimals,
|
|
425
|
+
},
|
|
426
|
+
)
|
|
427
|
+
return false
|
|
428
|
+
}
|
|
429
|
+
return true
|
|
430
|
+
})
|
|
431
|
+
.map((tokenInfo: ApiTokenInfo) =>
|
|
432
|
+
convertApiTokenToToken(tokenInfo, getTokenImageUrl),
|
|
433
|
+
)
|
|
434
|
+
}, [
|
|
435
|
+
chainFilteredTokenList,
|
|
436
|
+
getTokenImageUrl,
|
|
437
|
+
shouldUseApiSearch,
|
|
438
|
+
filterByChainId,
|
|
439
|
+
])
|
|
440
|
+
|
|
441
|
+
const sortedTokens = useMemo<Array<Token>>(() => {
|
|
217
442
|
// If fundMethod is "qr-code" or "exchange", use filtered supported tokens
|
|
218
443
|
if (fundMethod === "qr-code" || fundMethod === "exchange") {
|
|
444
|
+
// If chain filter is active and we have chain-filtered tokens, use those
|
|
445
|
+
if (
|
|
446
|
+
shouldUseApiSearch &&
|
|
447
|
+
filterByChainId !== null &&
|
|
448
|
+
chainFilteredApiTokens.length > 0
|
|
449
|
+
) {
|
|
450
|
+
// Merge chain-filtered tokens with local tokens (prefer local tokens with balances)
|
|
451
|
+
const localTokensMap = new Map<string, Token>()
|
|
452
|
+
for (const tokenItem of allSortedTokens) {
|
|
453
|
+
const contractAddress = tokenItem.contractAddress || zeroAddress
|
|
454
|
+
const key = getTokenKey(tokenItem.chainId || 0, contractAddress)
|
|
455
|
+
localTokensMap.set(key, tokenItem)
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const mergedTokens: Token[] = chainFilteredApiTokens.map(
|
|
459
|
+
(apiToken: Token) => {
|
|
460
|
+
const contractAddress = apiToken.contractAddress || zeroAddress
|
|
461
|
+
const key = getTokenKey(apiToken.chainId || 0, contractAddress)
|
|
462
|
+
const localToken = localTokensMap.get(key)
|
|
463
|
+
|
|
464
|
+
// If local token exists and has balance, use it; otherwise use API token
|
|
465
|
+
return localToken && (localToken.balanceUsd || 0) > 0
|
|
466
|
+
? localToken
|
|
467
|
+
: apiToken
|
|
468
|
+
},
|
|
469
|
+
)
|
|
470
|
+
|
|
471
|
+
// Add any local tokens that weren't in API results
|
|
472
|
+
const apiTokenKeys = new Set(
|
|
473
|
+
mergedTokens.map((token) =>
|
|
474
|
+
getTokenKey(
|
|
475
|
+
token.chainId || 0,
|
|
476
|
+
token.contractAddress || zeroAddress,
|
|
477
|
+
),
|
|
478
|
+
),
|
|
479
|
+
)
|
|
480
|
+
for (const localToken of allSortedTokens) {
|
|
481
|
+
const key = getTokenKey(
|
|
482
|
+
localToken.chainId || 0,
|
|
483
|
+
localToken.contractAddress || zeroAddress,
|
|
484
|
+
)
|
|
485
|
+
if (
|
|
486
|
+
!apiTokenKeys.has(key) &&
|
|
487
|
+
(localToken.chainId || 0) === filterByChainId
|
|
488
|
+
) {
|
|
489
|
+
mergedTokens.push(localToken)
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
return mergedTokens
|
|
494
|
+
}
|
|
495
|
+
|
|
219
496
|
// Filter to only show specific tokens for QR code and exchange modes
|
|
220
497
|
const filteredTokens = supportedTokens.filter((token: any) => {
|
|
221
498
|
const symbol = token.symbol.toUpperCase()
|
|
222
499
|
return STANDARD_TOKENS.includes(symbol as any)
|
|
223
500
|
})
|
|
224
501
|
|
|
225
|
-
// Convert SupportedToken to
|
|
226
|
-
const tokensWithPrices = filteredTokens.map(
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
p
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
502
|
+
// Convert SupportedToken to Token format and add price data
|
|
503
|
+
const tokensWithPrices: Token[] = filteredTokens.map(
|
|
504
|
+
(token: Token): Token => {
|
|
505
|
+
// Find price data for this token
|
|
506
|
+
const priceData = supportedTokenPrices?.find(
|
|
507
|
+
(p: any) =>
|
|
508
|
+
p.token.tokenAddress?.toLowerCase() ===
|
|
509
|
+
(token.contractAddress || zeroAddress).toLowerCase() &&
|
|
510
|
+
p.token.chainId === token.chainId,
|
|
511
|
+
)
|
|
234
512
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
chainId: token.chainId,
|
|
242
|
-
balance: "0", // No balance info for QR code and exchange modes
|
|
243
|
-
balanceUsd: 0,
|
|
244
|
-
balanceUsdFormatted: "0",
|
|
245
|
-
price:
|
|
246
|
-
priceData?.priceUsd !== undefined
|
|
247
|
-
? { value: priceData.priceUsd, currency: "USD" }
|
|
248
|
-
: { value: 0, currency: "USD" },
|
|
249
|
-
price24hVol: 0, // Volume data not available in new API
|
|
250
|
-
imageUrl: token.imageUrl,
|
|
251
|
-
symbol: token.symbol,
|
|
252
|
-
isSufficientBalance: true, // Always true for QR code and exchange modes
|
|
253
|
-
accountAddress: address as Address.Address,
|
|
254
|
-
} as NativeTokenBalanceWithPrice
|
|
255
|
-
} else {
|
|
256
|
-
// ERC20 token format
|
|
513
|
+
const priceUsd = priceData?.priceUsd
|
|
514
|
+
|
|
515
|
+
// Format price fields if price exists
|
|
516
|
+
const priceFields = priceUsd ? formatPriceFields(priceUsd) : undefined
|
|
517
|
+
|
|
518
|
+
// Return Token with price data
|
|
257
519
|
return {
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
? { value: priceData.priceUsd, currency: "USD" }
|
|
266
|
-
: { value: 0, currency: "USD" },
|
|
267
|
-
price24hVol: 0, // Volume data not available in new API
|
|
268
|
-
imageUrl: token.imageUrl,
|
|
269
|
-
contractInfo: {
|
|
270
|
-
decimals: token.decimals,
|
|
271
|
-
symbol: token.symbol,
|
|
272
|
-
name: token.name,
|
|
273
|
-
},
|
|
520
|
+
...token,
|
|
521
|
+
// No balance info for QR code and exchange modes
|
|
522
|
+
...(priceFields && {
|
|
523
|
+
priceUsd: priceFields.priceUsd,
|
|
524
|
+
priceUsdFormatted: priceFields.priceUsdFormatted,
|
|
525
|
+
priceUsdDisplay: priceFields.priceUsdDisplay,
|
|
526
|
+
}),
|
|
274
527
|
isSufficientBalance: true, // Always true for QR code and exchange modes
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
transactionIndex: 0,
|
|
283
|
-
uniqueCollectibles: "0",
|
|
284
|
-
isSummary: false,
|
|
285
|
-
} as any
|
|
286
|
-
}
|
|
528
|
+
}
|
|
529
|
+
},
|
|
530
|
+
)
|
|
531
|
+
|
|
532
|
+
// Sort by price (highest first) since volume is not available
|
|
533
|
+
return tokensWithPrices.sort((a, b) => {
|
|
534
|
+
return (b.priceUsd || 0) - (a.priceUsd || 0)
|
|
287
535
|
})
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// If allSupportedTokens is true and chain filter is active, use chain-filtered tokens
|
|
539
|
+
if (
|
|
540
|
+
allSupportedTokens &&
|
|
541
|
+
shouldUseApiSearch &&
|
|
542
|
+
filterByChainId !== null &&
|
|
543
|
+
chainFilteredApiTokens.length > 0
|
|
544
|
+
) {
|
|
545
|
+
// Merge chain-filtered tokens with local tokens (prefer local tokens with balances)
|
|
546
|
+
const localTokensMap = new Map<string, Token>()
|
|
547
|
+
for (const tokenItem of allSortedTokens) {
|
|
548
|
+
const contractAddress = tokenItem.contractAddress || zeroAddress
|
|
549
|
+
const key = getTokenKey(tokenItem.chainId || 0, contractAddress)
|
|
550
|
+
localTokensMap.set(key, tokenItem)
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
const mergedTokens: Token[] = chainFilteredApiTokens.map(
|
|
554
|
+
(apiToken: Token) => {
|
|
555
|
+
const contractAddress = apiToken.contractAddress || zeroAddress
|
|
556
|
+
const key = getTokenKey(apiToken.chainId || 0, contractAddress)
|
|
557
|
+
const localToken = localTokensMap.get(key)
|
|
558
|
+
|
|
559
|
+
// If local token exists and has balance, use it; otherwise use API token
|
|
560
|
+
return localToken && (localToken.balanceUsd || 0) > 0
|
|
561
|
+
? localToken
|
|
562
|
+
: apiToken
|
|
563
|
+
},
|
|
564
|
+
)
|
|
288
565
|
|
|
289
|
-
//
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
566
|
+
// Add any local tokens that weren't in API results
|
|
567
|
+
const apiTokenKeys = new Set(
|
|
568
|
+
mergedTokens.map((token) =>
|
|
569
|
+
getTokenKey(token.chainId || 0, token.contractAddress || zeroAddress),
|
|
570
|
+
),
|
|
571
|
+
)
|
|
572
|
+
for (const localToken of allSortedTokens) {
|
|
573
|
+
const key = getTokenKey(
|
|
574
|
+
localToken.chainId || 0,
|
|
575
|
+
localToken.contractAddress || zeroAddress,
|
|
576
|
+
)
|
|
577
|
+
if (
|
|
578
|
+
!apiTokenKeys.has(key) &&
|
|
579
|
+
(localToken.chainId || 0) === filterByChainId
|
|
580
|
+
) {
|
|
581
|
+
mergedTokens.push(localToken)
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
return mergedTokens
|
|
295
586
|
}
|
|
296
587
|
|
|
297
|
-
// Default behavior: use account-specific tokens
|
|
298
|
-
return allSortedTokens.filter((token:
|
|
299
|
-
if (!supportedChainIds.has(token.chainId)) {
|
|
588
|
+
// Default behavior: use account-specific tokens (already Token[])
|
|
589
|
+
return allSortedTokens.filter((token: Token) => {
|
|
590
|
+
if (!supportedChainIds.has(token.chainId || 0)) {
|
|
300
591
|
return false
|
|
301
592
|
}
|
|
302
593
|
return true
|
|
@@ -306,8 +597,11 @@ export function useTokenList({
|
|
|
306
597
|
supportedChainIds,
|
|
307
598
|
fundMethod,
|
|
308
599
|
supportedTokens,
|
|
309
|
-
address,
|
|
310
600
|
supportedTokenPrices,
|
|
601
|
+
shouldUseApiSearch,
|
|
602
|
+
filterByChainId,
|
|
603
|
+
chainFilteredApiTokens,
|
|
604
|
+
allSupportedTokens,
|
|
311
605
|
])
|
|
312
606
|
|
|
313
607
|
useEffect(() => {
|
|
@@ -316,17 +610,14 @@ export function useTokenList({
|
|
|
316
610
|
}
|
|
317
611
|
}, [balanceError, onError])
|
|
318
612
|
|
|
319
|
-
const handleTokenSelect = (token:
|
|
320
|
-
const isNative =
|
|
321
|
-
|
|
322
|
-
const chainInfo = getChainInfo(token.chainId)
|
|
323
|
-
const imageUrl = token.imageUrl
|
|
324
|
-
const decimals = isNative ? 18 : token.contractInfo?.decimals
|
|
613
|
+
const handleTokenSelect = (token: Token) => {
|
|
614
|
+
const isNative = token.isNativeToken ?? false
|
|
615
|
+
const decimals = token.decimals
|
|
325
616
|
if (!decimals) {
|
|
326
617
|
logger.console.warn(
|
|
327
618
|
"[trails-sdk] [useTokenList] Missing decimals for token:",
|
|
328
619
|
{
|
|
329
|
-
token: token
|
|
620
|
+
token: token,
|
|
330
621
|
chainId: token.chainId,
|
|
331
622
|
isNative,
|
|
332
623
|
},
|
|
@@ -335,71 +626,32 @@ export function useTokenList({
|
|
|
335
626
|
return
|
|
336
627
|
}
|
|
337
628
|
|
|
338
|
-
|
|
339
|
-
if (isNative) {
|
|
340
|
-
formattedToken = {
|
|
341
|
-
id: (token as TokenBalanceExtended).chainId,
|
|
342
|
-
name: chainInfo?.nativeCurrency.name || "Native Token",
|
|
343
|
-
symbol: chainInfo?.nativeCurrency.symbol || "ETH",
|
|
344
|
-
balance: (token as TokenBalanceExtended).balance,
|
|
345
|
-
imageUrl,
|
|
346
|
-
chainId: (token as TokenBalanceExtended).chainId,
|
|
347
|
-
contractAddress: zeroAddress,
|
|
348
|
-
balanceUsdFormatted:
|
|
349
|
-
(token as TokenBalanceExtended).balanceUsdFormatted ?? "",
|
|
350
|
-
tokenPriceUsd: (token as TokenBalanceExtended).price?.value ?? 0,
|
|
351
|
-
contractInfo: {
|
|
352
|
-
decimals,
|
|
353
|
-
symbol: chainInfo?.nativeCurrency.symbol || "ETH",
|
|
354
|
-
name: chainInfo?.nativeCurrency.name || "Native Token",
|
|
355
|
-
},
|
|
356
|
-
}
|
|
357
|
-
} else {
|
|
358
|
-
formattedToken = {
|
|
359
|
-
id: token.chainId,
|
|
360
|
-
name: token.contractInfo?.name || "Unknown Token",
|
|
361
|
-
symbol: token.contractInfo?.symbol || "???",
|
|
362
|
-
balance: token.balance,
|
|
363
|
-
imageUrl,
|
|
364
|
-
chainId: token.chainId,
|
|
365
|
-
contractAddress: token.contractAddress,
|
|
366
|
-
contractInfo: {
|
|
367
|
-
...token.contractInfo,
|
|
368
|
-
name: token.contractInfo?.name ?? "Unknown Token",
|
|
369
|
-
symbol: token.contractInfo?.symbol ?? "???",
|
|
370
|
-
decimals,
|
|
371
|
-
},
|
|
372
|
-
balanceUsdFormatted: token.balanceUsdFormatted ?? "",
|
|
373
|
-
tokenPriceUsd: token.price?.value ?? 0,
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
setSelectedToken(formattedToken)
|
|
629
|
+
setSelectedToken(token)
|
|
378
630
|
const canContinue =
|
|
379
631
|
(!targetAmountUsd || (targetAmountUsd && hasSufficientBalanceUsd)) &&
|
|
380
|
-
token.isSufficientBalance
|
|
632
|
+
(token.isSufficientBalance ?? true)
|
|
381
633
|
|
|
382
634
|
if (!canContinue) {
|
|
383
635
|
logger.console.warn("[trails-sdk] Cannot continue with token selection", {
|
|
384
|
-
token:
|
|
636
|
+
token: token,
|
|
385
637
|
targetAmountUsd,
|
|
386
638
|
hasSufficientBalanceUsd,
|
|
387
639
|
isSufficientBalance: token.isSufficientBalance,
|
|
388
640
|
})
|
|
389
641
|
}
|
|
390
642
|
|
|
391
|
-
onContinue(
|
|
643
|
+
onContinue(token)
|
|
392
644
|
}
|
|
393
645
|
|
|
394
|
-
const isTokenSelected = (token:
|
|
646
|
+
const isTokenSelected = (token: Token): boolean => {
|
|
395
647
|
if (!selectedToken) return false
|
|
396
648
|
|
|
397
|
-
const isNative =
|
|
398
|
-
|
|
649
|
+
const isNative = token.isNativeToken ?? false
|
|
650
|
+
const selectedIsNative = selectedToken.isNativeToken ?? false
|
|
399
651
|
return (
|
|
400
|
-
selectedToken.chainId === token.chainId &&
|
|
652
|
+
(selectedToken.chainId || 0) === (token.chainId || 0) &&
|
|
401
653
|
(isNative
|
|
402
|
-
?
|
|
654
|
+
? selectedIsNative
|
|
403
655
|
: isAddressEqual(
|
|
404
656
|
Address.from(selectedToken.contractAddress),
|
|
405
657
|
Address.from(token.contractAddress),
|
|
@@ -407,105 +659,182 @@ export function useTokenList({
|
|
|
407
659
|
)
|
|
408
660
|
}
|
|
409
661
|
|
|
662
|
+
// Map API search results to Token format
|
|
663
|
+
// Only used for exchange/qr-code modes
|
|
664
|
+
const apiSearchTokens = useMemo(() => {
|
|
665
|
+
if (!shouldUseApiSearch || !searchTokensResult?.tokens?.length) {
|
|
666
|
+
return []
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
return searchTokensResult.tokens
|
|
670
|
+
.filter((tokenInfo: ApiTokenInfo) => {
|
|
671
|
+
// Validate decimals - must be between 1 and 18 (formatRawAmount requires 1-18)
|
|
672
|
+
const decimals = tokenInfo.decimals
|
|
673
|
+
if (!Number.isInteger(decimals) || decimals < 1 || decimals > 18) {
|
|
674
|
+
logger.console.warn(
|
|
675
|
+
"[trails-sdk] Skipping token with invalid decimals:",
|
|
676
|
+
{
|
|
677
|
+
symbol: tokenInfo.symbol,
|
|
678
|
+
address: tokenInfo.address,
|
|
679
|
+
decimals: tokenInfo.decimals,
|
|
680
|
+
},
|
|
681
|
+
)
|
|
682
|
+
return false
|
|
683
|
+
}
|
|
684
|
+
return true
|
|
685
|
+
})
|
|
686
|
+
.map((tokenInfo: ApiTokenInfo) =>
|
|
687
|
+
convertApiTokenToToken(tokenInfo, getTokenImageUrl),
|
|
688
|
+
)
|
|
689
|
+
}, [searchTokensResult, getTokenImageUrl, shouldUseApiSearch])
|
|
690
|
+
|
|
691
|
+
// Convert custom token info to Token format
|
|
692
|
+
const customTokenAsTokenBalance = useMemo(() => {
|
|
693
|
+
if (!customTokenInfo || !customTokenChainId) return null
|
|
694
|
+
|
|
695
|
+
const chainInfo = getChainInfo(customTokenChainId)
|
|
696
|
+
const validatedDecimals = validateDecimals(customTokenInfo.decimals)
|
|
697
|
+
|
|
698
|
+
return {
|
|
699
|
+
symbol: customTokenInfo.symbol,
|
|
700
|
+
name: customTokenInfo.name,
|
|
701
|
+
decimals: validatedDecimals,
|
|
702
|
+
contractAddress: customTokenInfo.contractAddress,
|
|
703
|
+
chainId: customTokenChainId,
|
|
704
|
+
chainName: chainInfo?.name || `Chain ${customTokenChainId}`,
|
|
705
|
+
imageUrl: getTokenImageUrl({
|
|
706
|
+
chainId: customTokenChainId,
|
|
707
|
+
contractAddress: customTokenInfo.contractAddress,
|
|
708
|
+
symbol: customTokenInfo.symbol,
|
|
709
|
+
}),
|
|
710
|
+
isCustomToken: true,
|
|
711
|
+
} as Token
|
|
712
|
+
}, [customTokenInfo, customTokenChainId, getTokenImageUrl])
|
|
713
|
+
|
|
410
714
|
const filteredTokens = useMemo(() => {
|
|
411
|
-
if
|
|
412
|
-
|
|
715
|
+
// If searching by address and getTokenList returned no results, return custom token if available
|
|
716
|
+
if (
|
|
717
|
+
shouldUseApiSearch &&
|
|
718
|
+
isSearchQueryAddress &&
|
|
719
|
+
hasNoSearchResults &&
|
|
720
|
+
customTokenAsTokenBalance
|
|
721
|
+
) {
|
|
722
|
+
return [customTokenAsTokenBalance]
|
|
413
723
|
}
|
|
414
724
|
|
|
415
|
-
|
|
416
|
-
|
|
725
|
+
// sortedTokens is already Token[] from useTokenBalances
|
|
726
|
+
// Only use API search results for exchange/qr-code modes
|
|
727
|
+
if (
|
|
728
|
+
shouldUseApiSearch &&
|
|
729
|
+
trimmedSearchQuery &&
|
|
730
|
+
apiSearchTokens.length > 0
|
|
731
|
+
) {
|
|
732
|
+
// Create a map of local tokens by chainId and contractAddress for quick lookup
|
|
733
|
+
const localTokensMap = new Map<string, Token>()
|
|
734
|
+
for (const tokenItem of sortedTokens) {
|
|
735
|
+
const contractAddress = tokenItem.contractAddress || zeroAddress
|
|
736
|
+
const key = getTokenKey(tokenItem.chainId || 0, contractAddress)
|
|
737
|
+
localTokensMap.set(key, tokenItem)
|
|
738
|
+
}
|
|
417
739
|
|
|
418
|
-
|
|
419
|
-
(
|
|
420
|
-
const
|
|
421
|
-
|
|
422
|
-
const
|
|
423
|
-
const chainName = chainInfo?.name || ""
|
|
424
|
-
const chainNameLower = chainName.toLowerCase()
|
|
740
|
+
// Merge API results with local tokens (prefer local tokens with balances)
|
|
741
|
+
const mergedTokens: Token[] = apiSearchTokens.map((apiToken: Token) => {
|
|
742
|
+
const contractAddress = apiToken.contractAddress || zeroAddress
|
|
743
|
+
const key = getTokenKey(apiToken.chainId || 0, contractAddress)
|
|
744
|
+
const localToken = localTokensMap.get(key)
|
|
425
745
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
746
|
+
// If local token exists and has balance, use it; otherwise use API token
|
|
747
|
+
return localToken && (localToken.balanceUsd || 0) > 0
|
|
748
|
+
? localToken
|
|
749
|
+
: apiToken
|
|
750
|
+
})
|
|
431
751
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
nativeSymbolLower.includes(part) ||
|
|
440
|
-
nativeNameLower.includes(part),
|
|
441
|
-
)
|
|
442
|
-
return matchesChain && matchesToken
|
|
443
|
-
}
|
|
752
|
+
// Add any local tokens that weren't in API results but match the search
|
|
753
|
+
const apiTokenKeys = new Set(
|
|
754
|
+
mergedTokens.map((token) => {
|
|
755
|
+
const contractAddress = token.contractAddress || zeroAddress
|
|
756
|
+
return getTokenKey(token.chainId || 0, contractAddress)
|
|
757
|
+
}),
|
|
758
|
+
)
|
|
444
759
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
760
|
+
const queryLower = trimmedSearchQuery.toLowerCase()
|
|
761
|
+
for (const tokenItem of sortedTokens) {
|
|
762
|
+
const contractAddress = tokenItem.contractAddress || zeroAddress
|
|
763
|
+
const key = getTokenKey(tokenItem.chainId || 0, contractAddress)
|
|
764
|
+
|
|
765
|
+
if (!apiTokenKeys.has(key)) {
|
|
766
|
+
// Check if token matches search query locally
|
|
767
|
+
const tokenSymbol = (tokenItem.symbol || "").toLowerCase()
|
|
768
|
+
const tokenName = (tokenItem.name || "").toLowerCase()
|
|
769
|
+
|
|
770
|
+
if (
|
|
771
|
+
tokenSymbol.includes(queryLower) ||
|
|
772
|
+
tokenName.includes(queryLower) ||
|
|
773
|
+
contractAddress.toLowerCase().includes(queryLower)
|
|
774
|
+
) {
|
|
775
|
+
mergedTokens.push(tokenItem)
|
|
776
|
+
}
|
|
452
777
|
}
|
|
778
|
+
}
|
|
453
779
|
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
tokenNameLower.includes(part) ||
|
|
470
|
-
contractAddressLower.includes(part),
|
|
471
|
-
)
|
|
472
|
-
return matchesChain && matchesToken
|
|
473
|
-
}
|
|
780
|
+
return mergedTokens
|
|
781
|
+
}
|
|
782
|
+
|
|
783
|
+
// Fallback to local filtering if no API results or not using API search
|
|
784
|
+
if (!trimmedSearchQuery) {
|
|
785
|
+
return sortedTokens
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const queryLower = trimmedSearchQuery.toLowerCase()
|
|
789
|
+
const queryParts = queryLower.split(/\s+/).filter((part) => part.length > 0)
|
|
790
|
+
|
|
791
|
+
const matchingTokens = sortedTokens.filter((token: Token) => {
|
|
792
|
+
const chainInfo = getChainInfo(token.chainId || 0)
|
|
793
|
+
const chainName = chainInfo?.name || ""
|
|
794
|
+
const chainNameLower = chainName.toLowerCase()
|
|
474
795
|
|
|
475
|
-
|
|
476
|
-
|
|
796
|
+
const tokenSymbol = (token.symbol || "???").toLowerCase()
|
|
797
|
+
const tokenName = (token.name || "Unknown Token").toLowerCase()
|
|
798
|
+
const contractAddress = (
|
|
799
|
+
token.contractAddress || zeroAddress
|
|
800
|
+
).toLowerCase()
|
|
801
|
+
|
|
802
|
+
// If multiple query parts, check if they match chain + token combination
|
|
803
|
+
if (queryParts.length > 1) {
|
|
804
|
+
const matchesChain = queryParts.some((part) =>
|
|
805
|
+
chainNameLower.includes(part),
|
|
806
|
+
)
|
|
807
|
+
const matchesToken = queryParts.some(
|
|
477
808
|
(part) =>
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
contractAddressLower.includes(part),
|
|
809
|
+
tokenSymbol.includes(part) ||
|
|
810
|
+
tokenName.includes(part) ||
|
|
811
|
+
contractAddress.includes(part),
|
|
482
812
|
)
|
|
483
|
-
|
|
484
|
-
|
|
813
|
+
return matchesChain && matchesToken
|
|
814
|
+
}
|
|
485
815
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
816
|
+
// Single query part - match against any field
|
|
817
|
+
return queryParts.some(
|
|
818
|
+
(part) =>
|
|
819
|
+
tokenSymbol.includes(part) ||
|
|
820
|
+
tokenName.includes(part) ||
|
|
821
|
+
chainNameLower.includes(part) ||
|
|
822
|
+
contractAddress.includes(part),
|
|
823
|
+
)
|
|
824
|
+
})
|
|
490
825
|
|
|
491
|
-
|
|
492
|
-
|
|
826
|
+
// Sort by relevance for balance tokens too
|
|
827
|
+
return matchingTokens.sort((a: Token, b: Token) => {
|
|
828
|
+
const aChainInfo = getChainInfo(a.chainId || 0)
|
|
829
|
+
const bChainInfo = getChainInfo(b.chainId || 0)
|
|
493
830
|
const aChainName = aChainInfo?.name?.toLowerCase() || ""
|
|
494
831
|
const bChainName = bChainInfo?.name?.toLowerCase() || ""
|
|
495
832
|
|
|
496
|
-
const aTokenSymbol =
|
|
497
|
-
|
|
498
|
-
: (a.contractInfo?.symbol || "").toLowerCase()
|
|
499
|
-
const bTokenSymbol = isBNative
|
|
500
|
-
? (bChainInfo?.nativeCurrency.symbol || "ETH").toLowerCase()
|
|
501
|
-
: (b.contractInfo?.symbol || "").toLowerCase()
|
|
833
|
+
const aTokenSymbol = (a.symbol || "???").toLowerCase()
|
|
834
|
+
const bTokenSymbol = (b.symbol || "???").toLowerCase()
|
|
502
835
|
|
|
503
|
-
const aTokenName =
|
|
504
|
-
|
|
505
|
-
: (a.contractInfo?.name || "").toLowerCase()
|
|
506
|
-
const bTokenName = isBNative
|
|
507
|
-
? (bChainInfo?.nativeCurrency.name || "Native Token").toLowerCase()
|
|
508
|
-
: (b.contractInfo?.name || "").toLowerCase()
|
|
836
|
+
const aTokenName = (a.name || "Unknown Token").toLowerCase()
|
|
837
|
+
const bTokenName = (b.name || "Unknown Token").toLowerCase()
|
|
509
838
|
|
|
510
839
|
// Calculate relevance scores using helper function
|
|
511
840
|
const aScore = calculateSearchRelevanceScore(
|
|
@@ -523,118 +852,63 @@ export function useTokenList({
|
|
|
523
852
|
|
|
524
853
|
return bScore - aScore // Higher score first
|
|
525
854
|
})
|
|
526
|
-
}, [
|
|
855
|
+
}, [
|
|
856
|
+
sortedTokens,
|
|
857
|
+
trimmedSearchQuery,
|
|
858
|
+
apiSearchTokens,
|
|
859
|
+
shouldUseApiSearch,
|
|
860
|
+
isSearchQueryAddress,
|
|
861
|
+
hasNoSearchResults,
|
|
862
|
+
customTokenAsTokenBalance,
|
|
863
|
+
])
|
|
527
864
|
|
|
528
865
|
const filteredTokensFormatted = useMemo(() => {
|
|
529
|
-
// Get base formatted tokens
|
|
866
|
+
// Get base formatted tokens - filteredTokens is already Token[]
|
|
530
867
|
const baseFormattedTokens = filteredTokens
|
|
531
|
-
.map((token:
|
|
532
|
-
const isNative =
|
|
533
|
-
|
|
534
|
-
const
|
|
535
|
-
const nativeSymbol = chainInfo?.nativeCurrency.symbol || "ETH"
|
|
536
|
-
const tokenSymbol = isNative
|
|
537
|
-
? nativeSymbol
|
|
538
|
-
: token.contractInfo?.symbol || "???"
|
|
539
|
-
const contractAddress = isNative ? zeroAddress : token.contractAddress
|
|
540
|
-
let imageContractAddress = contractAddress
|
|
541
|
-
if (tokenSymbol === "WETH") {
|
|
542
|
-
imageContractAddress = zeroAddress
|
|
543
|
-
}
|
|
544
|
-
const fallbackImageUrl =
|
|
545
|
-
(token as TokenBalanceWithPrice).contractInfo?.logoURI || ""
|
|
546
|
-
const imageUrl = getTokenImageUrl({
|
|
547
|
-
chainId: token.chainId,
|
|
548
|
-
contractAddress: imageContractAddress,
|
|
549
|
-
symbol: tokenSymbol,
|
|
550
|
-
fallbackImageUrl: fallbackImageUrl || undefined,
|
|
551
|
-
})
|
|
552
|
-
const currentTokenName =
|
|
553
|
-
(token as TokenBalanceWithPrice).contractInfo?.name || ""
|
|
554
|
-
const tokenName = getFormatttedTokenName(
|
|
555
|
-
currentTokenName,
|
|
556
|
-
tokenSymbol,
|
|
557
|
-
token.chainId,
|
|
558
|
-
)
|
|
559
|
-
const decimals = isNative ? 18 : token.contractInfo?.decimals
|
|
868
|
+
.map((token: Token): Token | null => {
|
|
869
|
+
const isNative = token.isNativeToken ?? false
|
|
870
|
+
const chainInfo = getChainInfo(token.chainId || 0)
|
|
871
|
+
const decimals = token.decimals
|
|
560
872
|
if (!decimals) {
|
|
561
873
|
logger.console.warn(
|
|
562
874
|
"[trails-sdk] Missing decimals for token, skipping:",
|
|
563
875
|
{
|
|
564
|
-
token:
|
|
876
|
+
token: token,
|
|
565
877
|
chainId: token.chainId,
|
|
566
878
|
isNative,
|
|
567
879
|
},
|
|
568
880
|
)
|
|
569
881
|
return null // Return null to filter out tokens without decimals
|
|
570
882
|
}
|
|
571
|
-
const formattedBalance = formatRawAmount(token.balance, decimals)
|
|
572
|
-
const priceUsd = Number(token.price?.value) ?? 0
|
|
573
|
-
const balanceUsdFormatted = token.balanceUsdFormatted ?? ""
|
|
574
883
|
|
|
575
|
-
let isSufficientBalance = true
|
|
884
|
+
let isSufficientBalance = token.isSufficientBalance ?? true
|
|
576
885
|
if (targetAmountUsd) {
|
|
577
886
|
isSufficientBalance = (token.balanceUsd ?? 0) >= targetAmountUsd
|
|
578
887
|
}
|
|
579
|
-
const chainName = chainInfo?.name || ""
|
|
888
|
+
const chainName = token.chainName || chainInfo?.name || ""
|
|
580
889
|
|
|
890
|
+
// Format price fields if not already formatted
|
|
891
|
+
const priceFields = token.priceUsdFormatted
|
|
892
|
+
? undefined
|
|
893
|
+
: token.priceUsd
|
|
894
|
+
? formatPriceFields(token.priceUsd)
|
|
895
|
+
: undefined
|
|
896
|
+
|
|
897
|
+
// Return Token with all formatting fields set
|
|
581
898
|
return {
|
|
582
899
|
...token,
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
(token as TokenBalanceWithPrice).contractInfo?.logoURI || "",
|
|
591
|
-
deployed:
|
|
592
|
-
(token as TokenBalanceWithPrice).contractInfo?.deployed || false,
|
|
593
|
-
bytecodeHash:
|
|
594
|
-
(token as TokenBalanceWithPrice).contractInfo?.bytecodeHash || "",
|
|
595
|
-
updatedAt:
|
|
596
|
-
(token as TokenBalanceWithPrice).contractInfo?.updatedAt || "",
|
|
597
|
-
queuedAt:
|
|
598
|
-
(token as TokenBalanceWithPrice).contractInfo?.queuedAt || "",
|
|
599
|
-
extensions: (token as TokenBalanceWithPrice).contractInfo
|
|
600
|
-
?.extensions || {
|
|
601
|
-
link: "",
|
|
602
|
-
description: "",
|
|
603
|
-
categories: [],
|
|
604
|
-
ogImage: "",
|
|
605
|
-
ogName: "",
|
|
606
|
-
originChainId: 0,
|
|
607
|
-
originAddress: "",
|
|
608
|
-
blacklist: false,
|
|
609
|
-
verified: false,
|
|
610
|
-
featureIndex: 0,
|
|
611
|
-
verifiedBy: "",
|
|
612
|
-
featured: false,
|
|
613
|
-
},
|
|
614
|
-
status:
|
|
615
|
-
(token as TokenBalanceWithPrice).contractInfo?.status ||
|
|
616
|
-
ResourceStatus.NOT_AVAILABLE,
|
|
617
|
-
address: contractAddress,
|
|
618
|
-
name: tokenName,
|
|
619
|
-
symbol: tokenSymbol,
|
|
620
|
-
decimals: decimals,
|
|
621
|
-
},
|
|
622
|
-
name: tokenName,
|
|
623
|
-
symbol: tokenSymbol,
|
|
624
|
-
balanceFormatted: formattedBalance,
|
|
625
|
-
imageUrl,
|
|
626
|
-
chainId: token.chainId,
|
|
627
|
-
contractAddress: contractAddress,
|
|
628
|
-
balanceUsdFormatted,
|
|
629
|
-
tokenPriceUsd: priceUsd,
|
|
630
|
-
isNative: isNative,
|
|
631
|
-
tokenName: tokenName,
|
|
632
|
-
priceUsd: priceUsd,
|
|
900
|
+
balanceDisplay: token.balanceDisplay || token.balanceFormatted,
|
|
901
|
+
priceUsdFormatted:
|
|
902
|
+
token.priceUsdFormatted || priceFields?.priceUsdFormatted,
|
|
903
|
+
priceUsdDisplay:
|
|
904
|
+
token.priceUsdDisplay || priceFields?.priceUsdDisplay,
|
|
905
|
+
balanceUsdDisplay:
|
|
906
|
+
token.balanceUsdDisplay || token.balanceUsdFormatted,
|
|
633
907
|
isSufficientBalance,
|
|
634
908
|
chainName,
|
|
635
909
|
}
|
|
636
910
|
})
|
|
637
|
-
.filter(Boolean) as
|
|
911
|
+
.filter(Boolean) as Token[] // Filter out null values for tokens without decimals
|
|
638
912
|
|
|
639
913
|
// If allSupportedTokens is true, combine with supported tokens
|
|
640
914
|
if (allSupportedTokens) {
|
|
@@ -646,19 +920,15 @@ export function useTokenList({
|
|
|
646
920
|
)
|
|
647
921
|
|
|
648
922
|
// Add supported tokens that don't exist in the base list
|
|
649
|
-
const additionalSupportedTokens = supportedTokens
|
|
923
|
+
const additionalSupportedTokens: Token[] = supportedTokens
|
|
650
924
|
.filter((supportedToken) => {
|
|
651
|
-
const key = `${supportedToken.chainId}-${supportedToken.contractAddress.toLowerCase()}`
|
|
925
|
+
const key = `${supportedToken.chainId || 0}-${supportedToken.contractAddress.toLowerCase()}`
|
|
652
926
|
return !existingTokenKeys.has(key)
|
|
653
927
|
})
|
|
654
|
-
.map((supportedToken) => {
|
|
655
|
-
const chainInfo = getChainInfo(supportedToken.chainId)
|
|
656
|
-
const chainName =
|
|
657
|
-
|
|
658
|
-
supportedToken.name,
|
|
659
|
-
supportedToken.symbol,
|
|
660
|
-
supportedToken.chainId,
|
|
661
|
-
)
|
|
928
|
+
.map((supportedToken): Token => {
|
|
929
|
+
const chainInfo = getChainInfo(supportedToken.chainId || 0)
|
|
930
|
+
const chainName =
|
|
931
|
+
chainInfo?.name || `Chain ${supportedToken.chainId || 0}`
|
|
662
932
|
|
|
663
933
|
// Find price data for this supported token
|
|
664
934
|
const priceData = supportedTokenPrices?.find(
|
|
@@ -668,30 +938,30 @@ export function useTokenList({
|
|
|
668
938
|
p.token.chainId === supportedToken.chainId,
|
|
669
939
|
)
|
|
670
940
|
|
|
941
|
+
// Format price fields if price exists
|
|
942
|
+
const priceFields = priceData?.priceUsd
|
|
943
|
+
? formatPriceFields(priceData.priceUsd)
|
|
944
|
+
: undefined
|
|
945
|
+
|
|
671
946
|
return {
|
|
672
|
-
|
|
673
|
-
|
|
947
|
+
symbol: supportedToken.symbol,
|
|
948
|
+
name: supportedToken.name,
|
|
949
|
+
decimals: supportedToken.decimals,
|
|
950
|
+
contractAddress: supportedToken.contractAddress,
|
|
951
|
+
chainId: supportedToken.chainId,
|
|
674
952
|
chainName: chainName,
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
balance: "",
|
|
682
|
-
balanceFormatted: "",
|
|
683
|
-
balanceUsdFormatted: "",
|
|
684
|
-
priceUsd: priceData?.priceUsd || 0,
|
|
685
|
-
price24hVol: 0, // Volume data not available in new API
|
|
953
|
+
imageUrl: supportedToken.imageUrl || "",
|
|
954
|
+
...(priceFields && {
|
|
955
|
+
priceUsd: priceFields.priceUsd,
|
|
956
|
+
priceUsdFormatted: priceFields.priceUsdFormatted,
|
|
957
|
+
priceUsdDisplay: priceFields.priceUsdDisplay,
|
|
958
|
+
}),
|
|
686
959
|
isSufficientBalance: true,
|
|
687
|
-
|
|
688
|
-
} as any
|
|
960
|
+
}
|
|
689
961
|
})
|
|
690
|
-
.sort((a
|
|
691
|
-
// Sort supported tokens by
|
|
692
|
-
|
|
693
|
-
const bVolume = Number(b?.price24hVol || 0)
|
|
694
|
-
return bVolume - aVolume
|
|
962
|
+
.sort((a, b) => {
|
|
963
|
+
// Sort supported tokens by price (highest first) since volume is not available
|
|
964
|
+
return (b.priceUsd || 0) - (a.priceUsd || 0)
|
|
695
965
|
})
|
|
696
966
|
|
|
697
967
|
// Combine lists with base tokens taking precedence
|
|
@@ -705,37 +975,35 @@ export function useTokenList({
|
|
|
705
975
|
const query = searchQuery.toLowerCase().trim()
|
|
706
976
|
const queryParts = query.split(/\s+/).filter((part) => part.length > 0)
|
|
707
977
|
|
|
708
|
-
const filteredTokens = combinedTokens.filter(
|
|
709
|
-
(
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
const matchesToken = queryParts.some(
|
|
721
|
-
(part) =>
|
|
722
|
-
tokenSymbol.includes(part) ||
|
|
723
|
-
tokenName.includes(part) ||
|
|
724
|
-
contractAddress.includes(part),
|
|
725
|
-
)
|
|
726
|
-
return matchesChain && matchesToken
|
|
727
|
-
}
|
|
728
|
-
|
|
729
|
-
// Single query part - match against any field
|
|
730
|
-
return queryParts.some(
|
|
978
|
+
const filteredTokens = combinedTokens.filter((token: Token) => {
|
|
979
|
+
const chainName = token.chainName?.toLowerCase() || ""
|
|
980
|
+
const tokenSymbol = token.symbol?.toLowerCase() || ""
|
|
981
|
+
const tokenName = token.name?.toLowerCase() || ""
|
|
982
|
+
const contractAddress = token.contractAddress?.toLowerCase() || ""
|
|
983
|
+
|
|
984
|
+
// If multiple query parts, check if they match chain + token combination
|
|
985
|
+
if (queryParts.length > 1) {
|
|
986
|
+
const matchesChain = queryParts.some((part) =>
|
|
987
|
+
chainName.includes(part),
|
|
988
|
+
)
|
|
989
|
+
const matchesToken = queryParts.some(
|
|
731
990
|
(part) =>
|
|
732
991
|
tokenSymbol.includes(part) ||
|
|
733
992
|
tokenName.includes(part) ||
|
|
734
|
-
chainName.includes(part) ||
|
|
735
993
|
contractAddress.includes(part),
|
|
736
994
|
)
|
|
737
|
-
|
|
738
|
-
|
|
995
|
+
return matchesChain && matchesToken
|
|
996
|
+
}
|
|
997
|
+
|
|
998
|
+
// Single query part - match against any field
|
|
999
|
+
return queryParts.some(
|
|
1000
|
+
(part) =>
|
|
1001
|
+
tokenSymbol.includes(part) ||
|
|
1002
|
+
tokenName.includes(part) ||
|
|
1003
|
+
chainName.includes(part) ||
|
|
1004
|
+
contractAddress.includes(part),
|
|
1005
|
+
)
|
|
1006
|
+
})
|
|
739
1007
|
|
|
740
1008
|
// Sort filtered tokens by relevance
|
|
741
1009
|
return filteredTokens.sort((a, b) => {
|
|
@@ -783,7 +1051,6 @@ export function useTokenList({
|
|
|
783
1051
|
supportedTokens,
|
|
784
1052
|
supportedTokenPrices,
|
|
785
1053
|
searchQuery,
|
|
786
|
-
getTokenImageUrl,
|
|
787
1054
|
])
|
|
788
1055
|
|
|
789
1056
|
const showInsufficientBalance = useMemo(() => {
|
|
@@ -805,12 +1072,17 @@ export function useTokenList({
|
|
|
805
1072
|
totalBalanceUsd,
|
|
806
1073
|
])
|
|
807
1074
|
|
|
1075
|
+
// isSearching: true when user is typing (debounce pending) or API is fetching
|
|
1076
|
+
const isSearching = isSearchPending || isLoadingSearchTokens
|
|
1077
|
+
|
|
808
1078
|
return {
|
|
809
1079
|
selectedToken,
|
|
810
1080
|
searchQuery,
|
|
811
1081
|
handleTokenSelect,
|
|
812
1082
|
filteredTokens,
|
|
813
|
-
isLoadingSortedTokens,
|
|
1083
|
+
isLoadingSortedTokens: isLoadingSortedTokens || isLoadingSearchTokens,
|
|
1084
|
+
isLoadingSearchTokens,
|
|
1085
|
+
isSearching,
|
|
814
1086
|
balanceError,
|
|
815
1087
|
showContinueButton,
|
|
816
1088
|
isTokenSelected,
|