0xtrails 0.5.0 → 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 (189) hide show
  1. package/dist/analytics.d.ts +8 -3
  2. package/dist/analytics.d.ts.map +1 -1
  3. package/dist/{ccip-DhEkQ6QC.js → ccip-Dw5AN7oU.js} +1 -1
  4. package/dist/cctp.d.ts +0 -149
  5. package/dist/cctp.d.ts.map +1 -1
  6. package/dist/chains.d.ts +28 -3
  7. package/dist/chains.d.ts.map +1 -1
  8. package/dist/config.d.ts +11 -0
  9. package/dist/config.d.ts.map +1 -1
  10. package/dist/constants.d.ts +1 -1
  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-MhD2DA7_.js → index-BtVUTbEZ.js} +30984 -38945
  18. package/dist/index.d.ts +7 -5
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +108 -107
  21. package/dist/indexerClient.d.ts +2 -2
  22. package/dist/intents.d.ts +0 -17
  23. package/dist/intents.d.ts.map +1 -1
  24. package/dist/mutations.d.ts.map +1 -1
  25. package/dist/paymasterSend.d.ts.map +1 -1
  26. package/dist/prepareSend.d.ts +1 -1
  27. package/dist/prepareSend.d.ts.map +1 -1
  28. package/dist/sendUserOp.d.ts +0 -18
  29. package/dist/sendUserOp.d.ts.map +1 -1
  30. package/dist/tokenBalances.d.ts.map +1 -1
  31. package/dist/tokens.d.ts +10 -8
  32. package/dist/tokens.d.ts.map +1 -1
  33. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +4 -5
  34. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  35. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +4 -5
  36. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
  37. package/dist/transactionIntent/deposits/standardDeposit.d.ts +2 -2
  38. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  39. package/dist/transactionIntent/execution/transactionState.d.ts +2 -2
  40. package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -1
  41. package/dist/transactionIntent/handlers/crossChain.d.ts +4 -4
  42. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  43. package/dist/transactionIntent/handlers/index.d.ts +0 -1
  44. package/dist/transactionIntent/handlers/index.d.ts.map +1 -1
  45. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +4 -34
  46. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  47. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  48. package/dist/transactionIntent/quote/quoteHelpers.d.ts +2 -1
  49. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  50. package/dist/transactionIntent/types.d.ts +6 -19
  51. package/dist/transactionIntent/types.d.ts.map +1 -1
  52. package/dist/transactionIntent/utils/index.d.ts +0 -1
  53. package/dist/transactionIntent/utils/index.d.ts.map +1 -1
  54. package/dist/transactions.d.ts +2 -20
  55. package/dist/transactions.d.ts.map +1 -1
  56. package/dist/utils.d.ts +8 -2
  57. package/dist/utils.d.ts.map +1 -1
  58. package/dist/walletUtils.d.ts +21 -0
  59. package/dist/walletUtils.d.ts.map +1 -0
  60. package/dist/wallets.d.ts +33 -240
  61. package/dist/wallets.d.ts.map +1 -1
  62. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  63. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  64. package/dist/widget/components/FeeOption.d.ts +8 -13
  65. package/dist/widget/components/FeeOption.d.ts.map +1 -1
  66. package/dist/widget/components/FeeOptions.d.ts +11 -5
  67. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  68. package/dist/widget/components/NativeGasOption.d.ts.map +1 -1
  69. package/dist/widget/components/Pay.d.ts.map +1 -1
  70. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  71. package/dist/widget/components/QRCodeDeposit.d.ts +5 -0
  72. package/dist/widget/components/QRCodeDeposit.d.ts.map +1 -1
  73. package/dist/widget/components/QRCodeWalletSelect.d.ts +13 -0
  74. package/dist/widget/components/QRCodeWalletSelect.d.ts.map +1 -0
  75. package/dist/widget/components/QrCode.d.ts.map +1 -1
  76. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  77. package/dist/widget/components/Receipt.d.ts.map +1 -1
  78. package/dist/widget/components/ScreenHeader.d.ts +1 -1
  79. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  80. package/dist/widget/components/Toast.d.ts.map +1 -1
  81. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  82. package/dist/widget/css/compiled.css +1 -1
  83. package/dist/widget/hooks/useCheckout.d.ts +15 -1
  84. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  85. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  86. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  87. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  88. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  89. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  90. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts +7 -0
  91. package/dist/widget/hooks/useIsConnectedWalletSmartContract.d.ts.map +1 -0
  92. package/dist/widget/hooks/useIsSequenceWallet.d.ts +6 -0
  93. package/dist/widget/hooks/useIsSequenceWallet.d.ts.map +1 -0
  94. package/dist/widget/hooks/useQuote.d.ts +5 -8
  95. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  96. package/dist/widget/hooks/useRecentTokens.d.ts.map +1 -1
  97. package/dist/widget/hooks/useSelectedFeeOption.d.ts +30 -0
  98. package/dist/widget/hooks/useSelectedFeeOption.d.ts.map +1 -0
  99. package/dist/widget/hooks/useSendForm.d.ts +6 -15
  100. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  101. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  102. package/dist/widget/index.js +1 -1
  103. package/dist/widget/providers/TrailsProvider.d.ts +23 -12
  104. package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
  105. package/dist/widget/widget.d.ts +11 -0
  106. package/dist/widget/widget.d.ts.map +1 -1
  107. package/package.json +8 -8
  108. package/src/analytics.ts +53 -21
  109. package/src/cctp.ts +0 -1016
  110. package/src/chains.ts +93 -39
  111. package/src/config.ts +24 -6
  112. package/src/constants.ts +1 -4
  113. package/src/contractUtils.ts +6 -6
  114. package/src/estimate.ts +3 -6
  115. package/src/fees.ts +5 -10
  116. package/src/gasless.ts +45 -0
  117. package/src/index.ts +7 -6
  118. package/src/indexerClient.ts +2 -2
  119. package/src/intents.ts +52 -206
  120. package/src/mutations.ts +3 -2
  121. package/src/paymasterSend.ts +2 -5
  122. package/src/prepareSend.ts +9 -12
  123. package/src/sendUserOp.ts +3 -64
  124. package/src/tokenBalances.ts +2 -1
  125. package/src/tokens.ts +62 -133
  126. package/src/trailsClient.ts +1 -1
  127. package/src/transactionIntent/deposits/depositOrchestrator.ts +14 -15
  128. package/src/transactionIntent/deposits/gaslessDeposit.ts +70 -100
  129. package/src/transactionIntent/deposits/standardDeposit.ts +22 -28
  130. package/src/transactionIntent/execution/transactionState.ts +2 -2
  131. package/src/transactionIntent/handlers/crossChain.ts +165 -385
  132. package/src/transactionIntent/handlers/index.ts +0 -1
  133. package/src/transactionIntent/handlers/sameChainSameToken.ts +228 -94
  134. package/src/transactionIntent/quote/normalizeQuote.ts +4 -6
  135. package/src/transactionIntent/quote/quoteHelpers.ts +35 -3
  136. package/src/transactionIntent/types.ts +6 -27
  137. package/src/transactionIntent/utils/index.ts +0 -1
  138. package/src/transactions.ts +6 -203
  139. package/src/umd.tsx +1 -3
  140. package/src/utils.ts +28 -8
  141. package/src/walletUtils.ts +42 -0
  142. package/src/wallets.ts +361 -203
  143. package/src/widget/compiled.css +1 -1
  144. package/src/widget/components/AccountIntentTransactionHistory.tsx +73 -4
  145. package/src/widget/components/AccountSettings.tsx +17 -17
  146. package/src/widget/components/ChainList.tsx +3 -3
  147. package/src/widget/components/ClassicSwap.tsx +19 -10
  148. package/src/widget/components/ConfigDisplay.tsx +1 -1
  149. package/src/widget/components/FeeOption.tsx +63 -20
  150. package/src/widget/components/FeeOptions.tsx +54 -123
  151. package/src/widget/components/NativeGasOption.tsx +3 -1
  152. package/src/widget/components/Pay.tsx +18 -11
  153. package/src/widget/components/PoolDeposit.tsx +23 -10
  154. package/src/widget/components/QRCodeDeposit.tsx +50 -30
  155. package/src/widget/components/QRCodeWalletSelect.tsx +77 -0
  156. package/src/widget/components/QrCode.tsx +188 -233
  157. package/src/widget/components/QuoteDetails.tsx +48 -2
  158. package/src/widget/components/Receipt.tsx +5 -2
  159. package/src/widget/components/ScreenHeader.tsx +10 -8
  160. package/src/widget/components/Toast.tsx +10 -0
  161. package/src/widget/components/TokenImage.tsx +56 -13
  162. package/src/widget/hooks/useCheckout.ts +71 -0
  163. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  164. package/src/widget/hooks/useDebugScreens.ts +5 -0
  165. package/src/widget/hooks/useIntentTransactionHistory.ts +788 -418
  166. package/src/widget/hooks/useIsConnectedWalletSmartContract.ts +43 -0
  167. package/src/widget/hooks/useIsSequenceWallet.ts +17 -0
  168. package/src/widget/hooks/useQuote.ts +16 -17
  169. package/src/widget/hooks/useRecentTokens.ts +2 -1
  170. package/src/widget/hooks/useSelectedFeeOption.tsx +257 -0
  171. package/src/widget/hooks/useSendForm.ts +172 -47
  172. package/src/widget/hooks/useTokenList.ts +15 -2
  173. package/src/widget/providers/TrailsProvider.tsx +53 -25
  174. package/src/widget/widget.tsx +119 -48
  175. package/dist/cctpqueue.d.ts +0 -18
  176. package/dist/cctpqueue.d.ts.map +0 -1
  177. package/dist/preconditions.d.ts +0 -12
  178. package/dist/preconditions.d.ts.map +0 -1
  179. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts +0 -62
  180. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts.map +0 -1
  181. package/dist/transactionIntent/utils/lifiHelpers.d.ts +0 -10
  182. package/dist/transactionIntent/utils/lifiHelpers.d.ts.map +0 -1
  183. package/dist/widget/hooks/useSelectedFeeToken.d.ts +0 -33
  184. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +0 -1
  185. package/src/cctpqueue.ts +0 -69
  186. package/src/preconditions.ts +0 -47
  187. package/src/transactionIntent/handlers/sameChainDifferentToken.ts +0 -323
  188. package/src/transactionIntent/utils/lifiHelpers.ts +0 -68
  189. 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
+ }
@@ -14,10 +14,8 @@ import { prepareSend } from "../../prepareSend.js"
14
14
  import { abortControllerRegistry } from "../../abortController.js"
15
15
  import { TradeType } from "../../prepareSend.js"
16
16
  import { getChainInfo } from "../../chains.js"
17
- import type {
18
- MetaTxnReceipt,
19
- PrepareSendOptions,
20
- } from "../../transactionIntent/types.js"
17
+ import type { IntentTransaction } from "@0xsequence/trails-api"
18
+ import type { PrepareSendOptions } from "../../transactionIntent/types.js"
21
19
  import type { Chain } from "../../chains.js"
22
20
  import type { SupportedToken } from "../../tokens.js"
23
21
  import type { TransactionState } from "../../transactions.js"
@@ -25,6 +23,7 @@ import type { PrepareSendFees } from "../../prepareSend.js"
25
23
  import { getTokenPrice } from "../../prices.js"
26
24
  import { useCommitIntent, useExecuteIntent } from "../../mutations.js"
27
25
  import type { CheckoutOnHandlers } from "./useCheckout.js"
26
+ import type { FeeOption } from "@0xsequence/trails-api"
28
27
 
29
28
  export type UseQuoteProps = {
30
29
  walletClient?: any // TODO: fix this, has to do with viem/wagmi versions
@@ -69,10 +68,7 @@ export type UseQuoteProps = {
69
68
  */
70
69
  checkoutOnHandlers?: Partial<CheckoutOnHandlers>
71
70
  paymasterUrl?: string
72
- selectedFeeToken?: {
73
- tokenAddress: string
74
- tokenSymbol?: string
75
- } | null
71
+ selectedFeeOption?: FeeOption | null
76
72
  abortSignal?: AbortSignal
77
73
  apiKey?: string | null
78
74
  nodeGatewayEnv?: "prod" | "dev" | "local" | "cors-anywhere"
@@ -82,12 +78,12 @@ export type SwapReturn = {
82
78
  originTransaction: {
83
79
  transactionHash?: string | null
84
80
  explorerUrl?: string | null
85
- receipt: TransactionReceipt | MetaTxnReceipt | null
81
+ receipt: TransactionReceipt | IntentTransaction | null
86
82
  }
87
83
  destinationTransaction: {
88
84
  transactionHash?: string | null
89
85
  explorerUrl?: string | null
90
- receipt: MetaTxnReceipt | null
86
+ receipt: IntentTransaction | null
91
87
  }
92
88
  totalCompletionSeconds?: number
93
89
  }
@@ -149,7 +145,7 @@ export function useQuote({
149
145
  checkoutOnHandlers,
150
146
  quoteProvider,
151
147
  paymasterUrl,
152
- selectedFeeToken,
148
+ selectedFeeOption,
153
149
  nodeGatewayEnv,
154
150
  abortSignal: externalAbortSignal,
155
151
  apiKey,
@@ -183,6 +179,8 @@ export function useQuote({
183
179
  : abortControllerRef.current.signal
184
180
 
185
181
  const { trailsApiKey, trailsApiUrl, sequenceIndexerUrl } = useTrails()
182
+ // trailsApiKey is the same as sequenceProjectAccessKey
183
+ const sequenceProjectAccessKey = trailsApiKey
186
184
 
187
185
  const trailsClient = useTrailsClient({
188
186
  apiKey: trailsApiKey,
@@ -375,13 +373,14 @@ export function useQuote({
375
373
  slippageTolerance: slippageTolerance?.toString(),
376
374
  quoteProvider: quoteProvider,
377
375
  paymasterUrl: paymasterUrl,
378
- selectedFeeToken: selectedFeeToken ?? undefined,
376
+ selectedFeeOption: selectedFeeOption ?? null,
379
377
  abortSignal: combinedAbortSignal,
380
378
  originNativeTokenPriceUsd: originNativeTokenPriceUsd,
381
379
  commitIntentFn: commitIntentMutation.mutateAsync,
382
380
  executeIntentFn: executeIntentMutation.mutateAsync,
383
381
  checkoutOnHandlers,
384
382
  sequenceIndexerUrl,
383
+ sequenceProjectAccessKey,
385
384
  }
386
385
 
387
386
  logger.console.log("[trails-sdk] options", options)
@@ -418,10 +417,10 @@ export function useQuote({
418
417
  const swap = async (): Promise<SwapReturn> => {
419
418
  const {
420
419
  depositUserTxnReceipt,
421
- destinationMetaTxnReceipt,
420
+ destinationIntentTransaction,
422
421
  totalCompletionSeconds,
423
422
  } = await send({
424
- selectedFeeToken: selectedFeeToken ?? undefined,
423
+ selectedFeeOption: selectedFeeOption ?? null,
425
424
  })
426
425
 
427
426
  return {
@@ -434,12 +433,12 @@ export function useQuote({
434
433
  receipt: depositUserTxnReceipt,
435
434
  },
436
435
  destinationTransaction: {
437
- transactionHash: destinationMetaTxnReceipt?.txnHash,
436
+ transactionHash: destinationIntentTransaction?.txnHash,
438
437
  explorerUrl: getExplorerUrl({
439
- txHash: destinationMetaTxnReceipt?.txnHash as string,
438
+ txHash: destinationIntentTransaction?.txnHash as string,
440
439
  chainId: toChainId,
441
440
  }),
442
- receipt: destinationMetaTxnReceipt,
441
+ receipt: destinationIntentTransaction,
443
442
  },
444
443
  totalCompletionSeconds,
445
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
+ }