0xtrails 0.4.3 → 0.6.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.
Files changed (207) hide show
  1. package/dist/analytics.d.ts +8 -3
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/{ccip-C9CA3tIi.js → ccip-Dw5AN7oU.js} +27 -27
  4. package/dist/cctp.d.ts +0 -149
  5. package/dist/cctp.d.ts.map +1 -1
  6. package/dist/chains.d.ts +31 -4
  7. package/dist/chains.d.ts.map +1 -1
  8. package/dist/config.d.ts +28 -52
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/constants.d.ts +1 -7
  11. package/dist/constants.d.ts.map +1 -1
  12. package/dist/contractUtils.d.ts.map +1 -1
  13. package/dist/estimate.d.ts.map +1 -1
  14. package/dist/fees.d.ts.map +1 -1
  15. package/dist/gasless.d.ts +12 -0
  16. package/dist/gasless.d.ts.map +1 -1
  17. package/dist/{index-CNB57v0n.js → index-BtVUTbEZ.js} +32255 -40547
  18. package/dist/index.d.ts +13 -11
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +320 -944
  21. package/dist/indexerClient.d.ts +9 -6
  22. package/dist/indexerClient.d.ts.map +1 -1
  23. package/dist/intents.d.ts +0 -18
  24. package/dist/intents.d.ts.map +1 -1
  25. package/dist/mutations.d.ts.map +1 -1
  26. package/dist/paymasterSend.d.ts.map +1 -1
  27. package/dist/prepareSend.d.ts +1 -1
  28. package/dist/prepareSend.d.ts.map +1 -1
  29. package/dist/sendUserOp.d.ts +0 -18
  30. package/dist/sendUserOp.d.ts.map +1 -1
  31. package/dist/sequenceWallet.d.ts +1 -1
  32. package/dist/sequenceWallet.d.ts.map +1 -1
  33. package/dist/tokenBalances.d.ts.map +1 -1
  34. package/dist/tokens.d.ts +10 -8
  35. package/dist/tokens.d.ts.map +1 -1
  36. package/dist/trailsClient.d.ts +3 -3
  37. package/dist/trailsClient.d.ts.map +1 -1
  38. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +4 -5
  39. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  40. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +4 -5
  41. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
  42. package/dist/transactionIntent/deposits/standardDeposit.d.ts +2 -2
  43. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  44. package/dist/transactionIntent/execution/transactionState.d.ts +2 -2
  45. package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -1
  46. package/dist/transactionIntent/handlers/crossChain.d.ts +6 -4
  47. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  48. package/dist/transactionIntent/handlers/index.d.ts +0 -1
  49. package/dist/transactionIntent/handlers/index.d.ts.map +1 -1
  50. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +4 -34
  51. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  52. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  53. package/dist/transactionIntent/quote/quoteHelpers.d.ts +2 -1
  54. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  55. package/dist/transactionIntent/types.d.ts +7 -19
  56. package/dist/transactionIntent/types.d.ts.map +1 -1
  57. package/dist/transactionIntent/utils/index.d.ts +0 -1
  58. package/dist/transactionIntent/utils/index.d.ts.map +1 -1
  59. package/dist/transactions.d.ts +6 -18
  60. package/dist/transactions.d.ts.map +1 -1
  61. package/dist/utils.d.ts +8 -2
  62. package/dist/utils.d.ts.map +1 -1
  63. package/dist/walletUtils.d.ts +21 -0
  64. package/dist/walletUtils.d.ts.map +1 -0
  65. package/dist/wallets.d.ts +33 -240
  66. package/dist/wallets.d.ts.map +1 -1
  67. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  68. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  69. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  70. package/dist/widget/components/FeeOption.d.ts +8 -13
  71. package/dist/widget/components/FeeOption.d.ts.map +1 -1
  72. package/dist/widget/components/FeeOptions.d.ts +11 -5
  73. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  74. package/dist/widget/components/NativeGasOption.d.ts.map +1 -1
  75. package/dist/widget/components/Pay.d.ts.map +1 -1
  76. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  77. package/dist/widget/components/QRCodeDeposit.d.ts +5 -0
  78. package/dist/widget/components/QRCodeDeposit.d.ts.map +1 -1
  79. package/dist/widget/components/QRCodeWalletSelect.d.ts +13 -0
  80. package/dist/widget/components/QRCodeWalletSelect.d.ts.map +1 -0
  81. package/dist/widget/components/QrCode.d.ts.map +1 -1
  82. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  83. package/dist/widget/components/Receipt.d.ts.map +1 -1
  84. package/dist/widget/components/ScreenHeader.d.ts +1 -1
  85. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  86. package/dist/widget/components/SlippageToleranceSettings.d.ts +1 -0
  87. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  88. package/dist/widget/components/Toast.d.ts.map +1 -1
  89. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  90. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  91. package/dist/widget/css/compiled.css +1 -1
  92. package/dist/widget/hooks/useCheckout.d.ts +15 -1
  93. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  94. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  95. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  96. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  97. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  98. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  99. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts +7 -0
  100. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts.map +1 -0
  101. package/dist/widget/hooks/useIsSequenceWallet.d.ts +6 -0
  102. package/dist/widget/hooks/useIsSequenceWallet.d.ts.map +1 -0
  103. package/dist/widget/hooks/useQuote.d.ts +6 -11
  104. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  105. package/dist/widget/hooks/useRecentTokens.d.ts.map +1 -1
  106. package/dist/widget/hooks/useSelectedFeeOption.d.ts +30 -0
  107. package/dist/widget/hooks/useSelectedFeeOption.d.ts.map +1 -0
  108. package/dist/widget/hooks/useSendForm.d.ts +6 -15
  109. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  110. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  111. package/dist/widget/index.d.ts +1 -0
  112. package/dist/widget/index.d.ts.map +1 -1
  113. package/dist/widget/index.js +4 -2
  114. package/dist/widget/providers/TrailsProvider.d.ts +29 -0
  115. package/dist/widget/providers/TrailsProvider.d.ts.map +1 -0
  116. package/dist/widget/widget.d.ts +14 -3
  117. package/dist/widget/widget.d.ts.map +1 -1
  118. package/package.json +9 -8
  119. package/src/analytics.ts +53 -21
  120. package/src/cctp.ts +0 -1016
  121. package/src/chains.ts +155 -68
  122. package/src/config.ts +51 -207
  123. package/src/constants.ts +1 -13
  124. package/src/contractUtils.ts +6 -6
  125. package/src/estimate.ts +3 -6
  126. package/src/fees.ts +5 -10
  127. package/src/gasless.ts +45 -0
  128. package/src/index.ts +18 -40
  129. package/src/indexerClient.ts +39 -48
  130. package/src/intents.ts +51 -226
  131. package/src/mutations.ts +3 -2
  132. package/src/paymasterSend.ts +2 -5
  133. package/src/prepareSend.ts +21 -14
  134. package/src/sendUserOp.ts +3 -64
  135. package/src/sequenceWallet.ts +1 -2
  136. package/src/tokenBalances.ts +2 -1
  137. package/src/tokens.ts +62 -133
  138. package/src/trailsClient.ts +17 -12
  139. package/src/transactionIntent/deposits/depositOrchestrator.ts +14 -15
  140. package/src/transactionIntent/deposits/gaslessDeposit.ts +70 -100
  141. package/src/transactionIntent/deposits/standardDeposit.ts +22 -28
  142. package/src/transactionIntent/execution/transactionState.ts +2 -2
  143. package/src/transactionIntent/handlers/crossChain.ts +179 -381
  144. package/src/transactionIntent/handlers/index.ts +0 -1
  145. package/src/transactionIntent/handlers/sameChainSameToken.ts +228 -94
  146. package/src/transactionIntent/quote/normalizeQuote.ts +4 -6
  147. package/src/transactionIntent/quote/quoteHelpers.ts +35 -3
  148. package/src/transactionIntent/types.ts +7 -27
  149. package/src/transactionIntent/utils/index.ts +0 -1
  150. package/src/transactions.ts +19 -118
  151. package/src/umd.tsx +1 -3
  152. package/src/utils.ts +28 -8
  153. package/src/walletUtils.ts +42 -0
  154. package/src/wallets.ts +365 -208
  155. package/src/widget/compiled.css +1 -1
  156. package/src/widget/components/AccountIntentTransactionHistory.tsx +78 -4
  157. package/src/widget/components/AccountSettings.tsx +17 -17
  158. package/src/widget/components/ChainList.tsx +3 -3
  159. package/src/widget/components/ClassicSwap.tsx +19 -10
  160. package/src/widget/components/ConfigDisplay.tsx +19 -59
  161. package/src/widget/components/FeeOption.tsx +63 -20
  162. package/src/widget/components/FeeOptions.tsx +54 -123
  163. package/src/widget/components/NativeGasOption.tsx +3 -1
  164. package/src/widget/components/Pay.tsx +18 -11
  165. package/src/widget/components/PoolDeposit.tsx +23 -10
  166. package/src/widget/components/QRCodeDeposit.tsx +50 -30
  167. package/src/widget/components/QRCodeWalletSelect.tsx +77 -0
  168. package/src/widget/components/QrCode.tsx +188 -233
  169. package/src/widget/components/QuoteDetails.tsx +48 -2
  170. package/src/widget/components/Receipt.tsx +5 -2
  171. package/src/widget/components/ScreenHeader.tsx +10 -8
  172. package/src/widget/components/SlippageToleranceSettings.tsx +63 -14
  173. package/src/widget/components/Toast.tsx +10 -0
  174. package/src/widget/components/TokenImage.tsx +56 -13
  175. package/src/widget/components/WalletConnect.tsx +37 -99
  176. package/src/widget/hooks/useCheckout.ts +71 -0
  177. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  178. package/src/widget/hooks/useDebugScreens.ts +5 -0
  179. package/src/widget/hooks/useIntentTransactionHistory.ts +790 -412
  180. package/src/widget/hooks/useIsConnectedWalletSmartContract.ts +43 -0
  181. package/src/widget/hooks/useIsSequenceWallet.ts +17 -0
  182. package/src/widget/hooks/useQuote.ts +27 -34
  183. package/src/widget/hooks/useRecentTokens.ts +2 -1
  184. package/src/widget/hooks/useSelectedFeeOption.tsx +257 -0
  185. package/src/widget/hooks/useSendForm.ts +175 -46
  186. package/src/widget/hooks/useTokenList.ts +15 -2
  187. package/src/widget/index.tsx +8 -0
  188. package/src/widget/providers/TrailsProvider.tsx +123 -0
  189. package/src/widget/widget.tsx +165 -143
  190. package/dist/cctpqueue.d.ts +0 -18
  191. package/dist/cctpqueue.d.ts.map +0 -1
  192. package/dist/preconditions.d.ts +0 -12
  193. package/dist/preconditions.d.ts.map +0 -1
  194. package/dist/trails.d.ts +0 -110
  195. package/dist/trails.d.ts.map +0 -1
  196. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts +0 -62
  197. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts.map +0 -1
  198. package/dist/transactionIntent/utils/lifiHelpers.d.ts +0 -10
  199. package/dist/transactionIntent/utils/lifiHelpers.d.ts.map +0 -1
  200. package/dist/widget/hooks/useSelectedFeeToken.d.ts +0 -33
  201. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +0 -1
  202. package/src/cctpqueue.ts +0 -66
  203. package/src/preconditions.ts +0 -47
  204. package/src/trails.ts +0 -1303
  205. package/src/transactionIntent/handlers/sameChainDifferentToken.ts +0 -323
  206. package/src/transactionIntent/utils/lifiHelpers.ts +0 -68
  207. package/src/widget/hooks/useSelectedFeeToken.tsx +0 -288
@@ -0,0 +1,43 @@
1
+ import { useEffect, useState } from "react"
2
+ import { useAccount } from "wagmi"
3
+ import { isSmartContractWallet as checkIsSmartContractWallet } from "../../walletUtils.js"
4
+ import { logger } from "../../logger.js"
5
+
6
+ /**
7
+ * Hook to check if the currently connected wallet is a smart contract.
8
+ * @param chainId - The chain ID to check the wallet on
9
+ * @returns true if the connected wallet is a smart contract, false otherwise
10
+ */
11
+ export function useIsConnectedWalletSmartContract(chainId?: number): boolean {
12
+ const { address } = useAccount()
13
+ const [isSmartContractWallet, setIsSmartContractWallet] = useState(false)
14
+
15
+ useEffect(() => {
16
+ const checkIfContract = async () => {
17
+ if (address && chainId) {
18
+ try {
19
+ const isContract = await checkIsSmartContractWallet(address, chainId)
20
+ setIsSmartContractWallet(isContract)
21
+ } catch (error) {
22
+ logger.console.error(
23
+ "[trails-sdk] Error checking if wallet is contract:",
24
+ error,
25
+ )
26
+ setIsSmartContractWallet(false)
27
+ }
28
+ } else {
29
+ setIsSmartContractWallet(false)
30
+ }
31
+ }
32
+
33
+ checkIfContract()
34
+ }, [address, chainId])
35
+
36
+ logger.console.log("[trails-sdk] isSmartContractWallet:", {
37
+ isSmartContractWallet,
38
+ address,
39
+ chainId,
40
+ })
41
+
42
+ return isSmartContractWallet
43
+ }
@@ -0,0 +1,17 @@
1
+ import { useMemo } from "react"
2
+ import { useAccount } from "wagmi"
3
+ import { isSequenceWallet as checkIsSequenceWallet } from "../../walletUtils.js"
4
+
5
+ /**
6
+ * Hook to check if the currently connected wallet is a Sequence wallet.
7
+ * @returns true if the connected wallet is a Sequence wallet, false otherwise
8
+ */
9
+ export function useIsSequenceWallet(): boolean {
10
+ const { connector } = useAccount()
11
+
12
+ const isSequenceWallet = useMemo(() => {
13
+ return checkIsSequenceWallet(connector)
14
+ }, [connector])
15
+
16
+ return isSequenceWallet
17
+ }
@@ -1,5 +1,5 @@
1
1
  import { useQuery } from "@tanstack/react-query"
2
- import { useRef, useMemo } from "react"
2
+ import { useRef } from "react"
3
3
  import type { TransactionReceipt } from "viem"
4
4
  import { zeroAddress } from "viem"
5
5
  import { useIndexerGatewayClient } from "../../indexerClient.js"
@@ -7,26 +7,23 @@ import { getTokenBalancesWithPrices } from "../../tokenBalances.js"
7
7
  import { logger } from "../../logger.js"
8
8
  import { useSupportedTokens } from "../../tokens.js"
9
9
  import { useTrailsClient } from "../../trailsClient.js"
10
+ import { useTrails } from "../../widget/providers/TrailsProvider.js"
10
11
  import { getExplorerUrl } from "../../explorer.js"
11
12
  import { getFullErrorMessage, getPrettifiedErrorMessage } from "../../error.js"
12
13
  import { prepareSend } from "../../prepareSend.js"
13
14
  import { abortControllerRegistry } from "../../abortController.js"
14
15
  import { TradeType } from "../../prepareSend.js"
15
16
  import { getChainInfo } from "../../chains.js"
16
- import type { TrailsClientConfig } from "../../trailsClient.js"
17
- import type { IndexerGatewayConfig } from "../../indexerClient.js"
18
- import type {
19
- MetaTxnReceipt,
20
- PrepareSendOptions,
21
- } from "../../transactionIntent/types.js"
17
+ import type { IntentTransaction } from "@0xsequence/trails-api"
18
+ import type { PrepareSendOptions } from "../../transactionIntent/types.js"
22
19
  import type { Chain } from "../../chains.js"
23
20
  import type { SupportedToken } from "../../tokens.js"
24
21
  import type { TransactionState } from "../../transactions.js"
25
22
  import type { PrepareSendFees } from "../../prepareSend.js"
26
- import type { SequenceEnv } from "../../config.js"
27
23
  import { getTokenPrice } from "../../prices.js"
28
24
  import { useCommitIntent, useExecuteIntent } from "../../mutations.js"
29
25
  import type { CheckoutOnHandlers } from "./useCheckout.js"
26
+ import type { FeeOption } from "@0xsequence/trails-api"
30
27
 
31
28
  export type UseQuoteProps = {
32
29
  walletClient?: any // TODO: fix this, has to do with viem/wagmi versions
@@ -71,24 +68,22 @@ export type UseQuoteProps = {
71
68
  */
72
69
  checkoutOnHandlers?: Partial<CheckoutOnHandlers>
73
70
  paymasterUrl?: string
74
- selectedFeeToken?: {
75
- tokenAddress: string
76
- tokenSymbol?: string
77
- } | null
71
+ selectedFeeOption?: FeeOption | null
78
72
  abortSignal?: AbortSignal
79
73
  apiKey?: string | null
74
+ nodeGatewayEnv?: "prod" | "dev" | "local" | "cors-anywhere"
80
75
  }
81
76
 
82
77
  export type SwapReturn = {
83
78
  originTransaction: {
84
79
  transactionHash?: string | null
85
80
  explorerUrl?: string | null
86
- receipt: TransactionReceipt | MetaTxnReceipt | null
81
+ receipt: TransactionReceipt | IntentTransaction | null
87
82
  }
88
83
  destinationTransaction: {
89
84
  transactionHash?: string | null
90
85
  explorerUrl?: string | null
91
- receipt: MetaTxnReceipt | null
86
+ receipt: IntentTransaction | null
92
87
  }
93
88
  totalCompletionSeconds?: number
94
89
  }
@@ -150,13 +145,11 @@ export function useQuote({
150
145
  checkoutOnHandlers,
151
146
  quoteProvider,
152
147
  paymasterUrl,
153
- selectedFeeToken,
148
+ selectedFeeOption,
154
149
  nodeGatewayEnv,
155
150
  abortSignal: externalAbortSignal,
156
151
  apiKey,
157
- }: Partial<
158
- UseQuoteProps & { nodeGatewayEnv?: SequenceEnv }
159
- > = {}): UseQuoteReturn {
152
+ }: Partial<UseQuoteProps> = {}): UseQuoteReturn {
160
153
  // Set node gateway environment override for this quote session
161
154
  if (nodeGatewayEnv) {
162
155
  ;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
@@ -185,17 +178,15 @@ export function useQuote({
185
178
  })()
186
179
  : abortControllerRef.current.signal
187
180
 
188
- const trailsClientConfig = useMemo<TrailsClientConfig>(() => {
189
- return apiKey ? { apiKey } : {}
190
- }, [apiKey])
191
- const indexerGatewayClientConfig = useMemo<IndexerGatewayConfig>(() => {
192
- return apiKey ? { projectAccessKey: apiKey } : {}
193
- }, [apiKey])
181
+ const { trailsApiKey, trailsApiUrl, sequenceIndexerUrl } = useTrails()
182
+ // trailsApiKey is the same as sequenceProjectAccessKey
183
+ const sequenceProjectAccessKey = trailsApiKey
194
184
 
195
- const trailsClient = useTrailsClient(trailsClientConfig)
196
- const indexerGatewayClient = useIndexerGatewayClient(
197
- indexerGatewayClientConfig,
198
- )
185
+ const trailsClient = useTrailsClient({
186
+ apiKey: trailsApiKey,
187
+ hostname: trailsApiUrl,
188
+ })
189
+ const indexerGatewayClient = useIndexerGatewayClient()
199
190
 
200
191
  const { supportedTokens } = useSupportedTokens()
201
192
 
@@ -382,12 +373,14 @@ export function useQuote({
382
373
  slippageTolerance: slippageTolerance?.toString(),
383
374
  quoteProvider: quoteProvider,
384
375
  paymasterUrl: paymasterUrl,
385
- selectedFeeToken: selectedFeeToken ?? undefined,
376
+ selectedFeeOption: selectedFeeOption ?? null,
386
377
  abortSignal: combinedAbortSignal,
387
378
  originNativeTokenPriceUsd: originNativeTokenPriceUsd,
388
379
  commitIntentFn: commitIntentMutation.mutateAsync,
389
380
  executeIntentFn: executeIntentMutation.mutateAsync,
390
381
  checkoutOnHandlers,
382
+ sequenceIndexerUrl,
383
+ sequenceProjectAccessKey,
391
384
  }
392
385
 
393
386
  logger.console.log("[trails-sdk] options", options)
@@ -424,10 +417,10 @@ export function useQuote({
424
417
  const swap = async (): Promise<SwapReturn> => {
425
418
  const {
426
419
  depositUserTxnReceipt,
427
- destinationMetaTxnReceipt,
420
+ destinationIntentTransaction,
428
421
  totalCompletionSeconds,
429
422
  } = await send({
430
- selectedFeeToken: selectedFeeToken ?? undefined,
423
+ selectedFeeOption: selectedFeeOption ?? null,
431
424
  })
432
425
 
433
426
  return {
@@ -440,12 +433,12 @@ export function useQuote({
440
433
  receipt: depositUserTxnReceipt,
441
434
  },
442
435
  destinationTransaction: {
443
- transactionHash: destinationMetaTxnReceipt?.txnHash,
436
+ transactionHash: destinationIntentTransaction?.txnHash,
444
437
  explorerUrl: getExplorerUrl({
445
- txHash: destinationMetaTxnReceipt?.txnHash as string,
438
+ txHash: destinationIntentTransaction?.txnHash as string,
446
439
  chainId: toChainId,
447
440
  }),
448
- receipt: destinationMetaTxnReceipt,
441
+ receipt: destinationIntentTransaction,
449
442
  },
450
443
  totalCompletionSeconds,
451
444
  }
@@ -1,3 +1,4 @@
1
+ import { JsonEncode } from "@0xsequence/trails-api"
1
2
  import { useState, useEffect, useCallback } from "react"
2
3
  import type { SupportedToken } from "../../tokens.js"
3
4
  import { logger } from "../../logger.js"
@@ -66,7 +67,7 @@ export function useRecentTokens(accountAddress?: string) {
66
67
 
67
68
  // Update storage
68
69
  allRecentTokens[accountKey] = accountTokens
69
- localStorage.setItem(RECENT_TOKENS_KEY, JSON.stringify(allRecentTokens))
70
+ localStorage.setItem(RECENT_TOKENS_KEY, JsonEncode(allRecentTokens))
70
71
 
71
72
  // Update state
72
73
  setRecentTokens(accountTokens)
@@ -0,0 +1,257 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useState,
5
+ useCallback,
6
+ useEffect,
7
+ useMemo,
8
+ type ReactNode,
9
+ } from "react"
10
+ import type { FeeOption } from "@0xsequence/trails-api"
11
+ import { formatRawAmount, formatUsdAmountDisplay } from "../../tokenBalances.js"
12
+ import { logger } from "../../index.js"
13
+ import { isNativeToken, normalizeAddress } from "../../utils.js"
14
+ import { isAddressEqual } from "viem"
15
+
16
+ const createBalanceKey = (
17
+ chainId?: number,
18
+ tokenAddress?: string | null,
19
+ ): string =>
20
+ `${chainId ?? "unknown"}:${
21
+ !tokenAddress || isNativeToken(tokenAddress)
22
+ ? "native"
23
+ : normalizeAddress(tokenAddress)
24
+ }`
25
+
26
+ const safeFormatAmountWithSymbol = (option: FeeOption): string => {
27
+ const tokenDecimals =
28
+ typeof option.tokenDecimals === "number" &&
29
+ option.tokenDecimals > 0 &&
30
+ option.tokenDecimals <= 18
31
+ ? option.tokenDecimals
32
+ : isNativeToken(option.tokenAddress)
33
+ ? 18
34
+ : undefined
35
+
36
+ if (tokenDecimals === undefined) {
37
+ logger.console.warn("[trails-sdk] [FEE-OPTIONS] Missing token decimals", {
38
+ tokenAddress: option.tokenAddress,
39
+ tokenSymbol: option.tokenSymbol,
40
+ })
41
+ return option.tokenSymbol ? `-- ${option.tokenSymbol}` : "--"
42
+ }
43
+
44
+ try {
45
+ const formattedAmount = formatRawAmount(option.amount, tokenDecimals)
46
+ return option.tokenSymbol
47
+ ? `${formattedAmount} ${option.tokenSymbol}`
48
+ : formattedAmount
49
+ } catch (error) {
50
+ logger.console.warn(
51
+ "[trails-sdk] [FEE-OPTIONS] Failed to format fee option amount",
52
+ {
53
+ option,
54
+ tokenDecimals,
55
+ error,
56
+ },
57
+ )
58
+ return option.tokenSymbol ? `-- ${option.tokenSymbol}` : "--"
59
+ }
60
+ }
61
+
62
+ export type ProcessedFeeOption = FeeOption & {
63
+ notEnoughBalance?: boolean
64
+ tokenImageUrl?: string
65
+ chainId?: number
66
+ amountFormatted: string
67
+ amountUsdDisplay?: string
68
+ }
69
+
70
+ // Token type with balance information
71
+ export interface TokenWithBalance {
72
+ chainId: number
73
+ contractAddress?: string
74
+ balance?: string
75
+ imageUrl?: string
76
+ }
77
+
78
+ interface SelectedFeeOptionContextType {
79
+ selectedFeeOption: FeeOption | null
80
+ setSelectedFeeOption: (token: FeeOption | null) => void
81
+ clearSelectedFeeOption: () => void
82
+ processedFeeOptions: ProcessedFeeOption[]
83
+ setFeeOptions: (
84
+ feeOptions: ReadonlyArray<FeeOption> | null | undefined,
85
+ originChainId: number | undefined,
86
+ availableTokens: TokenWithBalance[],
87
+ ) => void
88
+ }
89
+
90
+ const SelectedFeeOptionContext = createContext<
91
+ SelectedFeeOptionContextType | undefined
92
+ >(undefined)
93
+
94
+ interface SelectedFeeOptionProviderProps {
95
+ children: ReactNode
96
+ initialToken?: FeeOption | null
97
+ }
98
+
99
+ export const SelectedFeeOptionProvider: React.FC<
100
+ SelectedFeeOptionProviderProps
101
+ > = ({ children, initialToken = null }) => {
102
+ const [selectedFeeOption, setSelectedFeeOptionInternalRaw] =
103
+ useState<FeeOption | null>(initialToken)
104
+ const [hasUserSelectedFeeOption, setHasUserSelectedFeeOption] =
105
+ useState(false)
106
+ const [feeOptions, setFeeOptionsInternal] = useState<FeeOption[]>([])
107
+ const [originTokenChainId, setOriginTokenChainId] = useState<
108
+ number | undefined
109
+ >(undefined)
110
+ const [availableTokens, setAvailableTokens] = useState<TokenWithBalance[]>([])
111
+
112
+ const tokenBalanceLookup = useMemo(() => {
113
+ const map = new Map<string, TokenWithBalance>()
114
+ for (const token of availableTokens) {
115
+ map.set(
116
+ createBalanceKey(token.chainId, token.contractAddress ?? null),
117
+ token,
118
+ )
119
+ }
120
+ return map
121
+ }, [availableTokens])
122
+
123
+ // Wrapper to track when user makes an explicit selection
124
+ const setSelectedFeeOption = useCallback((token: FeeOption | null) => {
125
+ setSelectedFeeOptionInternalRaw(token)
126
+ setHasUserSelectedFeeOption(true)
127
+ }, [])
128
+
129
+ const clearSelectedFeeOption = useCallback(() => {
130
+ setSelectedFeeOptionInternalRaw(null)
131
+ setHasUserSelectedFeeOption(false)
132
+ }, [])
133
+
134
+ // Process raw fee options with balance checks
135
+ const processedFeeOptions: ProcessedFeeOption[] = useMemo(() => {
136
+ if (!feeOptions.length) {
137
+ return []
138
+ }
139
+
140
+ return feeOptions
141
+ .map((option) => {
142
+ const balanceRecord =
143
+ originTokenChainId !== undefined
144
+ ? tokenBalanceLookup.get(
145
+ createBalanceKey(originTokenChainId, option.tokenAddress),
146
+ )
147
+ : undefined
148
+
149
+ const hasBalanceInfo =
150
+ Boolean(balanceRecord?.balance) && originTokenChainId !== undefined
151
+ const userBalance = balanceRecord?.balance ?? "0"
152
+ const requiredAmount = option.amount ? option.amount.toString() : "0"
153
+ const notEnoughBalance = hasBalanceInfo
154
+ ? BigInt(userBalance) < BigInt(requiredAmount)
155
+ : true
156
+ const amountFormatted = safeFormatAmountWithSymbol(option)
157
+
158
+ return {
159
+ ...option,
160
+ notEnoughBalance,
161
+ tokenImageUrl: balanceRecord?.imageUrl,
162
+ chainId: balanceRecord?.chainId ?? originTokenChainId,
163
+ amountUsdDisplay: formatUsdAmountDisplay(option.amountUsd ?? 0),
164
+ amountFormatted,
165
+ }
166
+ })
167
+ .sort(
168
+ (a, b) => Number(!!a.notEnoughBalance) - Number(!!b.notEnoughBalance),
169
+ )
170
+ }, [feeOptions, originTokenChainId, tokenBalanceLookup])
171
+
172
+ // Auto-select first fee option (ERC20 or native gas) with sufficient balance
173
+ // NOTE: If sufficient balance, it will usually resolve to the native gas option because native gas is the first option in the list returned by the API
174
+ useEffect(() => {
175
+ if (processedFeeOptions.length === 0) {
176
+ if (selectedFeeOption !== null) {
177
+ setSelectedFeeOptionInternalRaw(null)
178
+ setHasUserSelectedFeeOption(false)
179
+ }
180
+ return
181
+ }
182
+
183
+ const selectionStillValid = selectedFeeOption
184
+ ? processedFeeOptions.some((option) =>
185
+ isAddressEqual(
186
+ option.tokenAddress as `0x${string}`,
187
+ selectedFeeOption.tokenAddress as `0x${string}`,
188
+ ),
189
+ )
190
+ : false
191
+
192
+ if (selectedFeeOption && !selectionStillValid) {
193
+ setSelectedFeeOptionInternalRaw(null)
194
+ setHasUserSelectedFeeOption(false)
195
+ return
196
+ }
197
+
198
+ if (!hasUserSelectedFeeOption && !selectedFeeOption) {
199
+ const fallbackOption =
200
+ processedFeeOptions.find((option) => !option.notEnoughBalance) ??
201
+ processedFeeOptions[0] ??
202
+ null
203
+
204
+ // Only fallback if the fallback option is erc20
205
+ if (fallbackOption && !isNativeToken(fallbackOption.tokenAddress)) {
206
+ setSelectedFeeOptionInternalRaw(fallbackOption)
207
+ }
208
+ }
209
+ }, [processedFeeOptions, selectedFeeOption, hasUserSelectedFeeOption])
210
+
211
+ // Function to set raw fee options and process them
212
+ const setFeeOptions = useCallback(
213
+ (
214
+ feeOptions: ReadonlyArray<FeeOption> | null | undefined,
215
+ originChainId: number | undefined,
216
+ tokens: TokenWithBalance[],
217
+ ) => {
218
+ const normalized = feeOptions ? [...feeOptions] : []
219
+
220
+ setFeeOptionsInternal(normalized)
221
+ setOriginTokenChainId(originChainId)
222
+ setAvailableTokens(tokens)
223
+
224
+ if (normalized.length === 0) {
225
+ setSelectedFeeOptionInternalRaw(null)
226
+ setHasUserSelectedFeeOption(false)
227
+ }
228
+ },
229
+ [],
230
+ )
231
+
232
+ const value: SelectedFeeOptionContextType = {
233
+ selectedFeeOption,
234
+ setSelectedFeeOption,
235
+ clearSelectedFeeOption,
236
+ processedFeeOptions,
237
+ setFeeOptions,
238
+ }
239
+
240
+ return (
241
+ <SelectedFeeOptionContext.Provider value={value}>
242
+ {children}
243
+ </SelectedFeeOptionContext.Provider>
244
+ )
245
+ }
246
+
247
+ export const useSelectedFeeOption = (): SelectedFeeOptionContextType => {
248
+ const context = useContext(SelectedFeeOptionContext)
249
+
250
+ if (context === undefined) {
251
+ throw new Error(
252
+ "useSelectedFeeOption must be used within a SelectedFeeOptionProvider",
253
+ )
254
+ }
255
+
256
+ return context
257
+ }