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
@@ -37,23 +37,23 @@ export const AccountSettings: React.FC<AccountSettingsProps> = ({ onBack }) => {
37
37
 
38
38
  return (
39
39
  <div className="flex flex-col h-full">
40
- <div className="flex items-center justify-between h-full">
41
- <ScreenHeader
42
- headerContent="Settings"
43
- headerContentAlign="left"
44
- onBack={onBack}
45
- />
46
- <Tooltip message="Get help" className="mb-4 mr-0">
47
- <button
48
- type="button"
49
- onClick={onHelp}
50
- className="flex h-8 w-8 justify-center items-center rounded-full bg-gray-50 dark:bg-gray-700 cursor-pointer transition-colors text-gray-900 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600"
51
- title="Get help"
52
- >
53
- <HelpCircle className="w-4 h-4" />
54
- </button>
55
- </Tooltip>
56
- </div>
40
+ <ScreenHeader
41
+ headerContent="Settings"
42
+ headerContentAlign="left"
43
+ onBack={onBack}
44
+ rightSideContent={
45
+ <Tooltip message="Get help">
46
+ <button
47
+ type="button"
48
+ onClick={onHelp}
49
+ className="flex h-8 w-8 justify-center items-center rounded-full bg-gray-50 dark:bg-gray-700 cursor-pointer transition-colors text-gray-900 dark:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-600"
50
+ title="Get help"
51
+ >
52
+ <HelpCircle className="w-4 h-4" />
53
+ </button>
54
+ </Tooltip>
55
+ }
56
+ />
57
57
 
58
58
  <div className="flex-1">
59
59
  <div className="space-y-4">
@@ -6,7 +6,7 @@ import { ChainImage } from "./ChainImage.js"
6
6
  import { AllChainsIcon } from "./AllChainsIcon.js"
7
7
  import { SearchInputField } from "./SearchInputField.js"
8
8
  import { useChainFilter } from "../hooks/useChainFilter.js"
9
- import { useSupportedChains } from "../../chains.js"
9
+ import { useSupportedChains, getNormalizedChainName } from "../../chains.js"
10
10
 
11
11
  export interface ChainListItem {
12
12
  chainId: number
@@ -105,7 +105,7 @@ export const ChainList: React.FC<ChainListProps> = ({ onBack }) => {
105
105
  ? "trails-list-item-selected"
106
106
  : ""
107
107
  }`}
108
- title={`Select ${chain.name}`}
108
+ title={`Select ${chain.name} (${chain.chainId})`}
109
109
  initial={{ opacity: 0, y: 20 }}
110
110
  animate={{ opacity: 1, y: 0 }}
111
111
  exit={{ opacity: 0, y: -20 }}
@@ -122,7 +122,7 @@ export const ChainList: React.FC<ChainListProps> = ({ onBack }) => {
122
122
 
123
123
  <div className="flex-1 min-w-0 text-left">
124
124
  <h3 className="text-sm font-medium truncate text-gray-900 dark:text-white">
125
- {chain.name}
125
+ {getNormalizedChainName(chain.chainId, chain.name)}
126
126
  </h3>
127
127
  </div>
128
128
 
@@ -143,9 +143,9 @@ export const ClassicSwap: React.FC<ClassicSwapProps> = ({
143
143
  quoteError,
144
144
  quoteErrorPrettified,
145
145
  isSameTokenWithoutCustomCalldata,
146
- feeOptions,
147
- selectedFeeToken,
148
- setSelectedFeeToken,
146
+ processedFeeOptions,
147
+ selectedFeeOption,
148
+ setSelectedFeeOption,
149
149
  } = useSendForm({
150
150
  account,
151
151
  toAmount: tradeType === TradeType.EXACT_OUTPUT ? buyAmount : toAmount,
@@ -771,13 +771,22 @@ export const ClassicSwap: React.FC<ClassicSwapProps> = ({
771
771
  )}
772
772
 
773
773
  {/* Fee Options */}
774
- <FeeOptions
775
- feeOptions={feeOptions || []}
776
- selectedFeeToken={selectedFeeToken}
777
- setSelectedFeeToken={(token) => setSelectedFeeToken(token as any)}
778
- chainId={originToken?.chainId}
779
- isRefetching={isLoadingQuote}
780
- />
774
+ {fundMethod !== "qr-code" && fundMethod !== "exchange" && (
775
+ <FeeOptions
776
+ processedFeeOptions={processedFeeOptions || []}
777
+ selectedFeeOption={selectedFeeOption}
778
+ setSelectedFeeOption={(feeOption) =>
779
+ setSelectedFeeOption(feeOption)
780
+ }
781
+ chainId={originToken?.chainId}
782
+ isRefetching={isLoadingQuote}
783
+ originTokenInfo={{
784
+ originToken: originToken!,
785
+ originTokenBalance: originToken?.balance ?? "0",
786
+ originTokenAmount: sellAmount,
787
+ }}
788
+ />
789
+ )}
781
790
 
782
791
  <button
783
792
  type="submit"
@@ -61,7 +61,7 @@ export const ConfigDisplay: React.FC<ConfigDisplayProps> = ({
61
61
  {
62
62
  label: "Trails API Key",
63
63
  value: config.trailsApiKey,
64
- display: truncateString(config.trailsApiKey),
64
+ display: truncateString(config.trailsApiKey || "N/A"),
65
65
  },
66
66
  {
67
67
  label: "WalletConnect Project ID",
@@ -1,24 +1,23 @@
1
+ import { useMemo } from "react"
1
2
  import type React from "react"
2
3
  import { TokenImage } from "./TokenImage.js"
3
-
4
- export type FeeOption = {
5
- tokenAddress: string
6
- tokenSymbol: string
7
- tokenDecimals: number
8
- amount: string
9
- amountUSD: number
10
- amountFormatted: string
11
- amountUsdDisplay: string
12
- tokenImageUrl?: string
13
- notEnoughBalance?: boolean
14
- chainId?: number
15
- }
4
+ import type { ProcessedFeeOption } from "../hooks/useSelectedFeeOption.js"
5
+ import type { Token } from "../hooks/useOriginSelectedToken.js"
6
+ import { normalizeAddress } from "../../utils.js"
7
+ import { parseUnits } from "viem"
8
+ import { Tooltip } from "./Tooltip.js"
9
+ import { logger } from "../../logger.js"
16
10
 
17
11
  interface FeeOptionProps {
18
- option: FeeOption
12
+ option: ProcessedFeeOption
19
13
  isSelected: boolean
20
14
  onClick: () => void
21
15
  chainId?: number
16
+ originTokenInfo?: {
17
+ originToken: Token
18
+ originTokenBalance: string
19
+ originTokenAmount: string
20
+ }
22
21
  }
23
22
 
24
23
  export const FeeOption: React.FC<FeeOptionProps> = ({
@@ -26,19 +25,53 @@ export const FeeOption: React.FC<FeeOptionProps> = ({
26
25
  isSelected,
27
26
  onClick,
28
27
  chainId,
28
+ originTokenInfo,
29
29
  }) => {
30
- return (
30
+ const isDisabled = useMemo(() => {
31
+ try {
32
+ const originTokenAddress = originTokenInfo?.originToken?.contractAddress
33
+ const optionAddress = option.tokenAddress
34
+ const isOriginFeeOption =
35
+ originTokenAddress &&
36
+ optionAddress &&
37
+ normalizeAddress(originTokenAddress) === normalizeAddress(optionAddress)
38
+
39
+ let isInsufficientOriginBalance = false
40
+
41
+ if (isOriginFeeOption && originTokenInfo) {
42
+ const balanceInWei = BigInt(originTokenInfo.originTokenBalance ?? "0")
43
+ const originTokenAmountInWei = parseUnits(
44
+ originTokenInfo.originTokenAmount ?? "0",
45
+ originTokenInfo.originToken?.contractInfo?.decimals ?? 18,
46
+ )
47
+ const feeAmountInWei = BigInt(option.amount ?? "0")
48
+
49
+ isInsufficientOriginBalance =
50
+ originTokenAmountInWei + feeAmountInWei > balanceInWei
51
+ }
52
+
53
+ return option.notEnoughBalance || isInsufficientOriginBalance
54
+ } catch (error) {
55
+ logger.console.error(
56
+ "[trails-sdk] [FEE-OPTION] error checking if fee option is disabled",
57
+ error,
58
+ )
59
+ return true
60
+ }
61
+ }, [originTokenInfo, option])
62
+
63
+ const button = (
31
64
  <button
32
65
  type="button"
33
66
  className={`w-full flex items-center justify-between p-1.5 trails-border-radius-input border transition-colors ${
34
- option.notEnoughBalance
67
+ isDisabled
35
68
  ? "cursor-not-allowed opacity-50 bg-gray-100 dark:bg-gray-800 border-gray-300 dark:border-gray-600"
36
69
  : isSelected
37
70
  ? "cursor-pointer trails-bg-primary/10 border-blue-500 bg-blue-50 dark:bg-blue-900/20"
38
71
  : "cursor-pointer trails-bg-card trails-border-primary hover:trails-hover-bg"
39
72
  }`}
40
- onClick={option.notEnoughBalance ? undefined : onClick}
41
- disabled={option.notEnoughBalance}
73
+ onClick={isDisabled ? undefined : onClick}
74
+ disabled={isDisabled}
42
75
  >
43
76
  <div className="flex items-center space-x-2">
44
77
  <TokenImage
@@ -50,7 +83,7 @@ export const FeeOption: React.FC<FeeOptionProps> = ({
50
83
  <div className="ml-2 text-left">
51
84
  <div
52
85
  className={`text-xs font-medium ${
53
- option.notEnoughBalance
86
+ isDisabled
54
87
  ? "text-gray-400 dark:text-gray-500"
55
88
  : "trails-text-primary"
56
89
  }`}
@@ -63,7 +96,7 @@ export const FeeOption: React.FC<FeeOptionProps> = ({
63
96
  <div className="text-right">
64
97
  <div
65
98
  className={`text-xs font-medium ${
66
- option.notEnoughBalance
99
+ isDisabled
67
100
  ? "text-gray-400 dark:text-gray-500"
68
101
  : "trails-text-primary"
69
102
  }`}
@@ -73,6 +106,16 @@ export const FeeOption: React.FC<FeeOptionProps> = ({
73
106
  </div>
74
107
  </button>
75
108
  )
109
+
110
+ if (isDisabled) {
111
+ return (
112
+ <Tooltip message="Not enough balance to use this fee option">
113
+ {button}
114
+ </Tooltip>
115
+ )
116
+ }
117
+
118
+ return button
76
119
  }
77
120
 
78
121
  export default FeeOption
@@ -1,100 +1,60 @@
1
1
  import { ChevronDown } from "lucide-react"
2
2
  import type React from "react"
3
- import { useRef, useState, useEffect } from "react"
4
- import { formatUsdAmountDisplay, formatRawAmount } from "../../tokenBalances.js"
5
- import {
6
- FeeOption,
7
- type FeeOption as EnhancedFeeOptionType,
8
- } from "./FeeOption.js"
9
- import { logger } from "../../logger.js"
10
- import { getTokenImageUrl } from "../../tokens.js"
3
+ import { useRef, useState } from "react"
11
4
  import { TokenImage } from "./TokenImage.js"
12
- import { ethAddress, zeroAddress } from "viem"
13
- import type { FeeOption as APIFeeOption } from "../../widget/hooks/useSelectedFeeToken.js"
14
- import type { SelectedFeeToken } from "../../prepareSend.js"
15
-
16
- const ZERO_ADDRESS = zeroAddress.toLowerCase()
17
- const ETH_ADDRESS = ethAddress.toLowerCase()
18
-
19
- const normalizeAddress = (address?: string | null): string =>
20
- (address ?? "").toLowerCase()
21
-
22
- const isNativeTokenAddress = (address?: string | null): boolean => {
23
- const normalized = normalizeAddress(address)
24
- return normalized === ZERO_ADDRESS || normalized === ETH_ADDRESS
25
- }
26
-
27
- const safeFormatAmountWithSymbol = (option: APIFeeOption): string => {
28
- const tokenDecimals =
29
- typeof option.tokenDecimals === "number" &&
30
- option.tokenDecimals > 0 &&
31
- option.tokenDecimals <= 18
32
- ? option.tokenDecimals
33
- : isNativeTokenAddress(option.tokenAddress)
34
- ? 18
35
- : undefined
36
-
37
- if (tokenDecimals === undefined) {
38
- logger.console.warn("[trails-sdk] [FEE-OPTIONS] Missing token decimals", {
39
- tokenAddress: option.tokenAddress,
40
- tokenSymbol: option.tokenSymbol,
41
- })
42
- return option.tokenSymbol ? `-- ${option.tokenSymbol}` : "--"
43
- }
44
-
45
- try {
46
- const formattedAmount = formatRawAmount(option.amount, tokenDecimals)
47
- return option.tokenSymbol
48
- ? `${formattedAmount} ${option.tokenSymbol}`
49
- : formattedAmount
50
- } catch (error) {
51
- logger.console.warn(
52
- "[trails-sdk] [FEE-OPTIONS] Failed to format fee option amount",
53
- {
54
- option,
55
- tokenDecimals,
56
- error,
57
- },
58
- )
59
- return option.tokenSymbol ? `-- ${option.tokenSymbol}` : "--"
60
- }
61
- }
62
-
63
- const safeFormatUsdDisplay = (option: APIFeeOption): string => {
64
- const usdValue =
65
- (option as { amountUsd?: number }).amountUsd ?? option.amountUSD ?? 0
66
- return formatUsdAmountDisplay(usdValue)
67
- }
5
+ import { useSelectedFundMethod } from "../hooks/useSelectedFundMethod.js"
6
+ import type { ProcessedFeeOption } from "../hooks/useSelectedFeeOption.js"
7
+ import { logger } from "../../logger.js"
8
+ import type { FeeOption } from "@0xsequence/trails-api"
9
+ import { FeeOption as FeeOptionComponent } from "./FeeOption.js"
10
+ import { isNativeToken, normalizeAddress } from "../../utils.js"
11
+ import type { Token } from "../hooks/useOriginSelectedToken.js"
68
12
 
69
13
  interface FeeOptionsProps {
70
- feeOptions: APIFeeOption[]
71
- selectedFeeToken: APIFeeOption | SelectedFeeToken | null
72
- setSelectedFeeToken: (token: APIFeeOption | null) => void
14
+ processedFeeOptions: ProcessedFeeOption[]
15
+ selectedFeeOption: FeeOption | null
16
+ setSelectedFeeOption: (token: FeeOption | null) => void
73
17
  chainId?: number
74
18
  isRefetching?: boolean // When true, the fee quote is stale and being refreshed, so hide the component
19
+ originTokenInfo?: {
20
+ originToken: Token
21
+ originTokenBalance: string
22
+ originTokenAmount: string
23
+ }
75
24
  }
76
25
 
77
26
  export const FeeOptions: React.FC<FeeOptionsProps> = ({
78
- feeOptions,
79
- selectedFeeToken,
80
- setSelectedFeeToken,
27
+ processedFeeOptions,
28
+ selectedFeeOption,
29
+ setSelectedFeeOption,
81
30
  chainId,
82
31
  isRefetching,
32
+ originTokenInfo,
83
33
  }) => {
84
- // Early returns BEFORE any hooks - this prevents "Rendered more hooks" error
34
+ // All hooks must be called before any early returns
35
+ const { selectedFundMethod } = useSelectedFundMethod()
36
+ const [isOpen, setIsOpen] = useState(false)
37
+ const accordionRef = useRef<HTMLDivElement>(null)
38
+
39
+ // Early returns AFTER all hooks - this is safe because hooks are always called first
40
+ // Hide component when fundMethod is qr-code or exchange
41
+ if (selectedFundMethod === "qr-code" || selectedFundMethod === "exchange") {
42
+ return null
43
+ }
44
+
85
45
  // Hide component when fee quote is stale and being refreshed
86
46
  if (isRefetching) {
87
47
  return null
88
48
  }
89
49
 
90
50
  // Don't render if no fee options available
91
- if (!feeOptions || feeOptions.length === 0) {
51
+ if (!processedFeeOptions || processedFeeOptions.length === 0) {
92
52
  return null
93
53
  }
94
54
 
95
55
  // Check if there are non-native options before calling hooks
96
- const hasNonNativeOptions = feeOptions.some(
97
- (opt) => !isNativeTokenAddress(opt.tokenAddress),
56
+ const hasNonNativeOptions = processedFeeOptions.some(
57
+ (opt) => !isNativeToken(opt.tokenAddress),
98
58
  )
99
59
 
100
60
  // Don't render if only native token is available
@@ -102,57 +62,27 @@ export const FeeOptions: React.FC<FeeOptionsProps> = ({
102
62
  return null
103
63
  }
104
64
 
105
- // NOW we can safely call hooks after all early returns
106
- const [isOpen, setIsOpen] = useState(false)
107
- const accordionRef = useRef<HTMLDivElement>(null)
108
-
109
- // Clear selected fee token when feeOptions change (stale quote)
110
- // This resets the user's fee selection when they pick a different token
111
- // Use comprehensive key that includes token addresses and amounts
112
- const feeOptionsKey = feeOptions
113
- .map((opt) => `${opt.tokenAddress}-${opt.tokenSymbol}-${opt.amount}`)
114
- .join("|")
115
-
116
- // biome-ignore lint/correctness/useExhaustiveDependencies: setSelectedFeeToken is stable
117
- useEffect(() => {
118
- setSelectedFeeToken(null)
119
- }, [feeOptionsKey])
120
-
121
- // Enhance ALL fee options with formatted values and image URLs (including native gas)
122
- const enhancedFeeOptions: EnhancedFeeOptionType[] = feeOptions.map(
123
- (option) => ({
124
- ...option,
125
- amountFormatted: safeFormatAmountWithSymbol(option),
126
- amountUsdDisplay: safeFormatUsdDisplay(option),
127
- tokenImageUrl: getTokenImageUrl({
128
- chainId: option.chainId || chainId,
129
- contractAddress: option.tokenAddress,
130
- symbol: option.tokenSymbol,
131
- }),
132
- }),
133
- )
134
-
135
65
  // Find native gas option (zero address) for header display
136
- const nativeGasOption = enhancedFeeOptions.find((opt) =>
137
- isNativeTokenAddress(opt.tokenAddress),
66
+ const nativeGasOption = processedFeeOptions.find((opt) =>
67
+ isNativeToken(opt.tokenAddress),
138
68
  )
139
69
 
140
70
  // Find the currently selected option from enhancedFeeOptions for header display
141
- const getSelectedOption = (): EnhancedFeeOptionType | undefined => {
71
+ const getSelectedOption = (): ProcessedFeeOption | undefined => {
142
72
  if (
143
- selectedFeeToken === null ||
144
- isNativeTokenAddress(selectedFeeToken?.tokenAddress)
73
+ selectedFeeOption === null ||
74
+ isNativeToken(selectedFeeOption?.tokenAddress)
145
75
  ) {
146
76
  return nativeGasOption
147
77
  }
148
78
 
149
- if (!selectedFeeToken) {
79
+ if (!selectedFeeOption) {
150
80
  return undefined
151
81
  }
152
82
 
153
- const selectedAddress = normalizeAddress(selectedFeeToken.tokenAddress)
83
+ const selectedAddress = normalizeAddress(selectedFeeOption.tokenAddress)
154
84
 
155
- return enhancedFeeOptions.find(
85
+ return processedFeeOptions.find(
156
86
  (opt) => normalizeAddress(opt.tokenAddress) === selectedAddress,
157
87
  )
158
88
  }
@@ -162,16 +92,16 @@ export const FeeOptions: React.FC<FeeOptionsProps> = ({
162
92
  // Use nativeGasOption as fallback when selectedOption is null/undefined
163
93
  const displayOption = selectedOption || nativeGasOption
164
94
 
165
- const handleFeeTokenSelect = (option: EnhancedFeeOptionType) => {
95
+ const handleFeeOptionSelect = (option: FeeOption) => {
166
96
  // For native gas (zero/ETH address), set to null to trigger non-gasless flow
167
- if (isNativeTokenAddress(option.tokenAddress)) {
168
- setSelectedFeeToken(null)
97
+ if (isNativeToken(option.tokenAddress)) {
98
+ setSelectedFeeOption(null)
169
99
  logger.console.log(
170
100
  "[trails-sdk] [FEE-SELECT] Selected native gas fee option",
171
101
  )
172
102
  } else {
173
103
  // Use the option directly - it already has all required fields
174
- setSelectedFeeToken(option as APIFeeOption)
104
+ setSelectedFeeOption(option)
175
105
  logger.console.log(
176
106
  "[trails-sdk] [FEE-SELECT] Selected ERC20 fee option:",
177
107
  option,
@@ -236,19 +166,20 @@ export const FeeOptions: React.FC<FeeOptionsProps> = ({
236
166
  {isOpen && (
237
167
  <div className="mt-2 space-y-1">
238
168
  {/* All Fee Options (including native gas) */}
239
- {enhancedFeeOptions.map((option, index) => (
240
- <FeeOption
169
+ {processedFeeOptions.map((option, index) => (
170
+ <FeeOptionComponent
241
171
  key={`${option.tokenAddress}-${index}`}
242
172
  option={option}
243
173
  isSelected={
244
- isNativeTokenAddress(option.tokenAddress)
245
- ? selectedFeeToken === null ||
246
- isNativeTokenAddress(selectedFeeToken?.tokenAddress)
247
- : normalizeAddress(selectedFeeToken?.tokenAddress) ===
174
+ isNativeToken(option.tokenAddress)
175
+ ? selectedFeeOption === null ||
176
+ isNativeToken(selectedFeeOption?.tokenAddress)
177
+ : normalizeAddress(selectedFeeOption?.tokenAddress) ===
248
178
  normalizeAddress(option.tokenAddress)
249
179
  }
250
- onClick={() => handleFeeTokenSelect(option)}
180
+ onClick={() => handleFeeOptionSelect(option)}
251
181
  chainId={option.chainId || chainId}
182
+ originTokenInfo={originTokenInfo}
252
183
  />
253
184
  ))}
254
185
  </div>
@@ -1,7 +1,7 @@
1
1
  import type React from "react"
2
2
  import { getChainInfo } from "../../chains.js"
3
3
  import { TokenImage } from "./TokenImage.js"
4
- import { getTokenImageUrl } from "../../tokens.js"
4
+ import { useGetTokenImageUrl } from "../../tokens.js"
5
5
  import { zeroAddress } from "viem"
6
6
 
7
7
  interface NativeGasOptionProps {
@@ -21,6 +21,8 @@ export const NativeGasOption: React.FC<NativeGasOptionProps> = ({
21
21
  chainId,
22
22
  notEnoughBalance = false,
23
23
  }) => {
24
+ const { getTokenImageUrl } = useGetTokenImageUrl()
25
+
24
26
  // Helper function to get native symbol for a chain
25
27
  const getNativeSymbol = (chainId?: number) => {
26
28
  if (!chainId) return "ETH"
@@ -194,9 +194,9 @@ export const Pay: React.FC<PayProps> = ({
194
194
  isValidCustomToken,
195
195
  isSenderContractOnOrigin,
196
196
  isSenderContractOnDestination,
197
- feeOptions,
198
- selectedFeeToken,
199
- setSelectedFeeToken,
197
+ processedFeeOptions,
198
+ selectedFeeOption,
199
+ setSelectedFeeOption,
200
200
  } = useSendForm({
201
201
  account,
202
202
  toAmount: tokenAmountForBackend || toAmount, // Use the input amount as target amount for EXACT_OUTPUT
@@ -672,7 +672,7 @@ export const Pay: React.FC<PayProps> = ({
672
672
  {/* Payment Request Header */}
673
673
  <div className="space-y-1 trails-bg-secondary trails-border-radius-container p-3">
674
674
  <div className="flex justify-start">
675
- <div className="flex items-center font-medium trails-text-primary text-sm whitespace-nowrap overflow-hidden min-w-full">
675
+ <div className="flex items-center font-medium trails-text-primary text-sm flex-wrap whitespace-break-spaces break-all min-w-full">
676
676
  {payMessage}
677
677
  </div>
678
678
  </div>
@@ -1019,13 +1019,20 @@ export const Pay: React.FC<PayProps> = ({
1019
1019
  ) : null}
1020
1020
 
1021
1021
  {/* Fee Options */}
1022
- <FeeOptions
1023
- feeOptions={feeOptions || []}
1024
- selectedFeeToken={selectedFeeToken}
1025
- setSelectedFeeToken={(token) => setSelectedFeeToken(token as any)}
1026
- chainId={originToken?.chainId}
1027
- isRefetching={isLoadingQuote}
1028
- />
1022
+ {fundMethod !== "qr-code" && fundMethod !== "exchange" && (
1023
+ <FeeOptions
1024
+ processedFeeOptions={processedFeeOptions}
1025
+ selectedFeeOption={selectedFeeOption}
1026
+ setSelectedFeeOption={(feeOption) => setSelectedFeeOption(feeOption)}
1027
+ chainId={originToken?.chainId}
1028
+ isRefetching={isLoadingQuote}
1029
+ originTokenInfo={{
1030
+ originToken: originToken!,
1031
+ originTokenBalance: originToken?.balance ?? "0",
1032
+ originTokenAmount: tokenAmountForBackend ?? "0",
1033
+ }}
1034
+ />
1035
+ )}
1029
1036
 
1030
1037
  {/* Quote Details */}
1031
1038
  {prepareSendQuote && (
@@ -39,6 +39,7 @@ import { FeeOptions } from "./FeeOptions.js"
39
39
  import { generateAaveDepositCalldata } from "../../aave.js"
40
40
  import { generateMorphoDepositCalldata } from "../../morpho.js"
41
41
  import { TRAILS_ROUTER_PLACEHOLDER_AMOUNT } from "../../trailsRouter.js"
42
+ import { ScreenHeader } from "./ScreenHeader.js"
42
43
 
43
44
  interface PoolDepositProps {
44
45
  account?: Account
@@ -180,9 +181,9 @@ export const PoolDeposit: React.FC<PoolDepositProps> = ({
180
181
  isSubmitting,
181
182
  buttonText,
182
183
  isValidRecipient,
183
- feeOptions,
184
- selectedFeeToken,
185
- setSelectedFeeToken,
184
+ processedFeeOptions,
185
+ selectedFeeOption,
186
+ setSelectedFeeOption,
186
187
  } = useSendForm({
187
188
  account,
188
189
  toAmount: undefined,
@@ -320,6 +321,11 @@ export const PoolDeposit: React.FC<PoolDepositProps> = ({
320
321
  if (showOriginTokenSelector) {
321
322
  return (
322
323
  <div className="space-y-2">
324
+ <ScreenHeader
325
+ onBack={() => setShowOriginTokenSelector(false)}
326
+ headerContent="Select Token"
327
+ headerContentAlign="left"
328
+ />
323
329
  <TokenSelector
324
330
  onTokenSelect={handleOriginTokenSelect}
325
331
  onError={onError}
@@ -583,13 +589,20 @@ export const PoolDeposit: React.FC<PoolDepositProps> = ({
583
589
  ) : null}
584
590
 
585
591
  {/* Fee Options */}
586
- <FeeOptions
587
- feeOptions={feeOptions || []}
588
- selectedFeeToken={selectedFeeToken as any}
589
- setSelectedFeeToken={(token) => setSelectedFeeToken(token as any)}
590
- chainId={originToken?.chainId}
591
- isRefetching={isLoadingQuote}
592
- />
592
+ {fundMethod !== "qr-code" && fundMethod !== "exchange" && (
593
+ <FeeOptions
594
+ processedFeeOptions={processedFeeOptions}
595
+ selectedFeeOption={selectedFeeOption as any}
596
+ setSelectedFeeOption={(feeOption) => setSelectedFeeOption(feeOption)}
597
+ chainId={originToken?.chainId}
598
+ isRefetching={isLoadingQuote}
599
+ originTokenInfo={{
600
+ originToken: originToken!,
601
+ originTokenBalance: originToken?.balance ?? "0",
602
+ originTokenAmount: amount ?? "0",
603
+ }}
604
+ />
605
+ )}
593
606
 
594
607
  {/* Quote Details */}
595
608
  {prepareSendQuote && (