0xtrails 0.1.2 → 0.1.3

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.
Files changed (103) hide show
  1. package/dist/analytics.d.ts +68 -1
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/{ccip-BmFTEOaB.js → ccip-CWd4g9uZ.js} +1 -1
  4. package/dist/chains.d.ts +9 -3
  5. package/dist/chains.d.ts.map +1 -1
  6. package/dist/ens.d.ts +7 -0
  7. package/dist/ens.d.ts.map +1 -0
  8. package/dist/error.d.ts +2 -0
  9. package/dist/error.d.ts.map +1 -1
  10. package/dist/{index-BPsVj7zK.js → index-BTUBzx4R.js} +23624 -21770
  11. package/dist/index.js +2 -2
  12. package/dist/lifi.d.ts +4 -0
  13. package/dist/lifi.d.ts.map +1 -0
  14. package/dist/mode.d.ts +1 -1
  15. package/dist/mode.d.ts.map +1 -1
  16. package/dist/prepareSend.d.ts +3 -1
  17. package/dist/prepareSend.d.ts.map +1 -1
  18. package/dist/prices.d.ts +2 -0
  19. package/dist/prices.d.ts.map +1 -1
  20. package/dist/relaySdk.d.ts.map +1 -1
  21. package/dist/relayer.d.ts.map +1 -1
  22. package/dist/tokenBalances.d.ts.map +1 -1
  23. package/dist/tokens.d.ts +2 -1
  24. package/dist/tokens.d.ts.map +1 -1
  25. package/dist/trails.d.ts +3 -3
  26. package/dist/trails.d.ts.map +1 -1
  27. package/dist/transactions.d.ts.map +1 -1
  28. package/dist/wallets.d.ts +247 -5
  29. package/dist/wallets.d.ts.map +1 -1
  30. package/dist/widget/components/ChainFilterDropdown.d.ts +2 -0
  31. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  32. package/dist/widget/components/ConnectWallet.d.ts +1 -0
  33. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  34. package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -1
  35. package/dist/widget/components/FundSendForm.d.ts +2 -2
  36. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  37. package/dist/widget/components/PaySendForm.d.ts +2 -2
  38. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  39. package/dist/widget/components/QrCode.d.ts +1 -1
  40. package/dist/widget/components/QrCode.d.ts.map +1 -1
  41. package/dist/widget/components/RefundAddressInput.d.ts +13 -0
  42. package/dist/widget/components/RefundAddressInput.d.ts.map +1 -0
  43. package/dist/widget/components/Swap.d.ts +43 -0
  44. package/dist/widget/components/Swap.d.ts.map +1 -0
  45. package/dist/widget/components/TokenList.d.ts +0 -2
  46. package/dist/widget/components/TokenList.d.ts.map +1 -1
  47. package/dist/widget/components/TokenSelector.d.ts +26 -0
  48. package/dist/widget/components/TokenSelector.d.ts.map +1 -0
  49. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  50. package/dist/widget/components/WalletConnectionPending.d.ts +12 -0
  51. package/dist/widget/components/WalletConnectionPending.d.ts.map +1 -0
  52. package/dist/widget/components/WalletList.d.ts.map +1 -1
  53. package/dist/widget/hooks/useAmountUsd.d.ts +1 -3
  54. package/dist/widget/hooks/useAmountUsd.d.ts.map +1 -1
  55. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  56. package/dist/widget/hooks/useSendForm.d.ts +6 -4
  57. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  58. package/dist/widget/hooks/useTokenList.d.ts +2 -3
  59. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  60. package/dist/widget/index.js +1 -1
  61. package/dist/widget/widget.d.ts.map +1 -1
  62. package/package.json +9 -6
  63. package/src/aave.ts +13 -13
  64. package/src/analytics.ts +87 -4
  65. package/src/chains.ts +45 -7
  66. package/src/constants.ts +4 -4
  67. package/src/ens.ts +17 -0
  68. package/src/error.ts +16 -1
  69. package/src/lifi.ts +58 -0
  70. package/src/mode.ts +1 -1
  71. package/src/morpho.ts +3 -3
  72. package/src/pools.ts +18 -18
  73. package/src/prepareSend.ts +35 -3
  74. package/src/prices.ts +21 -0
  75. package/src/relaySdk.ts +1 -0
  76. package/src/relayer.ts +8 -0
  77. package/src/tokenBalances.ts +3 -0
  78. package/src/tokens.ts +85 -19
  79. package/src/trails.ts +2 -2
  80. package/src/transactions.ts +1 -0
  81. package/src/wallets.ts +275 -35
  82. package/src/widget/compiled.css +1 -1
  83. package/src/widget/components/ChainFilterDropdown.tsx +42 -33
  84. package/src/widget/components/ChainImage.tsx +1 -1
  85. package/src/widget/components/ConnectWallet.tsx +92 -128
  86. package/src/widget/components/DebugScreensDropdown.tsx +3 -0
  87. package/src/widget/components/FundSendForm.tsx +17 -3
  88. package/src/widget/components/PaySendForm.tsx +16 -2
  89. package/src/widget/components/QRCodeDeposit.tsx +1 -1
  90. package/src/widget/components/QrCode.tsx +277 -16
  91. package/src/widget/components/Receipt.tsx +1 -1
  92. package/src/widget/components/RefundAddressInput.tsx +149 -0
  93. package/src/widget/components/Swap.tsx +648 -0
  94. package/src/widget/components/TokenList.tsx +27 -363
  95. package/src/widget/components/TokenSelector.tsx +405 -0
  96. package/src/widget/components/WalletConnect.tsx +9 -7
  97. package/src/widget/components/WalletConnectionPending.tsx +157 -0
  98. package/src/widget/components/WalletList.tsx +6 -5
  99. package/src/widget/hooks/useAmountUsd.ts +3 -8
  100. package/src/widget/hooks/useCheckout.ts +3 -2
  101. package/src/widget/hooks/useSendForm.ts +66 -32
  102. package/src/widget/hooks/useTokenList.ts +158 -106
  103. package/src/widget/widget.tsx +335 -72
@@ -1,4 +1,5 @@
1
1
  import { useCallback, useRef } from "react"
2
+ import { getSessionId } from "../../analytics.js"
2
3
 
3
4
  export type CheckoutCallbacks = {
4
5
  onCheckoutStart?: (data: { sessionId: string }) => void
@@ -38,10 +39,10 @@ export function useCheckout({
38
39
  onCheckoutError,
39
40
  onCheckoutStatusUpdate,
40
41
  }: UseCheckoutProps): UseCheckoutReturn {
41
- const sessionIdRef = useRef<string>(Date.now().toString())
42
+ const sessionIdRef = useRef<string>(getSessionId())
42
43
 
43
44
  const startCheckout = useCallback(() => {
44
- sessionIdRef.current = Date.now().toString()
45
+ sessionIdRef.current = getSessionId()
45
46
  }, [])
46
47
 
47
48
  const triggerCheckoutStart = useCallback(() => {
@@ -9,8 +9,6 @@ import {
9
9
  type WalletClient,
10
10
  zeroAddress,
11
11
  } from "viem"
12
- import { mainnet } from "viem/chains"
13
- import { useEnsAddress } from "wagmi"
14
12
  import { useAPIClient } from "../../apiClient.js"
15
13
  import { getChainInfo, useSupportedChains } from "../../chains.js"
16
14
  import { getFullErrorMessage } from "../../error.js"
@@ -37,6 +35,8 @@ import {
37
35
  useTokenInfo,
38
36
  } from "../../tokens.js"
39
37
  import type { CheckoutOnHandlers } from "./useCheckout.js"
38
+ import { useResolveEnsAddress } from "../../ens.js"
39
+ import * as chains from "viem/chains"
40
40
 
41
41
  export interface Token {
42
42
  id: number
@@ -100,6 +100,7 @@ export type UseSendProps = {
100
100
  toChainId?: number
101
101
  toToken?: string
102
102
  toCalldata?: string
103
+ refundAddress?: string
103
104
  walletClient: WalletClient
104
105
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
105
106
  onError: (error: Error | string | null) => void
@@ -109,12 +110,12 @@ export type UseSendProps = {
109
110
  onSend: (amount: string, recipient: string) => void
110
111
  onConfirm: () => void
111
112
  onComplete: (result: OnCompleteProps) => void
112
- selectedToken: Token
113
+ selectedToken?: Token
113
114
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
114
115
  tradeType?: TradeType
115
116
  quoteProvider?: string
116
- fundMethod?: string | null
117
- mode?: "pay" | "fund" | "earn"
117
+ fundMethod?: string
118
+ mode?: "pay" | "fund" | "earn" | "swap"
118
119
  onNavigateToMeshConnect?: (
119
120
  props: {
120
121
  toTokenSymbol: string
@@ -161,7 +162,7 @@ export type UseSendReturn = {
161
162
  isValidRecipient: boolean
162
163
  destTokenPrices: TokenPrice[] | null
163
164
  sourceTokenPrices: TokenPrice[] | null
164
- selectedToken: Token
165
+ selectedToken?: Token
165
166
  selectedFeeToken: TokenInfo | null
166
167
  setIsChainDropdownOpen: (isOpen: boolean) => void
167
168
  setIsTokenDropdownOpen: (isOpen: boolean) => void
@@ -179,6 +180,7 @@ export function useSendForm({
179
180
  toChainId, // Custom specified destination chain id
180
181
  toToken, // Custom specified destination token address or symbol
181
182
  toCalldata, // Custom specified destination calldata
183
+ refundAddress, // Custom specified refund address
182
184
  walletClient,
183
185
  onTransactionStateChange,
184
186
  onError,
@@ -197,6 +199,19 @@ export function useSendForm({
197
199
  onNavigateToMeshConnect,
198
200
  checkoutOnHandlers,
199
201
  }: UseSendProps): UseSendReturn {
202
+ // Auto-set quoteProvider to "lifi" if either from or to chain is etherlink
203
+ const effectiveQuoteProvider = useMemo(() => {
204
+ if (!quoteProvider || quoteProvider === "auto") {
205
+ if (
206
+ selectedToken?.chainId === chains.etherlink.id ||
207
+ toChainId === chains.etherlink.id
208
+ ) {
209
+ return "lifi"
210
+ }
211
+ }
212
+ return quoteProvider
213
+ }, [quoteProvider, selectedToken?.chainId, toChainId])
214
+
200
215
  const [amount, setAmount] = useState(
201
216
  tradeType === TradeType.EXACT_INPUT ? "" : (toAmount ?? ""),
202
217
  )
@@ -204,16 +219,10 @@ export function useSendForm({
204
219
  const [recipient, setRecipient] = useState(toRecipient ?? "")
205
220
  const [error, setError] = useState<string | null>(null)
206
221
  const { supportedChains } = useSupportedChains()
207
- const { data: ensAddress } = useEnsAddress({
208
- name: recipientInput?.endsWith(".eth") ? recipientInput : undefined,
209
- chainId: mainnet.id,
210
- query: {
211
- enabled: !!recipientInput && recipientInput.endsWith(".eth"),
212
- },
222
+ const { ensAddress } = useResolveEnsAddress({
223
+ textInput: recipientInput,
213
224
  })
214
225
 
215
- console.log("GENERATED CALLLDATA", toCalldata)
216
-
217
226
  useEffect(() => {
218
227
  if (ensAddress) {
219
228
  setRecipient(ensAddress)
@@ -287,13 +296,17 @@ export function useSendForm({
287
296
  }, [errorCustomToken, isCustomToken, isLoadingCustomToken])
288
297
 
289
298
  const defaultDestToken = useMemo(() => {
299
+ if (mode === "swap") {
300
+ return null
301
+ }
302
+
290
303
  if (selectedDestinationChain) {
291
304
  return supportedTokens.find(
292
305
  (token) => token.chainId === selectedDestinationChain.id,
293
306
  )
294
307
  }
295
308
  return supportedTokens?.[0] as TokenInfo
296
- }, [supportedTokens, selectedDestinationChain])
309
+ }, [supportedTokens, selectedDestinationChain, mode])
297
310
 
298
311
  const [isChainDropdownOpen, setIsChainDropdownOpen] = useState(false)
299
312
  const [isTokenDropdownOpen, setIsTokenDropdownOpen] = useState(false)
@@ -366,7 +379,7 @@ export function useSendForm({
366
379
 
367
380
  // Update selectedDestToken when toToken prop changes
368
381
  useEffect(() => {
369
- if (toToken && !isCustomToken) {
382
+ if (toToken && !isCustomToken && selectedDestinationChain?.id) {
370
383
  const isToTokenAddress = isAddress(toToken)
371
384
  const newToken = supportedTokens.find(
372
385
  (token) =>
@@ -385,7 +398,7 @@ export function useSendForm({
385
398
  toToken,
386
399
  supportedTokens,
387
400
  toChainId,
388
- selectedDestinationChain.id,
401
+ selectedDestinationChain?.id,
389
402
  isCustomToken,
390
403
  ])
391
404
 
@@ -406,7 +419,7 @@ export function useSendForm({
406
419
  setRecipient(toRecipient ?? "")
407
420
  }, [toRecipient])
408
421
 
409
- const chainInfo = getChainInfo(selectedToken.chainId)
422
+ const chainInfo = getChainInfo(selectedToken?.chainId)
410
423
  const [isSubmitting, setIsSubmitting] = useState(false)
411
424
  const [isWaitingForWalletConfirm, setIsWaitingForWalletConfirm] =
412
425
  useState(false)
@@ -422,11 +435,13 @@ export function useSendForm({
422
435
  [onTransactionStateChange],
423
436
  )
424
437
 
425
- const balanceFormatted = formatRawAmount(
426
- selectedToken.balance,
427
- selectedToken.contractInfo?.decimals,
428
- )
429
- const balanceUsdDisplay = selectedToken.balanceUsdFormatted ?? ""
438
+ const balanceFormatted = selectedToken
439
+ ? formatRawAmount(
440
+ selectedToken.balance,
441
+ selectedToken.contractInfo?.decimals,
442
+ )
443
+ : "0"
444
+ const balanceUsdDisplay = selectedToken?.balanceUsdFormatted ?? ""
430
445
  const isValidRecipient = Boolean(recipient && isAddress(recipient))
431
446
 
432
447
  // Calculate USD value based on trade type
@@ -460,7 +475,7 @@ export function useSendForm({
460
475
 
461
476
  // Calculate raw amount (in wei/smallest unit)
462
477
  const amountRaw = useMemo(() => {
463
- if (!amount) {
478
+ if (!amount || !selectedToken?.contractInfo?.decimals) {
464
479
  return "0"
465
480
  }
466
481
 
@@ -472,6 +487,12 @@ export function useSendForm({
472
487
  : selectedDestToken?.decimals
473
488
 
474
489
  if (!decimals) {
490
+ console.warn("[trails-sdk] Missing token decimals for quote", {
491
+ decimals,
492
+ selectedToken,
493
+ selectedDestToken,
494
+ tradeType,
495
+ })
475
496
  return "0"
476
497
  }
477
498
 
@@ -482,8 +503,10 @@ export function useSendForm({
482
503
  }
483
504
  }, [
484
505
  amount,
506
+ selectedDestToken,
507
+ selectedToken,
485
508
  selectedDestToken?.decimals,
486
- selectedToken.contractInfo?.decimals,
509
+ selectedToken?.contractInfo?.decimals,
487
510
  tradeType,
488
511
  ])
489
512
 
@@ -498,7 +521,8 @@ export function useSendForm({
498
521
  !selectedDestinationChain ||
499
522
  amount === "0" ||
500
523
  !amountRaw ||
501
- amountRaw === "0"
524
+ amountRaw === "0" ||
525
+ !selectedToken
502
526
  ) {
503
527
  setPrepareSendResult(null)
504
528
  return
@@ -609,6 +633,7 @@ export function useSendForm({
609
633
  originRelayer,
610
634
  destinationRelayer,
611
635
  destinationCalldata: toCalldata,
636
+ refundAddress,
612
637
  dryMode: isDryMode,
613
638
  onTransactionStateChange: handleTransactionStateChange,
614
639
  sourceTokenPriceUsd,
@@ -621,7 +646,8 @@ export function useSendForm({
621
646
  )?.url ?? undefined,
622
647
  gasless,
623
648
  originNativeTokenPriceUsd: nativeTokenPriceUsd,
624
- quoteProvider,
649
+ quoteProvider: effectiveQuoteProvider,
650
+ mode,
625
651
  fundMethod,
626
652
  checkoutOnHandlers,
627
653
  }
@@ -653,6 +679,7 @@ export function useSendForm({
653
679
  selectedToken?.balance,
654
680
  selectedToken?.tokenPriceUsd,
655
681
  toCalldata,
682
+ refundAddress,
656
683
  paymasterUrls,
657
684
  gasless,
658
685
  handleTransactionStateChange,
@@ -662,10 +689,11 @@ export function useSendForm({
662
689
  selectedDestToken,
663
690
  selectedDestinationChain,
664
691
  selectedToken,
665
- quoteProvider,
692
+ effectiveQuoteProvider,
666
693
  fundMethod,
667
694
  amountRaw,
668
695
  checkoutOnHandlers,
696
+ mode,
669
697
  ])
670
698
 
671
699
  // Auto-fetch quotes when inputs change (debounced)
@@ -865,10 +893,12 @@ export function useSendForm({
865
893
 
866
894
  // Get button text based on recipient and calldata
867
895
  const buttonText = useMemo(() => {
896
+ if (!selectedToken) return "Select a token"
897
+ if (!amount) return "Enter an amount"
898
+ if (!selectedDestToken?.symbol) return "Select a token"
868
899
  if (isWaitingForWalletConfirm) return "Waiting for wallet..."
869
900
  if (isSubmitting) return "Processing..."
870
- if (!amount) return "Enter amount"
871
- if (!isValidRecipient) return "Enter recipient"
901
+ if (!isValidRecipient) return "Enter a recipient"
872
902
  if (isLoadingQuote) return "Getting quote..."
873
903
  if (!prepareSendResult) return "No quote available"
874
904
 
@@ -886,11 +916,15 @@ export function useSendForm({
886
916
  )
887
917
 
888
918
  try {
889
- const isSameChain = selectedToken.chainId === selectedDestinationChain.id
919
+ const isSameChain = selectedToken.chainId === selectedDestinationChain?.id
890
920
  const isSameToken = selectedToken.symbol === selectedDestToken.symbol
891
921
  const checksummedRecipient = getAddress(recipient)
892
922
  const checksummedAccount = getAddress(account.address)
893
923
 
924
+ if (mode === "swap") {
925
+ return `Swap ${amountDisplay} ${tokenSymbol}`
926
+ }
927
+
894
928
  if (fundMethod === "exchange") {
895
929
  return `Continue to Exchange`
896
930
  }
@@ -936,7 +970,7 @@ export function useSendForm({
936
970
  selectedToken,
937
971
  tradeType,
938
972
  prepareSendResult?.quote?.originAmountFormatted,
939
- selectedDestinationChain.id,
973
+ selectedDestinationChain?.id,
940
974
  mode,
941
975
  fundMethod,
942
976
  ])
@@ -1,4 +1,3 @@
1
- import type { SequenceIndexerGateway } from "@0xsequence/indexer"
2
1
  import { ResourceStatus } from "@0xsequence/indexer"
3
2
  import { Address } from "ox"
4
3
  import { useEffect, useMemo, useState } from "react"
@@ -17,6 +16,7 @@ import {
17
16
  useTokenBalances,
18
17
  } from "../../tokenBalances.js"
19
18
  import { getFormatttedTokenName, getSupportedTokens } from "../../tokens.js"
19
+ import { useIndexerGatewayClient } from "../../indexerClient.js"
20
20
 
21
21
  export interface Token {
22
22
  id: number
@@ -50,9 +50,9 @@ export type TokenFormatted = Token &
50
50
  export type UseTokenListProps = {
51
51
  onContinue: (selectedToken: Token) => void
52
52
  targetAmountUsd?: number | null
53
- indexerGatewayClient: SequenceIndexerGateway
54
53
  onError: (error: Error | string | null) => void
55
54
  fundMethod?: string | null
55
+ allSupportedTokens?: boolean
56
56
  }
57
57
 
58
58
  export type UseTokenListReturn = {
@@ -76,27 +76,30 @@ export type UseTokenListReturn = {
76
76
  export function useTokenList({
77
77
  onContinue,
78
78
  targetAmountUsd,
79
- indexerGatewayClient,
80
79
  onError,
81
80
  fundMethod,
81
+ allSupportedTokens = false,
82
82
  }: UseTokenListProps): UseTokenListReturn {
83
83
  const [selectedToken, setSelectedToken] = useState<Token | null>(null)
84
84
  const [searchQuery, setSearchQuery] = useState("")
85
- const [allSupportedTokens, setAllSupportedTokens] = useState<any[]>([])
85
+ const [supportedTokens, setSupportedTokens] = useState<any[]>([])
86
86
  const [isLoadingSupportedTokens, setIsLoadingSupportedTokens] =
87
87
  useState(false)
88
88
  const { address } = useAccount()
89
+ const indexerGatewayClient = useIndexerGatewayClient()
89
90
  const {
90
91
  sortedTokens: allSortedTokens,
91
92
  isLoadingSortedTokens,
92
93
  balanceError,
93
94
  } = useTokenBalances(address as Address.Address, indexerGatewayClient)
94
95
 
95
- // Determine loading state based on fund method
96
+ // Determine loading state based on fund method and allSupportedTokens
96
97
  const isLoadingTokens =
97
98
  fundMethod === "qr-code" || fundMethod === "exchange"
98
99
  ? isLoadingSupportedTokens
99
- : isLoadingSortedTokens
100
+ : allSupportedTokens
101
+ ? isLoadingSortedTokens || isLoadingSupportedTokens
102
+ : isLoadingSortedTokens
100
103
 
101
104
  const {
102
105
  totalBalanceUsd,
@@ -111,13 +114,18 @@ export function useTokenList({
111
114
  const showContinueButton = false
112
115
  const { supportedChains: supportedToChains } = useSupportedChains()
113
116
 
114
- // Fetch all supported tokens when fundMethod is "qr-code" or "exchange"
117
+ // Fetch all supported tokens when fundMethod is "qr-code" or "exchange" or when allSupportedTokens is true
115
118
  useEffect(() => {
116
- if (fundMethod === "qr-code" || fundMethod === "exchange") {
119
+ if (
120
+ fundMethod === "qr-code" ||
121
+ fundMethod === "exchange" ||
122
+ allSupportedTokens
123
+ ) {
117
124
  setIsLoadingSupportedTokens(true)
118
125
  getSupportedTokens()
119
126
  .then((tokens) => {
120
- setAllSupportedTokens(tokens)
127
+ // Store supported tokens for use in filteredTokensFormatted
128
+ setSupportedTokens(tokens)
121
129
  })
122
130
  .catch((error) => {
123
131
  console.error("[trails-sdk] Failed to fetch supported tokens:", error)
@@ -126,17 +134,17 @@ export function useTokenList({
126
134
  setIsLoadingSupportedTokens(false)
127
135
  })
128
136
  }
129
- }, [fundMethod])
137
+ }, [fundMethod, allSupportedTokens])
130
138
 
131
139
  const supportedChainIds = useMemo(() => {
132
140
  return new Set(supportedToChains.map((c) => c.id))
133
141
  }, [supportedToChains])
134
142
 
135
143
  const sortedTokens = useMemo<Array<TokenBalanceExtended>>(() => {
136
- // If fundMethod is "qr-code" or "exchange", use all supported tokens instead of account-specific tokens
144
+ // If fundMethod is "qr-code" or "exchange", use filtered supported tokens
137
145
  if (fundMethod === "qr-code" || fundMethod === "exchange") {
138
146
  // Filter to only show specific tokens for QR code and exchange modes
139
- const filteredTokens = allSupportedTokens.filter((token) => {
147
+ const filteredTokens = supportedTokens.filter((token) => {
140
148
  const symbol = token.symbol.toUpperCase()
141
149
  return ["ETH", "POL", "USDC", "USDT", "DAI", "BAT", "WETH"].includes(
142
150
  symbol,
@@ -199,16 +207,10 @@ export function useTokenList({
199
207
  }
200
208
  return true
201
209
  })
202
- }, [
203
- allSortedTokens,
204
- supportedChainIds,
205
- fundMethod,
206
- allSupportedTokens,
207
- address,
208
- ])
210
+ }, [allSortedTokens, supportedChainIds, fundMethod, supportedTokens, address])
209
211
 
210
212
  useEffect(() => {
211
- if (onError) {
213
+ if (onError && balanceError) {
212
214
  onError(balanceError)
213
215
  }
214
216
  }, [balanceError, onError])
@@ -359,95 +361,145 @@ export function useTokenList({
359
361
  }, [sortedTokens, searchQuery])
360
362
 
361
363
  const filteredTokensFormatted = useMemo(() => {
362
- return filteredTokens.map((token: TokenBalanceExtended): TokenFormatted => {
363
- const isNative = !("contractAddress" in token)
364
- const chainInfo = getChainInfo(token.chainId)
365
- const nativeSymbol = chainInfo?.nativeCurrency.symbol || "ETH"
366
- const tokenSymbol = isNative
367
- ? nativeSymbol
368
- : token.contractInfo?.symbol || "???"
369
- const contractAddress = isNative ? zeroAddress : token.contractAddress
370
- let imageContractAddress = contractAddress
371
- if (tokenSymbol === "WETH") {
372
- imageContractAddress = zeroAddress
373
- }
374
- const imageUrl = `https://assets.sequence.info/images/tokens/small/${token.chainId}/${imageContractAddress}.webp`
375
- const currentTokenName =
376
- (token as TokenBalanceWithPrice).contractInfo?.name || ""
377
- const tokenName = getFormatttedTokenName(
378
- currentTokenName,
379
- tokenSymbol,
380
- token.chainId,
381
- )
382
- const formattedBalance = formatRawAmount(
383
- token.balance,
384
- isNative ? 18 : token.contractInfo?.decimals,
385
- )
386
- const priceUsd = Number(token.price?.value) ?? 0
387
- const balanceUsdFormatted = token.balanceUsdFormatted ?? ""
388
- const decimals = isNative ? 18 : (token.contractInfo?.decimals ?? 18)
389
- let isSufficientBalance = true
390
- if (targetAmountUsd) {
391
- isSufficientBalance = (token.balanceUsd ?? 0) >= targetAmountUsd
392
- }
393
- const chainName = chainInfo?.name || ""
394
-
395
- return {
396
- ...token,
397
- id: token.chainId,
398
- contractInfo: {
399
- ...(token as TokenBalanceWithPrice).contractInfo,
400
- chainId: Number(token.chainId),
401
- source: (token as TokenBalanceWithPrice).contractInfo?.source || "",
402
- type: (token as TokenBalanceWithPrice).contractInfo?.type || "",
403
- logoURI: (token as TokenBalanceWithPrice).contractInfo?.logoURI || "",
404
- deployed:
405
- (token as TokenBalanceWithPrice).contractInfo?.deployed || false,
406
- bytecodeHash:
407
- (token as TokenBalanceWithPrice).contractInfo?.bytecodeHash || "",
408
- updatedAt:
409
- (token as TokenBalanceWithPrice).contractInfo?.updatedAt || "",
410
- queuedAt:
411
- (token as TokenBalanceWithPrice).contractInfo?.queuedAt || "",
412
- extensions: (token as TokenBalanceWithPrice).contractInfo
413
- ?.extensions || {
414
- link: "",
415
- description: "",
416
- categories: [],
417
- ogImage: "",
418
- ogName: "",
419
- originChainId: 0,
420
- originAddress: "",
421
- blacklist: false,
422
- verified: false,
423
- featureIndex: 0,
424
- verifiedBy: "",
425
- featured: false,
364
+ // Get base formatted tokens
365
+ const baseFormattedTokens = filteredTokens.map(
366
+ (token: TokenBalanceExtended): TokenFormatted => {
367
+ const isNative = !("contractAddress" in token)
368
+ const chainInfo = getChainInfo(token.chainId)
369
+ const nativeSymbol = chainInfo?.nativeCurrency.symbol || "ETH"
370
+ const tokenSymbol = isNative
371
+ ? nativeSymbol
372
+ : token.contractInfo?.symbol || "???"
373
+ const contractAddress = isNative ? zeroAddress : token.contractAddress
374
+ let imageContractAddress = contractAddress
375
+ if (tokenSymbol === "WETH") {
376
+ imageContractAddress = zeroAddress
377
+ }
378
+ const imageUrl = `https://assets.sequence.info/images/tokens/small/${token.chainId}/${imageContractAddress}.webp`
379
+ const currentTokenName =
380
+ (token as TokenBalanceWithPrice).contractInfo?.name || ""
381
+ const tokenName = getFormatttedTokenName(
382
+ currentTokenName,
383
+ tokenSymbol,
384
+ token.chainId,
385
+ )
386
+ const formattedBalance = formatRawAmount(
387
+ token.balance,
388
+ isNative ? 18 : token.contractInfo?.decimals,
389
+ )
390
+ const priceUsd = Number(token.price?.value) ?? 0
391
+ const balanceUsdFormatted = token.balanceUsdFormatted ?? ""
392
+ const decimals = isNative ? 18 : (token.contractInfo?.decimals ?? 18)
393
+ let isSufficientBalance = true
394
+ if (targetAmountUsd) {
395
+ isSufficientBalance = (token.balanceUsd ?? 0) >= targetAmountUsd
396
+ }
397
+ const chainName = chainInfo?.name || ""
398
+
399
+ return {
400
+ ...token,
401
+ id: token.chainId,
402
+ contractInfo: {
403
+ ...(token as TokenBalanceWithPrice).contractInfo,
404
+ chainId: Number(token.chainId),
405
+ source: (token as TokenBalanceWithPrice).contractInfo?.source || "",
406
+ type: (token as TokenBalanceWithPrice).contractInfo?.type || "",
407
+ logoURI:
408
+ (token as TokenBalanceWithPrice).contractInfo?.logoURI || "",
409
+ deployed:
410
+ (token as TokenBalanceWithPrice).contractInfo?.deployed || false,
411
+ bytecodeHash:
412
+ (token as TokenBalanceWithPrice).contractInfo?.bytecodeHash || "",
413
+ updatedAt:
414
+ (token as TokenBalanceWithPrice).contractInfo?.updatedAt || "",
415
+ queuedAt:
416
+ (token as TokenBalanceWithPrice).contractInfo?.queuedAt || "",
417
+ extensions: (token as TokenBalanceWithPrice).contractInfo
418
+ ?.extensions || {
419
+ link: "",
420
+ description: "",
421
+ categories: [],
422
+ ogImage: "",
423
+ ogName: "",
424
+ originChainId: 0,
425
+ originAddress: "",
426
+ blacklist: false,
427
+ verified: false,
428
+ featureIndex: 0,
429
+ verifiedBy: "",
430
+ featured: false,
431
+ },
432
+ status:
433
+ (token as TokenBalanceWithPrice).contractInfo?.status ||
434
+ ResourceStatus.NOT_AVAILABLE,
435
+ address: contractAddress,
436
+ name: tokenName,
437
+ symbol: tokenSymbol,
438
+ decimals: decimals,
426
439
  },
427
- status:
428
- (token as TokenBalanceWithPrice).contractInfo?.status ||
429
- ResourceStatus.NOT_AVAILABLE,
430
- address: contractAddress,
431
440
  name: tokenName,
432
441
  symbol: tokenSymbol,
433
- decimals: decimals,
434
- },
435
- name: tokenName,
436
- symbol: tokenSymbol,
437
- balanceFormatted: formattedBalance,
438
- imageUrl,
439
- chainId: token.chainId,
440
- contractAddress: contractAddress,
441
- balanceUsdFormatted,
442
- tokenPriceUsd: priceUsd,
443
- isNative: isNative,
444
- tokenName: tokenName,
445
- priceUsd: priceUsd,
446
- isSufficientBalance,
447
- chainName,
448
- }
449
- })
450
- }, [filteredTokens, targetAmountUsd])
442
+ balanceFormatted: formattedBalance,
443
+ imageUrl,
444
+ chainId: token.chainId,
445
+ contractAddress: contractAddress,
446
+ balanceUsdFormatted,
447
+ tokenPriceUsd: priceUsd,
448
+ isNative: isNative,
449
+ tokenName: tokenName,
450
+ priceUsd: priceUsd,
451
+ isSufficientBalance,
452
+ chainName,
453
+ }
454
+ },
455
+ )
456
+
457
+ // If allSupportedTokens is true, combine with supported tokens
458
+ if (allSupportedTokens) {
459
+ // Create a set of existing token keys for quick lookup
460
+ const existingTokenKeys = new Set(
461
+ baseFormattedTokens.map(
462
+ (token) => `${token.chainId}-${token.contractAddress.toLowerCase()}`,
463
+ ),
464
+ )
465
+
466
+ // Add supported tokens that don't exist in the base list
467
+ const additionalSupportedTokens = supportedTokens
468
+ .filter((supportedToken) => {
469
+ const key = `${supportedToken.chainId}-${supportedToken.contractAddress.toLowerCase()}`
470
+ return !existingTokenKeys.has(key)
471
+ })
472
+ .map((supportedToken) => {
473
+ const tokenName = getFormatttedTokenName(
474
+ supportedToken.name,
475
+ supportedToken.symbol,
476
+ supportedToken.chainId,
477
+ )
478
+
479
+ return {
480
+ ...supportedToken,
481
+ tokenName: tokenName,
482
+ contractInfo: {
483
+ decimals: supportedToken.decimals,
484
+ symbol: supportedToken.symbol,
485
+ name: supportedToken.name,
486
+ },
487
+ // Add minimal required properties to make it work
488
+ balance: "",
489
+ balanceFormatted: "",
490
+ balanceUsdFormatted: "",
491
+ priceUsd: 0,
492
+ isSufficientBalance: true,
493
+ // Use any type to bypass strict type checking for now
494
+ } as any
495
+ })
496
+
497
+ // Combine lists with base tokens taking precedence
498
+ return [...baseFormattedTokens, ...additionalSupportedTokens]
499
+ }
500
+
501
+ return baseFormattedTokens
502
+ }, [filteredTokens, targetAmountUsd, allSupportedTokens, supportedTokens])
451
503
 
452
504
  const showInsufficientBalance = useMemo(() => {
453
505
  return (