0xtrails 0.2.5 → 0.3.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 (267) hide show
  1. package/dist/aave.d.ts +2 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/abortController.d.ts +8 -0
  4. package/dist/abortController.d.ts.map +1 -0
  5. package/dist/{ccip-CXlshvBY.js → ccip-BMB3uDZt.js} +1 -1
  6. package/dist/config.d.ts +0 -5
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/constants.d.ts +4 -4
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/error.d.ts +4 -1
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/fees.d.ts +19 -0
  13. package/dist/fees.d.ts.map +1 -0
  14. package/dist/{index-_QuyGrjU.js → index-QXPUrZVv.js} +48719 -50852
  15. package/dist/index.d.ts +9 -8
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +811 -784
  18. package/dist/intentReceiptMonitor.d.ts +24 -0
  19. package/dist/intentReceiptMonitor.d.ts.map +1 -0
  20. package/dist/intentReceiptPoller.d.ts +69 -0
  21. package/dist/intentReceiptPoller.d.ts.map +1 -0
  22. package/dist/intents.d.ts +15 -11
  23. package/dist/intents.d.ts.map +1 -1
  24. package/dist/morpho.d.ts +6 -5
  25. package/dist/morpho.d.ts.map +1 -1
  26. package/dist/mutations.d.ts +16 -0
  27. package/dist/mutations.d.ts.map +1 -0
  28. package/dist/preconditions.d.ts +5 -4
  29. package/dist/preconditions.d.ts.map +1 -1
  30. package/dist/prepareSend.d.ts +7 -258
  31. package/dist/prepareSend.d.ts.map +1 -1
  32. package/dist/prices.d.ts +9 -6
  33. package/dist/prices.d.ts.map +1 -1
  34. package/dist/sequenceWallet.d.ts +3 -16
  35. package/dist/sequenceWallet.d.ts.map +1 -1
  36. package/dist/tokenBalances.d.ts +17 -13
  37. package/dist/tokenBalances.d.ts.map +1 -1
  38. package/dist/trails.d.ts +24 -40
  39. package/dist/trails.d.ts.map +1 -1
  40. package/dist/transactionIntent/constants.d.ts +7 -0
  41. package/dist/transactionIntent/constants.d.ts.map +1 -0
  42. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +44 -0
  43. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -0
  44. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +30 -0
  45. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -0
  46. package/dist/transactionIntent/deposits/index.d.ts +4 -0
  47. package/dist/transactionIntent/deposits/index.d.ts.map +1 -0
  48. package/dist/transactionIntent/deposits/standardDeposit.d.ts +30 -0
  49. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -0
  50. package/dist/transactionIntent/execution/index.d.ts +2 -0
  51. package/dist/transactionIntent/execution/index.d.ts.map +1 -0
  52. package/dist/transactionIntent/execution/transactionState.d.ts +5 -0
  53. package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -0
  54. package/dist/transactionIntent/handlers/crossChain.d.ts +82 -0
  55. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -0
  56. package/dist/transactionIntent/handlers/index.d.ts +4 -0
  57. package/dist/transactionIntent/handlers/index.d.ts.map +1 -0
  58. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts +62 -0
  59. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts.map +1 -0
  60. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +72 -0
  61. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -0
  62. package/dist/transactionIntent/index.d.ts +9 -0
  63. package/dist/transactionIntent/index.d.ts.map +1 -0
  64. package/dist/transactionIntent/quote/feeExtractors.d.ts +17 -0
  65. package/dist/transactionIntent/quote/feeExtractors.d.ts.map +1 -0
  66. package/dist/transactionIntent/quote/index.d.ts +4 -0
  67. package/dist/transactionIntent/quote/index.d.ts.map +1 -0
  68. package/dist/transactionIntent/quote/normalizeQuote.d.ts +34 -0
  69. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -0
  70. package/dist/transactionIntent/quote/quoteHelpers.d.ts +5 -0
  71. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -0
  72. package/dist/transactionIntent/types.d.ts +131 -0
  73. package/dist/transactionIntent/types.d.ts.map +1 -0
  74. package/dist/transactionIntent/utils/balanceChecker.d.ts +18 -0
  75. package/dist/transactionIntent/utils/balanceChecker.d.ts.map +1 -0
  76. package/dist/transactionIntent/utils/index.d.ts +4 -0
  77. package/dist/transactionIntent/utils/index.d.ts.map +1 -0
  78. package/dist/transactionIntent/utils/lifiHelpers.d.ts +10 -0
  79. package/dist/transactionIntent/utils/lifiHelpers.d.ts.map +1 -0
  80. package/dist/transactionIntent/utils/testnetHelpers.d.ts +3 -0
  81. package/dist/transactionIntent/utils/testnetHelpers.d.ts.map +1 -0
  82. package/dist/transactionIntent/validators.d.ts +6 -0
  83. package/dist/transactionIntent/validators.d.ts.map +1 -0
  84. package/dist/transactions.d.ts +6 -3
  85. package/dist/transactions.d.ts.map +1 -1
  86. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
  87. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
  88. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  89. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  90. package/dist/widget/components/ClassicSwap.d.ts +2 -3
  91. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  92. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  93. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  94. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  95. package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
  96. package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
  97. package/dist/widget/components/DynamicSizeInputField.d.ts +13 -0
  98. package/dist/widget/components/DynamicSizeInputField.d.ts.map +1 -0
  99. package/dist/widget/components/Earn.d.ts +2 -3
  100. package/dist/widget/components/Earn.d.ts.map +1 -1
  101. package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
  102. package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
  103. package/dist/widget/components/FeeBreakdown.d.ts +9 -0
  104. package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
  105. package/dist/widget/components/FeeOptions.d.ts +5 -13
  106. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  107. package/dist/widget/components/Fund.d.ts +2 -3
  108. package/dist/widget/components/Fund.d.ts.map +1 -1
  109. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  110. package/dist/widget/components/FundSwap.d.ts +2 -3
  111. package/dist/widget/components/FundSwap.d.ts.map +1 -1
  112. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -1
  113. package/dist/widget/components/Identicon.d.ts.map +1 -1
  114. package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
  115. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  116. package/dist/widget/components/Modal.d.ts.map +1 -1
  117. package/dist/widget/components/Pay.d.ts +2 -3
  118. package/dist/widget/components/Pay.d.ts.map +1 -1
  119. package/dist/widget/components/PoolDeposit.d.ts +3 -3
  120. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  121. package/dist/widget/components/PoolWithdraw.d.ts +3 -20
  122. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -1
  123. package/dist/widget/components/QuoteDetails.d.ts +2 -0
  124. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  125. package/dist/widget/components/Receipt.d.ts.map +1 -1
  126. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
  127. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  128. package/dist/widget/components/Swap.d.ts +2 -3
  129. package/dist/widget/components/Swap.d.ts.map +1 -1
  130. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  131. package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
  132. package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
  133. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  134. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -1
  135. package/dist/widget/components/Tooltip.d.ts +9 -0
  136. package/dist/widget/components/Tooltip.d.ts.map +1 -0
  137. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  138. package/dist/widget/components/WaasFeeOptions.d.ts +1 -0
  139. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -1
  140. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  141. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  142. package/dist/widget/css/compiled.css +2 -2
  143. package/dist/widget/hooks/useCheckout.d.ts +17 -4
  144. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  145. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  146. package/dist/widget/hooks/useQuote.d.ts +82 -0
  147. package/dist/widget/hooks/useQuote.d.ts.map +1 -0
  148. package/dist/widget/hooks/useSelectedFeeToken.d.ts +1 -0
  149. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -1
  150. package/dist/widget/hooks/useSendForm.d.ts +5 -6
  151. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  152. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  153. package/dist/widget/hooks/useWalletConnectionContext.d.ts +25 -0
  154. package/dist/widget/hooks/useWalletConnectionContext.d.ts.map +1 -0
  155. package/dist/widget/index.js +2 -2
  156. package/dist/widget/widget.d.ts +17 -7
  157. package/dist/widget/widget.d.ts.map +1 -1
  158. package/package.json +19 -21
  159. package/src/aave.ts +54 -1
  160. package/src/abortController.ts +35 -0
  161. package/src/config.ts +57 -58
  162. package/src/constants.ts +11 -9
  163. package/src/error.ts +21 -3
  164. package/src/fees.ts +210 -0
  165. package/src/index.ts +35 -13
  166. package/src/intentReceiptMonitor.ts +102 -0
  167. package/src/intentReceiptPoller.ts +299 -0
  168. package/src/intents.ts +205 -171
  169. package/src/morpho.ts +58 -9
  170. package/src/mutations.ts +129 -0
  171. package/src/preconditions.ts +16 -21
  172. package/src/prepareSend.ts +92 -4699
  173. package/src/prices.ts +26 -22
  174. package/src/relaySdk.ts +2 -2
  175. package/src/sequenceWallet.ts +6 -73
  176. package/src/tokenBalances.ts +175 -69
  177. package/src/trails.ts +230 -722
  178. package/src/transactionIntent/constants.ts +11 -0
  179. package/src/transactionIntent/deposits/depositOrchestrator.ts +210 -0
  180. package/src/transactionIntent/deposits/gaslessDeposit.ts +588 -0
  181. package/src/transactionIntent/deposits/index.ts +3 -0
  182. package/src/transactionIntent/deposits/standardDeposit.ts +379 -0
  183. package/src/transactionIntent/execution/index.ts +1 -0
  184. package/src/transactionIntent/execution/transactionState.ts +35 -0
  185. package/src/transactionIntent/handlers/crossChain.ts +1707 -0
  186. package/src/transactionIntent/handlers/index.ts +3 -0
  187. package/src/transactionIntent/handlers/sameChainDifferentToken.ts +323 -0
  188. package/src/transactionIntent/handlers/sameChainSameToken.ts +712 -0
  189. package/src/transactionIntent/index.ts +9 -0
  190. package/src/transactionIntent/quote/feeExtractors.ts +81 -0
  191. package/src/transactionIntent/quote/index.ts +3 -0
  192. package/src/transactionIntent/quote/normalizeQuote.ts +367 -0
  193. package/src/transactionIntent/quote/quoteHelpers.ts +53 -0
  194. package/src/transactionIntent/types.ts +157 -0
  195. package/src/transactionIntent/utils/balanceChecker.ts +96 -0
  196. package/src/transactionIntent/utils/index.ts +3 -0
  197. package/src/transactionIntent/utils/lifiHelpers.ts +68 -0
  198. package/src/transactionIntent/utils/testnetHelpers.ts +10 -0
  199. package/src/transactionIntent/validators.ts +57 -0
  200. package/src/transactions.ts +98 -71
  201. package/src/widget/compiled.css +2 -2
  202. package/src/widget/components/AccountIntentTransactionHistory.tsx +36 -36
  203. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
  204. package/src/widget/components/AccountSettings.tsx +70 -41
  205. package/src/widget/components/ChainFilterDropdown.tsx +24 -3
  206. package/src/widget/components/ClassicSwap.tsx +44 -107
  207. package/src/widget/components/ConfigDisplay.tsx +0 -11
  208. package/src/widget/components/ConnectWallet.tsx +4 -1
  209. package/src/widget/components/ConnectedWallets.tsx +51 -25
  210. package/src/widget/components/DynamicInputStyles.tsx +76 -0
  211. package/src/widget/components/DynamicSizeInputField.tsx +109 -0
  212. package/src/widget/components/Earn.tsx +34 -45
  213. package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
  214. package/src/widget/components/FeeBreakdown.tsx +155 -0
  215. package/src/widget/components/FeeOption.tsx +2 -2
  216. package/src/widget/components/FeeOptions.tsx +151 -112
  217. package/src/widget/components/Fund.tsx +10 -29
  218. package/src/widget/components/FundMethods.tsx +4 -3
  219. package/src/widget/components/FundSwap.tsx +2 -3
  220. package/src/widget/components/FundingMethodSelectorButton.tsx +24 -14
  221. package/src/widget/components/Identicon.tsx +164 -95
  222. package/src/widget/components/MeshConnectExchanges.tsx +2 -15
  223. package/src/widget/components/Modal.tsx +0 -12
  224. package/src/widget/components/Pay.tsx +72 -75
  225. package/src/widget/components/PoolDeposit.tsx +221 -242
  226. package/src/widget/components/PoolWithdraw.tsx +347 -469
  227. package/src/widget/components/PriceImpactWarning.tsx +1 -1
  228. package/src/widget/components/QuoteDetails.tsx +906 -484
  229. package/src/widget/components/Receipt.tsx +16 -2
  230. package/src/widget/components/RecipientSelectorButton.tsx +7 -5
  231. package/src/widget/components/Recipients.tsx +1 -1
  232. package/src/widget/components/ScreenHeader.tsx +60 -36
  233. package/src/widget/components/Swap.tsx +2 -3
  234. package/src/widget/components/ThemeProvider.tsx +2 -1
  235. package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
  236. package/src/widget/components/TokenImage.tsx +1 -1
  237. package/src/widget/components/TokenSelector.tsx +62 -53
  238. package/src/widget/components/TokenSelectorButton.tsx +38 -15
  239. package/src/widget/components/Tooltip.tsx +51 -0
  240. package/src/widget/components/TransferPendingVertical.tsx +12 -8
  241. package/src/widget/components/WaasFeeOptions.tsx +139 -4
  242. package/src/widget/components/WalletConfirmation.tsx +23 -13
  243. package/src/widget/components/WalletConnect.tsx +93 -29
  244. package/src/widget/hooks/useAmountUsd.ts +9 -9
  245. package/src/widget/hooks/useCheckout.ts +97 -9
  246. package/src/widget/hooks/useDefaultTokenSelection.tsx +27 -21
  247. package/src/widget/hooks/useQuote.ts +466 -0
  248. package/src/widget/hooks/useSelectedFeeToken.tsx +32 -37
  249. package/src/widget/hooks/useSendForm.ts +45 -51
  250. package/src/widget/hooks/useTokenList.ts +34 -26
  251. package/src/widget/hooks/useWalletConnectionContext.tsx +128 -0
  252. package/src/widget/widget.tsx +365 -390
  253. package/dist/apiClient.d.ts +0 -9
  254. package/dist/apiClient.d.ts.map +0 -1
  255. package/dist/intentEntrypoint.d.ts +0 -114
  256. package/dist/intentEntrypoint.d.ts.map +0 -1
  257. package/dist/metaTxnMonitor.d.ts +0 -15
  258. package/dist/metaTxnMonitor.d.ts.map +0 -1
  259. package/dist/metaTxns.d.ts +0 -11
  260. package/dist/metaTxns.d.ts.map +0 -1
  261. package/dist/relayer.d.ts +0 -43
  262. package/dist/relayer.d.ts.map +0 -1
  263. package/src/apiClient.ts +0 -35
  264. package/src/intentEntrypoint.ts +0 -203
  265. package/src/metaTxnMonitor.ts +0 -171
  266. package/src/metaTxns.ts +0 -45
  267. package/src/relayer.ts +0 -289
@@ -0,0 +1,155 @@
1
+ import type React from "react"
2
+ import { useState } from "react"
3
+ import { InfoIcon } from "@0xsequence/design-system"
4
+ import { Tooltip } from "./Tooltip.js"
5
+ import { TokenImage } from "./TokenImage.js"
6
+ import type { TrailsFeeBreakdown, FeeItem } from "../../fees.js"
7
+
8
+ interface FeeBreakdownProps {
9
+ feeBreakdown: TrailsFeeBreakdown
10
+ children?: React.ReactNode
11
+ }
12
+
13
+ interface FeeRowProps {
14
+ label: string
15
+ feeItem: FeeItem
16
+ tooltip?: string
17
+ }
18
+
19
+ const FeeRow: React.FC<FeeRowProps> = ({ label, feeItem, tooltip }) => (
20
+ <div className="flex justify-between items-center py-1 items-start">
21
+ <span className="text-xs text-gray-600 dark:text-gray-400 flex items-center gap-1">
22
+ {label}
23
+ {tooltip && (
24
+ <Tooltip message={tooltip}>
25
+ <InfoIcon className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
26
+ </Tooltip>
27
+ )}
28
+ </span>
29
+ <div className="text-right">
30
+ <div className="font-medium text-xs text-gray-900 dark:text-white flex items-center gap-1 justify-end">
31
+ <TokenImage
32
+ imageUrl=""
33
+ symbol={feeItem.tokenSymbol}
34
+ chainId={feeItem.chainId}
35
+ contractAddress={feeItem.tokenAddress}
36
+ size={16}
37
+ />
38
+ {feeItem.amount} {feeItem.tokenSymbol}
39
+ </div>
40
+ <div className="text-xs text-gray-500 dark:text-gray-400">
41
+ ≈ {feeItem.usdValue}
42
+ </div>
43
+ </div>
44
+ </div>
45
+ )
46
+
47
+ export const FeeBreakdown: React.FC<FeeBreakdownProps> = ({
48
+ feeBreakdown,
49
+ children,
50
+ }) => {
51
+ const [isExpanded, setIsExpanded] = useState(false)
52
+
53
+ // Don't render at all if there are no fees
54
+ const hasAnyFees =
55
+ feeBreakdown.originRelayFee ||
56
+ feeBreakdown.destinationRelayFee ||
57
+ feeBreakdown.providerFee ||
58
+ feeBreakdown.trailsFee
59
+
60
+ if (!hasAnyFees) {
61
+ return null
62
+ }
63
+
64
+ return (
65
+ <div className="space-y-2">
66
+ {/* Accordion header */}
67
+ <button
68
+ type="button"
69
+ onClick={() => setIsExpanded(!isExpanded)}
70
+ className="w-full flex items-center justify-between py-1 text-xs transition-colors duration-200 text-gray-600 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 cursor-pointer"
71
+ aria-label={isExpanded ? "Hide fee breakdown" : "Show fee breakdown"}
72
+ >
73
+ <span className="flex items-center gap-1 w-full">
74
+ {children ? (
75
+ children
76
+ ) : (
77
+ <Tooltip message="Detailed breakdown of the Trails platform fees">
78
+ <InfoIcon className="w-3 h-3 text-gray-500 dark:text-gray-400 cursor-pointer" />
79
+ </Tooltip>
80
+ )}
81
+ </span>
82
+ <svg
83
+ className={`w-3 h-3 transition-transform duration-300 ease-out ml-2 ${
84
+ isExpanded ? "rotate-180" : ""
85
+ }`}
86
+ fill="none"
87
+ stroke="currentColor"
88
+ viewBox="0 0 24 24"
89
+ aria-hidden="true"
90
+ >
91
+ <path
92
+ strokeLinecap="round"
93
+ strokeLinejoin="round"
94
+ strokeWidth={2}
95
+ d="M19 9l-7 7-7-7"
96
+ />
97
+ </svg>
98
+ </button>
99
+
100
+ {/* Collapsible content */}
101
+ <div
102
+ className={`overflow-hidden transition-all duration-300 ease-out ${
103
+ isExpanded ? "max-h-96 opacity-100" : "max-h-0 opacity-0"
104
+ }`}
105
+ >
106
+ <div className="space-y-1 pl-2">
107
+ {feeBreakdown.originRelayFee && (
108
+ <FeeRow
109
+ label="Origin Relay Fee"
110
+ feeItem={feeBreakdown.originRelayFee}
111
+ tooltip="Fee for relaying the origin chain transaction"
112
+ />
113
+ )}
114
+ {feeBreakdown.destinationRelayFee && (
115
+ <FeeRow
116
+ label="Destination Relay Fee"
117
+ feeItem={feeBreakdown.destinationRelayFee}
118
+ tooltip="Fee for relaying the destination chain transaction"
119
+ />
120
+ )}
121
+ {feeBreakdown.providerFee && (
122
+ <FeeRow
123
+ label="Liquidity Provider Fee"
124
+ feeItem={feeBreakdown.providerFee}
125
+ tooltip="Fee charged by the bridge/swap liquidity provider for executing the transaction"
126
+ />
127
+ )}
128
+ {feeBreakdown.trailsFee && (
129
+ <FeeRow
130
+ label="Trails Platform Fee"
131
+ feeItem={feeBreakdown.trailsFee}
132
+ tooltip="Platform fee for using the Trails service"
133
+ />
134
+ )}
135
+
136
+ {/* Total line - only show if we have a total value */}
137
+ {/* {feeBreakdown.totalUsdValue && (
138
+ <div className="flex justify-between items-center py-1 pt-2 border-t border-gray-200 dark:border-gray-700">
139
+ <span className="text-xs font-medium text-gray-700 dark:text-gray-300">
140
+ Total Fees
141
+ </span>
142
+ <div className="text-right">
143
+ <div className="font-medium text-xs text-gray-900 dark:text-white">
144
+ ~{feeBreakdown.totalUsdValue}
145
+ </div>
146
+ </div>
147
+ </div>
148
+ )} */}
149
+ </div>
150
+ </div>
151
+ </div>
152
+ )
153
+ }
154
+
155
+ export default FeeBreakdown
@@ -47,7 +47,7 @@ export const FeeOption: React.FC<FeeOptionProps> = ({
47
47
  chainId={option.chainId || chainId}
48
48
  size={16}
49
49
  />
50
- <div className="text-left">
50
+ <div className="ml-2 text-left">
51
51
  <div
52
52
  className={`text-xs font-medium ${
53
53
  option.notEnoughBalance
@@ -68,7 +68,7 @@ export const FeeOption: React.FC<FeeOptionProps> = ({
68
68
  : "trails-text-primary"
69
69
  }`}
70
70
  >
71
- {option.amountUsdDisplay}
71
+ {option.amountUsdDisplay}
72
72
  </div>
73
73
  </div>
74
74
  </button>
@@ -1,34 +1,76 @@
1
1
  import { ChevronDown } from "lucide-react"
2
2
  import type React from "react"
3
- import { useRef, useState } from "react"
3
+ import { useRef, useState, useEffect } from "react"
4
4
  import { formatUsdAmountDisplay, formatRawAmount } from "../../tokenBalances.js"
5
5
  import {
6
6
  FeeOption,
7
7
  type FeeOption as EnhancedFeeOptionType,
8
8
  } from "./FeeOption.js"
9
9
  import { logger } from "../../logger.js"
10
- import { getChainInfo } from "../../chains.js"
11
- import { TokenImage } from "./TokenImage.js"
12
10
  import { getTokenImageUrl } from "../../tokens.js"
13
- import { zeroAddress } from "viem"
14
-
15
- // Original FeeOption type from useSendForm hook
16
- type OriginalFeeOption = {
17
- tokenAddress: string
18
- tokenSymbol: string
19
- tokenDecimals: number
20
- amount: string
21
- amountUSD: number
22
- notEnoughBalance?: boolean
23
- tokenImageUrl?: string
24
- chainId?: number
11
+ import { TokenImage } from "./TokenImage.js"
12
+ import { ethAddress, zeroAddress } from "viem"
13
+ import type { FeeOption as APIFeeOption } from "../../widget/hooks/useSelectedFeeToken.js"
14
+
15
+ const ZERO_ADDRESS = zeroAddress.toLowerCase()
16
+ const ETH_ADDRESS = ethAddress.toLowerCase()
17
+
18
+ const normalizeAddress = (address?: string | null): string =>
19
+ (address ?? "").toLowerCase()
20
+
21
+ const isNativeTokenAddress = (address?: string | null): boolean => {
22
+ const normalized = normalizeAddress(address)
23
+ return normalized === ZERO_ADDRESS || normalized === ETH_ADDRESS
24
+ }
25
+
26
+ const safeFormatAmountWithSymbol = (option: APIFeeOption): string => {
27
+ const tokenDecimals =
28
+ typeof option.tokenDecimals === "number" &&
29
+ option.tokenDecimals > 0 &&
30
+ option.tokenDecimals <= 18
31
+ ? option.tokenDecimals
32
+ : isNativeTokenAddress(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
+ const safeFormatUsdDisplay = (option: APIFeeOption): string => {
63
+ const usdValue =
64
+ (option as { amountUsd?: number }).amountUsd ?? option.amountUSD ?? 0
65
+ return formatUsdAmountDisplay(usdValue)
25
66
  }
26
67
 
27
68
  interface FeeOptionsProps {
28
- feeOptions: OriginalFeeOption[]
29
- selectedFeeToken: OriginalFeeOption | null
30
- setSelectedFeeToken: (token: OriginalFeeOption | null) => void
69
+ feeOptions: APIFeeOption[]
70
+ selectedFeeToken: APIFeeOption | null
71
+ setSelectedFeeToken: (token: APIFeeOption | null) => void
31
72
  chainId?: number
73
+ isRefetching?: boolean // When true, the fee quote is stale and being refreshed, so hide the component
32
74
  }
33
75
 
34
76
  export const FeeOptions: React.FC<FeeOptionsProps> = ({
@@ -36,23 +78,53 @@ export const FeeOptions: React.FC<FeeOptionsProps> = ({
36
78
  selectedFeeToken,
37
79
  setSelectedFeeToken,
38
80
  chainId,
81
+ isRefetching,
39
82
  }) => {
40
- const [isOpen, setIsOpen] = useState(false)
41
- const accordionRef = useRef<HTMLDivElement>(null)
83
+ // Early returns BEFORE any hooks - this prevents "Rendered more hooks" error
84
+ // Hide component when fee quote is stale and being refreshed
85
+ if (isRefetching) {
86
+ return null
87
+ }
42
88
 
43
89
  // Don't render if no fee options available
44
90
  if (!feeOptions || feeOptions.length === 0) {
45
91
  return null
46
92
  }
47
93
 
94
+ // Check if there are non-native options before calling hooks
95
+ const hasNonNativeOptions = feeOptions.some(
96
+ (opt) => !isNativeTokenAddress(opt.tokenAddress),
97
+ )
98
+
99
+ // Don't render if only native token is available
100
+ if (!hasNonNativeOptions) {
101
+ return null
102
+ }
103
+
104
+ // NOW we can safely call hooks after all early returns
105
+ const [isOpen, setIsOpen] = useState(false)
106
+ const accordionRef = useRef<HTMLDivElement>(null)
107
+
108
+ // Clear selected fee token when feeOptions change (stale quote)
109
+ // This resets the user's fee selection when they pick a different token
110
+ // Use comprehensive key that includes token addresses and amounts
111
+ const feeOptionsKey = feeOptions
112
+ .map((opt) => `${opt.tokenAddress}-${opt.tokenSymbol}-${opt.amount}`)
113
+ .join("|")
114
+
115
+ // biome-ignore lint/correctness/useExhaustiveDependencies: setSelectedFeeToken is stable
116
+ useEffect(() => {
117
+ setSelectedFeeToken(null)
118
+ }, [feeOptionsKey])
119
+
48
120
  // Enhance ALL fee options with formatted values and image URLs (including native gas)
49
121
  const enhancedFeeOptions: EnhancedFeeOptionType[] = feeOptions.map(
50
122
  (option) => ({
51
123
  ...option,
52
- amountFormatted: `${formatRawAmount(option.amount, option.tokenDecimals)} ${option.tokenSymbol}`,
53
- amountUsdDisplay: formatUsdAmountDisplay(option.amountUSD),
124
+ amountFormatted: safeFormatAmountWithSymbol(option),
125
+ amountUsdDisplay: safeFormatUsdDisplay(option),
54
126
  tokenImageUrl: getTokenImageUrl({
55
- chainId: option.chainId || chainId, // Use fee option's chain ID, fallback to prop
127
+ chainId: option.chainId || chainId,
56
128
  contractAddress: option.tokenAddress,
57
129
  symbol: option.tokenSymbol,
58
130
  }),
@@ -60,92 +132,54 @@ export const FeeOptions: React.FC<FeeOptionsProps> = ({
60
132
  )
61
133
 
62
134
  // Find native gas option (zero address) for header display
63
- const nativeGasOption = enhancedFeeOptions.find(
64
- (opt) => opt.tokenAddress.toLowerCase() === zeroAddress.toLowerCase(),
135
+ const nativeGasOption = enhancedFeeOptions.find((opt) =>
136
+ isNativeTokenAddress(opt.tokenAddress),
65
137
  )
66
138
 
67
- const handleFeeTokenSelect = (option: EnhancedFeeOptionType) => {
68
- // Convert back to original format for the parent component
69
- const originalOption: OriginalFeeOption = {
70
- tokenAddress: option.tokenAddress,
71
- tokenSymbol: option.tokenSymbol,
72
- tokenDecimals: option.tokenDecimals,
73
- amount: option.amount,
74
- amountUSD: option.amountUSD,
139
+ // Find the currently selected option from enhancedFeeOptions for header display
140
+ const getSelectedOption = (): EnhancedFeeOptionType | undefined => {
141
+ if (
142
+ selectedFeeToken === null ||
143
+ isNativeTokenAddress(selectedFeeToken?.tokenAddress)
144
+ ) {
145
+ return nativeGasOption
146
+ }
147
+
148
+ if (!selectedFeeToken) {
149
+ return undefined
75
150
  }
76
151
 
77
- // For native gas (zero address), set to null to trigger non-gasless flow
78
- if (option.tokenAddress.toLowerCase() === zeroAddress.toLowerCase()) {
152
+ const selectedAddress = normalizeAddress(selectedFeeToken.tokenAddress)
153
+
154
+ return enhancedFeeOptions.find(
155
+ (opt) => normalizeAddress(opt.tokenAddress) === selectedAddress,
156
+ )
157
+ }
158
+
159
+ const selectedOption = getSelectedOption()
160
+
161
+ // Use nativeGasOption as fallback when selectedOption is null/undefined
162
+ const displayOption = selectedOption || nativeGasOption
163
+
164
+ const handleFeeTokenSelect = (option: EnhancedFeeOptionType) => {
165
+ // For native gas (zero/ETH address), set to null to trigger non-gasless flow
166
+ if (isNativeTokenAddress(option.tokenAddress)) {
79
167
  setSelectedFeeToken(null)
80
168
  logger.console.log(
81
169
  "[trails-sdk] [FEE-SELECT] Selected native gas fee option",
82
170
  )
83
171
  } else {
84
- setSelectedFeeToken(originalOption)
172
+ // Use the option directly - it already has all required fields
173
+ setSelectedFeeToken(option as APIFeeOption)
85
174
  logger.console.log(
86
175
  "[trails-sdk] [FEE-SELECT] Selected ERC20 fee option:",
87
- originalOption,
176
+ option,
88
177
  )
89
178
  }
90
179
 
91
180
  setIsOpen(false)
92
181
  }
93
182
 
94
- // Get display text and image for selected option
95
- const getSelectedDisplayText = () => {
96
- if (
97
- selectedFeeToken === null ||
98
- selectedFeeToken?.tokenAddress?.toLowerCase() ===
99
- zeroAddress.toLowerCase()
100
- ) {
101
- // Native gas selected - use native gas option data
102
- const nativeSymbol =
103
- nativeGasOption?.tokenSymbol ||
104
- (chainId ? getNativeSymbol(chainId) : "ETH")
105
- const nativeImageUrl = getTokenImageUrl({
106
- chainId,
107
- contractAddress: zeroAddress,
108
- symbol: nativeSymbol,
109
- })
110
- const amountDisplay = nativeGasOption
111
- ? `${formatRawAmount(nativeGasOption.amount, nativeGasOption.tokenDecimals)} ${nativeGasOption.tokenSymbol}`
112
- : ""
113
- const usdDisplay = nativeGasOption
114
- ? formatUsdAmountDisplay(nativeGasOption.amountUSD)
115
- : ""
116
- return {
117
- title: nativeSymbol,
118
- amountDisplay,
119
- usdDisplay,
120
- symbol: nativeSymbol,
121
- imageUrl: nativeImageUrl,
122
- }
123
- } else {
124
- const tokenImageUrl = getTokenImageUrl({
125
- chainId: selectedFeeToken.chainId || chainId, // Use fee token's chain ID, fallback to prop
126
- contractAddress: selectedFeeToken.tokenAddress,
127
- symbol: selectedFeeToken.tokenSymbol,
128
- })
129
- const amountDisplay = `${formatRawAmount(selectedFeeToken.amount, selectedFeeToken.tokenDecimals)} ${selectedFeeToken.tokenSymbol}`
130
- const usdDisplay = formatUsdAmountDisplay(selectedFeeToken.amountUSD)
131
- return {
132
- title: selectedFeeToken.tokenSymbol,
133
- amountDisplay,
134
- usdDisplay,
135
- symbol: selectedFeeToken.tokenSymbol,
136
- imageUrl: tokenImageUrl,
137
- }
138
- }
139
- }
140
-
141
- // Helper function to get native symbol for a chain
142
- const getNativeSymbol = (chainId: number) => {
143
- const chainInfo = getChainInfo(chainId)
144
- return chainInfo?.nativeCurrency.symbol || "ETH"
145
- }
146
-
147
- const selectedDisplay = getSelectedDisplayText()
148
-
149
183
  return (
150
184
  <div className="space-y-1" ref={accordionRef}>
151
185
  <div className="p-2">
@@ -165,24 +199,30 @@ export const FeeOptions: React.FC<FeeOptionsProps> = ({
165
199
  className="w-full flex items-center justify-between p-1.5 trails-border-radius-input border trails-border-primary hover:trails-hover-bg transition-colors cursor-pointer"
166
200
  >
167
201
  <div className="flex items-center space-x-2">
168
- <TokenImage
169
- imageUrl={selectedDisplay.imageUrl}
170
- symbol={selectedDisplay.symbol}
171
- chainId={selectedFeeToken?.chainId || chainId}
172
- size={16}
173
- />
174
- <div className="text-left">
175
- <div className="text-xs font-medium trails-text-primary">
176
- {selectedDisplay.amountDisplay}
177
- </div>
178
- </div>
202
+ {displayOption && (
203
+ <>
204
+ <TokenImage
205
+ imageUrl={displayOption.tokenImageUrl}
206
+ symbol={displayOption.tokenSymbol}
207
+ chainId={displayOption.chainId || chainId}
208
+ size={16}
209
+ />
210
+ <div className="ml-2 text-left">
211
+ <div className="text-xs font-medium trails-text-primary">
212
+ {displayOption.amountFormatted}
213
+ </div>
214
+ </div>
215
+ </>
216
+ )}
179
217
  </div>
180
218
  <div className="flex items-center space-x-2">
181
- <div className="text-right">
182
- <div className="text-xs font-medium trails-text-primary">
183
- {selectedDisplay.usdDisplay}
219
+ {displayOption && (
220
+ <div className="text-right">
221
+ <div className="text-xs font-medium trails-text-primary">
222
+ ≈ {displayOption.amountUsdDisplay}
223
+ </div>
184
224
  </div>
185
- </div>
225
+ )}
186
226
  <ChevronDown
187
227
  className={`w-3 h-3 trails-text-muted transition-transform ${
188
228
  isOpen ? "transform rotate-180" : ""
@@ -200,12 +240,11 @@ export const FeeOptions: React.FC<FeeOptionsProps> = ({
200
240
  key={`${option.tokenAddress}-${index}`}
201
241
  option={option}
202
242
  isSelected={
203
- option.tokenAddress.toLowerCase() ===
204
- zeroAddress.toLowerCase()
243
+ isNativeTokenAddress(option.tokenAddress)
205
244
  ? selectedFeeToken === null ||
206
- selectedFeeToken?.tokenAddress?.toLowerCase() ===
207
- zeroAddress.toLowerCase()
208
- : selectedFeeToken?.tokenAddress === option.tokenAddress
245
+ isNativeTokenAddress(selectedFeeToken?.tokenAddress)
246
+ : normalizeAddress(selectedFeeToken?.tokenAddress) ===
247
+ normalizeAddress(option.tokenAddress)
209
248
  }
210
249
  onClick={() => handleFeeTokenSelect(option)}
211
250
  chainId={option.chainId || chainId}
@@ -32,11 +32,12 @@ import { logger } from "../../logger.js"
32
32
  import { RefundWarning } from "./RefundWarning.js"
33
33
  import { PercentageMaxButtons } from "./PercentageMaxButtons.js"
34
34
  import { TokenSelectorButton } from "./TokenSelectorButton.js"
35
+ import { useDynamicInputStyles } from "./DynamicInputStyles.js"
35
36
 
36
37
  interface FundProps {
37
38
  onBack?: () => void
38
- account: Account
39
- walletClient: WalletClient
39
+ account?: Account
40
+ walletClient?: WalletClient
40
41
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
41
42
  onError: (error: Error | string | null) => void
42
43
  onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
@@ -44,7 +45,6 @@ interface FundProps {
44
45
  onComplete: (result: OnCompleteProps) => void
45
46
  onSend: (amount: string, recipient: string) => void
46
47
  paymasterUrls?: Array<{ chainId: number; url: string }>
47
- gasless?: boolean
48
48
  isSequenceWallet?: boolean
49
49
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
50
50
  quoteProvider?: string
@@ -80,7 +80,6 @@ export const Fund: React.FC<FundProps> = ({
80
80
  onComplete,
81
81
  onSend,
82
82
  paymasterUrls,
83
- gasless,
84
83
  isSequenceWallet = false,
85
84
  setWalletConfirmRetryHandler,
86
85
  quoteProvider,
@@ -151,7 +150,7 @@ export const Fund: React.FC<FundProps> = ({
151
150
  } = useSendForm({
152
151
  account,
153
152
  toAmount: undefined, // Don't pass toAmount for fund form - user enters input amount
154
- toRecipient: selectedRecipient || account.address,
153
+ toRecipient: selectedRecipient || account?.address,
155
154
  toChainId,
156
155
  toToken,
157
156
  toCalldata,
@@ -160,7 +159,6 @@ export const Fund: React.FC<FundProps> = ({
160
159
  onError,
161
160
  onWaitingForWalletConfirm,
162
161
  paymasterUrls,
163
- gasless,
164
162
  onConfirm,
165
163
  onComplete,
166
164
  onSend,
@@ -521,27 +519,10 @@ export const Fund: React.FC<FundProps> = ({
521
519
  }, [tokenAmountForBackend, isInputTypeUsd, sourceTokenPrice])
522
520
 
523
521
  // Dynamic font size based on input length - matching Earn.tsx
524
- const inputStyles = useMemo(() => {
525
- const inputLength = displayAmount.length
526
- let fontSize: string
527
-
528
- if (inputLength > 12) {
529
- fontSize = "0.875rem"
530
- } else if (inputLength > 9) {
531
- fontSize = "1rem"
532
- } else if (inputLength > 6) {
533
- fontSize = "1.125rem"
534
- } else if (inputLength > 3) {
535
- fontSize = "1.25rem"
536
- } else {
537
- fontSize = "1.5rem"
538
- }
539
-
540
- return {
541
- fontSize,
542
- transition: "all 0.1s ease-in-out",
543
- }
544
- }, [displayAmount.length])
522
+ const inputStyles = useDynamicInputStyles({
523
+ inputValue: displayAmount,
524
+ variant: "smaller",
525
+ })
545
526
 
546
527
  const handleOriginTokenSelect = useCallback(
547
528
  (token: any) => {
@@ -839,7 +820,7 @@ export const Fund: React.FC<FundProps> = ({
839
820
  {/* Bottom Info Row */}
840
821
  <div className="mt-2 flex justify-between items-center">
841
822
  {/* USD Amount */}
842
- <div className="text-xs trails-text-muted">
823
+ <div className="text-xs text-gray-500 dark:text-gray-400">
843
824
  {originToken?.symbol && displayAmount ? (
844
825
  <>≈ {amountUsdDisplay || "$0.00"}</>
845
826
  ) : (
@@ -852,7 +833,7 @@ export const Fund: React.FC<FundProps> = ({
852
833
  <div className="flex items-center space-x-2">
853
834
  <button
854
835
  type="button"
855
- className="text-xs trails-text-muted cursor-pointer hover:trails-hover-text transition-colors bg-transparent border-none p-0"
836
+ className="text-xs text-gray-500 dark:text-gray-400 cursor-pointer hover:text-gray-700 dark:hover:text-gray-200 transition-colors bg-transparent border-none p-0"
856
837
  onClick={() => handlePercentageClick(100)}
857
838
  onKeyDown={(e) => {
858
839
  if (e.key === "Enter" || e.key === " ") {
@@ -1,5 +1,5 @@
1
1
  import type React from "react"
2
- import { QrCode, Send, ChevronRight } from "lucide-react"
2
+ import { QrCode, ArrowLeftRight, ChevronRight } from "lucide-react"
3
3
  import { useSwitchAccount } from "wagmi"
4
4
  import { ScreenHeader } from "./ScreenHeader.js"
5
5
  import { ConnectedWallets } from "./ConnectedWallets.js"
@@ -103,10 +103,11 @@ const FundMethods: React.FC<FundMethodsProps> = ({
103
103
  <button
104
104
  type="button"
105
105
  onClick={onSelectExchangeList}
106
- className="w-full text-left px-3 py-4 text-sm flex items-center justify-between cursor-pointer transition-colors trails-text-primary trails-hover-bg"
106
+ disabled
107
+ className="w-full text-left px-3 py-4 text-sm flex items-center justify-between cursor-not-allowed transition-colors opacity-50"
107
108
  >
108
109
  <div className="flex items-center gap-3">
109
- <Send className="w-4 h-4" />
110
+ <ArrowLeftRight className="w-4 h-4" />
110
111
  <span className="text-sm font-bold">Send from Exchange</span>
111
112
  </div>
112
113
  <ChevronRight className="w-5 h-5 text-gray-400" />
@@ -13,18 +13,17 @@ interface FundProps {
13
13
  onBack?: () => void
14
14
  onConfirm: () => void
15
15
  onComplete: (result: OnCompleteProps) => void
16
- account: Account
16
+ account?: Account
17
17
  toRecipient?: string
18
18
  toAmount?: string
19
19
  toChainId?: number
20
20
  toToken?: string
21
21
  toCalldata?: string
22
- walletClient: WalletClient
22
+ walletClient?: WalletClient
23
23
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
24
24
  onError: (error: Error | string | null) => void
25
25
  onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
26
26
  paymasterUrls?: Array<{ chainId: number; url: string }>
27
- gasless?: boolean
28
27
  setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
29
28
  quoteProvider?: string
30
29
  fundMethod?: string