0xtrails 0.1.13 → 0.2.1

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 (256) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +12 -2
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/apiClient.d.ts +1 -1
  5. package/dist/apiClient.d.ts.map +1 -1
  6. package/dist/{ccip-D3gTQONK.js → ccip-BbfANth7.js} +12 -12
  7. package/dist/cctp.d.ts.map +1 -1
  8. package/dist/cctpqueue.d.ts +3 -3
  9. package/dist/cctpqueue.d.ts.map +1 -1
  10. package/dist/chains.d.ts.map +1 -1
  11. package/dist/config.d.ts +18 -5
  12. package/dist/config.d.ts.map +1 -1
  13. package/dist/constants.d.ts +6 -5
  14. package/dist/constants.d.ts.map +1 -1
  15. package/dist/contractUtils.d.ts +2 -0
  16. package/dist/contractUtils.d.ts.map +1 -1
  17. package/dist/customChains.d.ts +24 -0
  18. package/dist/customChains.d.ts.map +1 -0
  19. package/dist/gasless.d.ts +19 -7
  20. package/dist/gasless.d.ts.map +1 -1
  21. package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +405 -394
  25. package/dist/indexerClient.d.ts +10 -0
  26. package/dist/indexerClient.d.ts.map +1 -1
  27. package/dist/intentEntrypoint.d.ts +122 -0
  28. package/dist/intentEntrypoint.d.ts.map +1 -0
  29. package/dist/intents.d.ts +5 -3
  30. package/dist/intents.d.ts.map +1 -1
  31. package/dist/metaTxnMonitor.d.ts.map +1 -1
  32. package/dist/morpho.d.ts.map +1 -1
  33. package/dist/pools.d.ts +3 -1
  34. package/dist/pools.d.ts.map +1 -1
  35. package/dist/prepareSend.d.ts +18 -9
  36. package/dist/prepareSend.d.ts.map +1 -1
  37. package/dist/prices.d.ts +1 -1
  38. package/dist/prices.d.ts.map +1 -1
  39. package/dist/relaySdk.d.ts.map +1 -1
  40. package/dist/relayer.d.ts.map +1 -1
  41. package/dist/toast.d.ts +9 -0
  42. package/dist/toast.d.ts.map +1 -0
  43. package/dist/tokenBalances.d.ts +6 -2
  44. package/dist/tokenBalances.d.ts.map +1 -1
  45. package/dist/tokens.d.ts.map +1 -1
  46. package/dist/trails.d.ts +6 -5
  47. package/dist/trails.d.ts.map +1 -1
  48. package/dist/trailsClient.d.ts +12 -0
  49. package/dist/trailsClient.d.ts.map +1 -0
  50. package/dist/trailsRouter.d.ts +22 -0
  51. package/dist/trailsRouter.d.ts.map +1 -0
  52. package/dist/transactions.d.ts +8 -1
  53. package/dist/transactions.d.ts.map +1 -1
  54. package/dist/wallets.d.ts.map +1 -1
  55. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  56. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  57. package/dist/widget/components/AccountSettings.d.ts +7 -0
  58. package/dist/widget/components/AccountSettings.d.ts.map +1 -0
  59. package/dist/widget/components/ChainList.d.ts +0 -1
  60. package/dist/widget/components/ChainList.d.ts.map +1 -1
  61. package/dist/widget/components/ClassicSwap.d.ts +46 -0
  62. package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
  63. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  64. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  65. package/dist/widget/components/ConnectedWallets.d.ts +9 -0
  66. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
  67. package/dist/widget/components/DebugMenu.d.ts.map +1 -1
  68. package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
  69. package/dist/widget/components/DebugToast.d.ts +3 -0
  70. package/dist/widget/components/DebugToast.d.ts.map +1 -0
  71. package/dist/widget/components/Earn.d.ts.map +1 -1
  72. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  73. package/dist/widget/components/FeeOption.d.ts +22 -0
  74. package/dist/widget/components/FeeOption.d.ts.map +1 -0
  75. package/dist/widget/components/FeeOptions.d.ts +13 -17
  76. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  77. package/dist/widget/components/Fund.d.ts +44 -0
  78. package/dist/widget/components/Fund.d.ts.map +1 -0
  79. package/dist/widget/components/FundMethods.d.ts +1 -1
  80. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  81. package/dist/widget/components/FundSendForm.d.ts.map +1 -1
  82. package/dist/widget/components/Identicon.d.ts +9 -0
  83. package/dist/widget/components/Identicon.d.ts.map +1 -0
  84. package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
  85. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  86. package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
  87. package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
  88. package/dist/widget/components/NativeGasOption.d.ts +12 -0
  89. package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
  90. package/dist/widget/components/Pay.d.ts +46 -0
  91. package/dist/widget/components/Pay.d.ts.map +1 -0
  92. package/dist/widget/components/PaySendForm.d.ts.map +1 -1
  93. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  94. package/dist/widget/components/Receive.d.ts.map +1 -1
  95. package/dist/widget/components/RecentTokens.d.ts.map +1 -1
  96. package/dist/widget/components/Recipients.d.ts +9 -0
  97. package/dist/widget/components/Recipients.d.ts.map +1 -0
  98. package/dist/widget/components/RefundWarning.d.ts +9 -0
  99. package/dist/widget/components/RefundWarning.d.ts.map +1 -0
  100. package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
  101. package/dist/widget/components/Swap.d.ts.map +1 -1
  102. package/dist/widget/components/SwapSettings.d.ts +1 -5
  103. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  104. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  105. package/dist/widget/components/ThemeSyncer.d.ts +6 -0
  106. package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
  107. package/dist/widget/components/Toast.d.ts +24 -0
  108. package/dist/widget/components/Toast.d.ts.map +1 -0
  109. package/dist/widget/components/TokenList.d.ts.map +1 -1
  110. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  111. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  112. package/dist/widget/components/TruncatedAddress.d.ts +2 -0
  113. package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
  114. package/dist/widget/components/UserPreferences.d.ts +7 -0
  115. package/dist/widget/components/UserPreferences.d.ts.map +1 -0
  116. package/dist/widget/hooks/useBack.d.ts +2 -0
  117. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  118. package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
  119. package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
  120. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  121. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  122. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  123. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  124. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  125. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
  126. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
  127. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  128. package/dist/widget/hooks/usePayMessage.d.ts +34 -0
  129. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
  130. package/dist/widget/hooks/useRecipients.d.ts +17 -0
  131. package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
  132. package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
  133. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
  134. package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
  135. package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
  136. package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
  137. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
  138. package/dist/widget/hooks/useSendForm.d.ts +10 -13
  139. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  140. package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
  141. package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
  142. package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
  143. package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
  144. package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
  145. package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
  146. package/dist/widget/hooks/useTheme.d.ts +14 -0
  147. package/dist/widget/hooks/useTheme.d.ts.map +1 -0
  148. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  149. package/dist/widget/index.js +2 -2
  150. package/dist/widget/widget.d.ts +9 -0
  151. package/dist/widget/widget.d.ts.map +1 -1
  152. package/package.json +38 -36
  153. package/src/aave.ts +6 -1
  154. package/src/analytics.ts +109 -53
  155. package/src/apiClient.ts +1 -1
  156. package/src/cctp.ts +6 -2
  157. package/src/cctpqueue.ts +7 -7
  158. package/src/chains.ts +18 -0
  159. package/src/config.ts +63 -17
  160. package/src/constants.ts +20 -16
  161. package/src/contractUtils.ts +33 -2
  162. package/src/customChains.ts +24 -0
  163. package/src/gasless.ts +162 -109
  164. package/src/index.ts +11 -1
  165. package/src/indexerClient.ts +73 -1
  166. package/src/intentEntrypoint.ts +218 -0
  167. package/src/intents.ts +85 -54
  168. package/src/metaTxnMonitor.ts +1 -0
  169. package/src/morpho.ts +13 -2
  170. package/src/pools.ts +68 -86
  171. package/src/prepareSend.ts +1719 -967
  172. package/src/prices.ts +51 -7
  173. package/src/relaySdk.ts +6 -4
  174. package/src/relayer.ts +6 -3
  175. package/src/toast.ts +110 -0
  176. package/src/tokenBalances.ts +112 -20
  177. package/src/tokens.ts +70 -7
  178. package/src/trails.ts +81 -80
  179. package/src/trailsClient.ts +48 -0
  180. package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
  181. package/src/transactions.ts +30 -88
  182. package/src/umd.tsx +1 -1
  183. package/src/wallets.ts +2 -1
  184. package/src/widget/assets/sequence-logo.svg +15 -0
  185. package/src/widget/compiled.css +2 -2
  186. package/src/widget/components/AccountActionsDropdown.tsx +18 -159
  187. package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
  188. package/src/widget/components/AccountSettings.tsx +102 -0
  189. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  190. package/src/widget/components/ChainList.tsx +10 -20
  191. package/src/widget/components/ClassicSwap.tsx +921 -0
  192. package/src/widget/components/ConfigDisplay.tsx +41 -5
  193. package/src/widget/components/ConnectWallet.tsx +168 -11
  194. package/src/widget/components/ConnectedWallets.tsx +342 -0
  195. package/src/widget/components/DebugMenu.tsx +2 -0
  196. package/src/widget/components/DebugScreensList.tsx +3 -0
  197. package/src/widget/components/DebugToast.tsx +63 -0
  198. package/src/widget/components/Earn.tsx +112 -143
  199. package/src/widget/components/EarnPools.tsx +2 -4
  200. package/src/widget/components/EarnPoolsFilters.tsx +6 -6
  201. package/src/widget/components/FeeOption.tsx +78 -0
  202. package/src/widget/components/FeeOptions.tsx +192 -127
  203. package/src/widget/components/Fund.tsx +1236 -0
  204. package/src/widget/components/FundMethods.tsx +4 -4
  205. package/src/widget/components/FundSendForm.tsx +1 -34
  206. package/src/widget/components/Identicon.tsx +158 -0
  207. package/src/widget/components/MeshConnectExchanges.tsx +32 -3
  208. package/src/widget/components/MeshConnectFlow.tsx +23 -4
  209. package/src/widget/components/NativeGasOption.tsx +99 -0
  210. package/src/widget/components/Pay.tsx +1092 -0
  211. package/src/widget/components/PaySendForm.tsx +1 -38
  212. package/src/widget/components/QuoteDetails.tsx +1 -30
  213. package/src/widget/components/Receipt.tsx +1 -1
  214. package/src/widget/components/Receive.tsx +4 -2
  215. package/src/widget/components/RecentTokens.tsx +2 -1
  216. package/src/widget/components/Recipients.tsx +448 -0
  217. package/src/widget/components/RefundWarning.tsx +61 -0
  218. package/src/widget/components/ScreenHeader.tsx +1 -1
  219. package/src/widget/components/SimpleSwap.tsx +74 -58
  220. package/src/widget/components/Swap.tsx +35 -853
  221. package/src/widget/components/SwapSettings.tsx +5 -11
  222. package/src/widget/components/ThemeProvider.tsx +32 -0
  223. package/src/widget/components/ThemeSyncer.tsx +47 -0
  224. package/src/widget/components/Toast.tsx +315 -0
  225. package/src/widget/components/TokenList.tsx +2 -34
  226. package/src/widget/components/TokenSelector.tsx +14 -3
  227. package/src/widget/components/TransactionDetails.tsx +153 -13
  228. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  229. package/src/widget/components/TruncatedAddress.tsx +5 -1
  230. package/src/widget/components/UserPreferences.tsx +155 -0
  231. package/src/widget/components/WalletList.tsx +1 -1
  232. package/src/widget/hooks/useBack.tsx +4 -0
  233. package/src/widget/hooks/useBalanceVisible.tsx +40 -2
  234. package/src/widget/hooks/useCheckout.ts +13 -0
  235. package/src/widget/hooks/useCurrentScreen.tsx +4 -0
  236. package/src/widget/hooks/useDebugScreens.ts +12 -2
  237. package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -0
  238. package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
  239. package/src/widget/hooks/usePayMessage.tsx +370 -0
  240. package/src/widget/hooks/useRecipients.ts +168 -0
  241. package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
  242. package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
  243. package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
  244. package/src/widget/hooks/useSendForm.ts +257 -49
  245. package/src/widget/hooks/useSwapAmount.tsx +50 -0
  246. package/src/widget/hooks/useSwapSettings.tsx +100 -0
  247. package/src/widget/hooks/useTargetAmount.ts +23 -0
  248. package/src/widget/hooks/useTheme.tsx +80 -0
  249. package/src/widget/hooks/useTokenList.ts +20 -11
  250. package/src/widget/index.css +45 -21
  251. package/src/widget/widget.tsx +294 -136
  252. package/dist/address.d.ts +0 -2
  253. package/dist/address.d.ts.map +0 -1
  254. package/dist/proxyCaller.d.ts +0 -21
  255. package/dist/proxyCaller.d.ts.map +0 -1
  256. package/src/address.ts +0 -6
@@ -0,0 +1,1236 @@
1
+ import {
2
+ ChevronRight,
3
+ Search,
4
+ Loader2,
5
+ ChevronDown,
6
+ ArrowDown,
7
+ } from "lucide-react"
8
+ import { useEffect, useState, useMemo, useRef, useCallback } from "react"
9
+ import type React from "react"
10
+ import type { Account, WalletClient } from "viem"
11
+ import type { TransactionState } from "../../transactions.js"
12
+ import type { OnCompleteProps } from "../hooks/useSendForm.js"
13
+ import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
14
+ import { useSendForm } from "../hooks/useSendForm.js"
15
+ import { TradeType } from "../../prepareSend.js"
16
+ import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
17
+ import { useDestinationSelectedToken } from "../hooks/useDestinationSelectedToken.js"
18
+ import { useSelectedRecipient } from "../hooks/useSelectedRecipient.js"
19
+ import { useSwapAmount } from "../hooks/useSwapAmount.js"
20
+ import { useMode } from "../hooks/useMode.js"
21
+ import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
22
+ import { useCurrentScreen } from "../hooks/useCurrentScreen.js"
23
+ import { useDefaultTokenSelection } from "../hooks/useDefaultTokenSelection.js" // Context version
24
+ import { useTargetAmount } from "../hooks/useTargetAmount.js"
25
+ import { TokenImage } from "./TokenImage.js"
26
+ import { Identicon } from "./Identicon.js"
27
+ import { TokenSelector } from "./TokenSelector.js"
28
+ import { ScreenHeader } from "./ScreenHeader.js"
29
+ import { ChainList } from "./ChainList.js"
30
+ import { QuoteDetails } from "./QuoteDetails.js"
31
+ import { ErrorDisplay } from "./ErrorDisplay.js"
32
+ import { getChainInfo } from "../../chains.js"
33
+ import { truncateAddress } from "../../utils.js"
34
+ import type { PrepareSendQuote } from "../../prepareSend.js"
35
+ import type { SupportedToken } from "../../tokens.js"
36
+ import { logger } from "../../logger.js"
37
+ import { RefundWarning } from "./RefundWarning.js"
38
+
39
+ interface FundProps {
40
+ onBack?: () => void
41
+ account: Account
42
+ walletClient: WalletClient
43
+ onTransactionStateChange: (transactionStates: TransactionState[]) => void
44
+ onError: (error: Error | string | null) => void
45
+ onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
46
+ onConfirm: () => void
47
+ onComplete: (result: OnCompleteProps) => void
48
+ onSend: (amount: string, recipient: string) => void
49
+ paymasterUrls?: Array<{ chainId: number; url: string }>
50
+ gasless?: boolean
51
+ setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
52
+ quoteProvider?: string
53
+ fundMethod?: string
54
+ onNavigateToMeshConnect?: (
55
+ props: {
56
+ toTokenSymbol: string
57
+ toTokenAmount: string
58
+ toChainId: number
59
+ toRecipientAddress: string
60
+ },
61
+ quote?: PrepareSendQuote | null,
62
+ ) => void
63
+ checkoutOnHandlers?: CheckoutOnHandlers
64
+ recentTokens?: SupportedToken[]
65
+ onRecentTokenSelect?: (token: SupportedToken) => void
66
+ onTrackToken?: (token: any) => void
67
+ toRecipient?: string
68
+ toAmount?: string
69
+ toChainId?: number
70
+ toToken?: string
71
+ toCalldata?: string
72
+ }
73
+
74
+ export const Fund: React.FC<FundProps> = ({
75
+ onBack,
76
+ account,
77
+ walletClient,
78
+ onTransactionStateChange,
79
+ onError,
80
+ onWaitingForWalletConfirm,
81
+ onConfirm,
82
+ onComplete,
83
+ onSend,
84
+ paymasterUrls,
85
+ gasless,
86
+ setWalletConfirmRetryHandler,
87
+ quoteProvider,
88
+ fundMethod,
89
+ onNavigateToMeshConnect,
90
+ checkoutOnHandlers,
91
+ recentTokens,
92
+ onRecentTokenSelect,
93
+ onTrackToken,
94
+ toRecipient,
95
+ toChainId,
96
+ toToken,
97
+ toCalldata,
98
+ }) => {
99
+ const { mode } = useMode()
100
+ const { isBalanceVisible } = useBalanceVisible()
101
+ const { selectedToken: originToken, setSelectedToken: setOriginToken } =
102
+ useOriginSelectedToken()
103
+ const {
104
+ selectedToken: globalDestinationToken,
105
+ setSelectedToken: setDestinationToken,
106
+ } = useDestinationSelectedToken()
107
+ const { selectedRecipient, setSelectedRecipient } = useSelectedRecipient()
108
+ const { amount: globalAmount, setAmount: setGlobalAmount } = useSwapAmount()
109
+ const { setCurrentScreen } = useCurrentScreen()
110
+ const { targetAmountUsd } = useTargetAmount()
111
+
112
+ // Use new default token selection hook
113
+ const {
114
+ defaultOriginToken,
115
+ defaultDestinationToken,
116
+ isLoading: isLoadingDefaults,
117
+ } = useDefaultTokenSelection()
118
+
119
+ // Local state for fund-specific functionality
120
+ const [isInputTypeUsd, setIsInputTypeUsd] = useState(false)
121
+ const [tokenAmountForBackend, setTokenAmountForBackend] = useState("")
122
+ const [inputDisplayValue, setInputDisplayValue] = useState("")
123
+ const [showOriginTokenSelector, setShowOriginTokenSelector] = useState(false)
124
+ const [showDestinationTokenSelector, setShowDestinationTokenSelector] =
125
+ useState(false)
126
+ const [showOriginChainList, setShowOriginChainList] = useState(false)
127
+ const [showDestinationChainList, setShowDestinationChainList] =
128
+ useState(false)
129
+ const inputRef = useRef<HTMLInputElement>(null)
130
+
131
+ // Use useSendForm for quote functionality
132
+ const {
133
+ amountUsdDisplay,
134
+ balanceFormatted,
135
+ isLoadingQuote,
136
+ prepareSendQuote,
137
+ setAmount: setSendFormAmount,
138
+ handleSubmit,
139
+ isSubmitting,
140
+ buttonText,
141
+ isValidRecipient,
142
+ selectedDestToken,
143
+ setSelectedDestToken,
144
+ setSelectedDestinationChain,
145
+ quoteError,
146
+ quoteErrorPrettified,
147
+ isSameTokenWithoutCustomCalldata,
148
+ destinationTokenAddress,
149
+ isValidCustomToken,
150
+ isSenderContractOnOrigin,
151
+ isSenderContractOnDestination,
152
+ } = useSendForm({
153
+ account,
154
+ toAmount: undefined, // Don't pass toAmount for fund form - user enters input amount
155
+ toRecipient: selectedRecipient || account.address,
156
+ toChainId,
157
+ toToken,
158
+ toCalldata,
159
+ walletClient,
160
+ onTransactionStateChange,
161
+ onError,
162
+ onWaitingForWalletConfirm,
163
+ paymasterUrls,
164
+ gasless,
165
+ onConfirm,
166
+ onComplete,
167
+ onSend,
168
+ selectedToken: originToken as any,
169
+ setWalletConfirmRetryHandler,
170
+ tradeType: TradeType.EXACT_INPUT,
171
+ quoteProvider,
172
+ fundMethod,
173
+ mode,
174
+ onNavigateToMeshConnect,
175
+ checkoutOnHandlers,
176
+ })
177
+
178
+ // Auto-select origin and destination tokens using new hook
179
+ useEffect(() => {
180
+ if (!originToken && !isLoadingDefaults && defaultOriginToken) {
181
+ logger.console.log(
182
+ "[trails-sdk] Auto-selecting origin token:",
183
+ defaultOriginToken,
184
+ )
185
+ setOriginToken(defaultOriginToken as any)
186
+ }
187
+ }, [originToken, isLoadingDefaults, defaultOriginToken, setOriginToken])
188
+
189
+ // Auto-select destination token using new hook
190
+ useEffect(() => {
191
+ if (
192
+ !globalDestinationToken &&
193
+ !toToken &&
194
+ originToken &&
195
+ !isLoadingDefaults &&
196
+ defaultDestinationToken
197
+ ) {
198
+ logger.console.log(
199
+ "[trails-sdk] Auto-selecting destination token:",
200
+ defaultDestinationToken,
201
+ )
202
+
203
+ const decimals =
204
+ defaultDestinationToken.contractInfo?.decimals ??
205
+ (defaultDestinationToken as any)?.decimals
206
+
207
+ setDestinationToken(defaultDestinationToken as any)
208
+ setSelectedDestToken({
209
+ symbol: defaultDestinationToken.symbol,
210
+ name: defaultDestinationToken.name,
211
+ imageUrl: defaultDestinationToken.imageUrl,
212
+ decimals,
213
+ })
214
+
215
+ // Also set the destination chain
216
+ if (setSelectedDestinationChain) {
217
+ const chainInfo = getChainInfo(defaultDestinationToken.chainId)
218
+ if (chainInfo) {
219
+ setSelectedDestinationChain(chainInfo)
220
+ }
221
+ }
222
+ }
223
+ }, [
224
+ originToken,
225
+ globalDestinationToken,
226
+ toToken,
227
+ isLoadingDefaults,
228
+ defaultDestinationToken,
229
+ setDestinationToken,
230
+ setSelectedDestToken,
231
+ setSelectedDestinationChain,
232
+ ])
233
+
234
+ // Initialize selected recipient from toRecipient prop or default to connected wallet
235
+ useEffect(() => {
236
+ if (toRecipient && !selectedRecipient) {
237
+ setSelectedRecipient(toRecipient)
238
+ } else if (!selectedRecipient && account?.address) {
239
+ // Default to connected wallet address if no recipient is set
240
+ setSelectedRecipient(account.address)
241
+ }
242
+ }, [toRecipient, selectedRecipient, setSelectedRecipient, account?.address])
243
+
244
+ // Sync global destination token with useSendForm
245
+ useEffect(() => {
246
+ if (globalDestinationToken && selectedDestToken && !toToken) {
247
+ const decimals =
248
+ globalDestinationToken.contractInfo?.decimals ??
249
+ (globalDestinationToken as any)?.decimals
250
+
251
+ // Convert global token format to useSendForm format
252
+ const formattedToken = {
253
+ symbol: globalDestinationToken.symbol,
254
+ name: globalDestinationToken.name,
255
+ imageUrl: globalDestinationToken.imageUrl,
256
+ decimals,
257
+ }
258
+
259
+ // Only update if the tokens are different (to avoid infinite loops)
260
+ if (selectedDestToken.symbol !== formattedToken.symbol) {
261
+ logger.console.log(
262
+ "[trails-sdk] Restoring destination token from global state:",
263
+ formattedToken,
264
+ )
265
+ setSelectedDestToken(formattedToken)
266
+
267
+ // Also update destination chain to match the selected token's chain
268
+ if (setSelectedDestinationChain && globalDestinationToken.chainId) {
269
+ const chainInfo = getChainInfo(globalDestinationToken.chainId)
270
+ if (chainInfo) {
271
+ setSelectedDestinationChain(chainInfo)
272
+ }
273
+ }
274
+ }
275
+ }
276
+ }, [
277
+ globalDestinationToken,
278
+ selectedDestToken,
279
+ setSelectedDestToken,
280
+ setSelectedDestinationChain,
281
+ toToken,
282
+ ])
283
+
284
+ // Force restore global destination token on mount (runs after useSendForm initialization)
285
+ useEffect(() => {
286
+ if (globalDestinationToken && !toToken) {
287
+ const decimals =
288
+ globalDestinationToken.contractInfo?.decimals ??
289
+ (globalDestinationToken as any)?.decimals
290
+
291
+ const timeoutId = setTimeout(() => {
292
+ const formattedToken = {
293
+ symbol: globalDestinationToken.symbol,
294
+ name: globalDestinationToken.name,
295
+ imageUrl: globalDestinationToken.imageUrl,
296
+ decimals,
297
+ }
298
+ logger.console.log(
299
+ "[trails-sdk] Force restoring destination token on mount:",
300
+ formattedToken,
301
+ )
302
+ setSelectedDestToken(formattedToken)
303
+
304
+ if (setSelectedDestinationChain && globalDestinationToken.chainId) {
305
+ const chainInfo = getChainInfo(globalDestinationToken.chainId)
306
+ if (chainInfo) {
307
+ setSelectedDestinationChain(chainInfo)
308
+ }
309
+ }
310
+ }, 50)
311
+
312
+ return () => clearTimeout(timeoutId)
313
+ }
314
+ }, [
315
+ globalDestinationToken,
316
+ setSelectedDestToken,
317
+ setSelectedDestinationChain,
318
+ toToken,
319
+ ])
320
+
321
+ // Initialize local amount states from global amount
322
+ useEffect(() => {
323
+ if (globalAmount && !tokenAmountForBackend && !inputDisplayValue) {
324
+ logger.console.log(
325
+ "[trails-sdk] Restoring amount from global state:",
326
+ globalAmount,
327
+ )
328
+ setTokenAmountForBackend(globalAmount)
329
+ setInputDisplayValue(globalAmount)
330
+ setSendFormAmount(globalAmount)
331
+ }
332
+ }, [
333
+ globalAmount,
334
+ tokenAmountForBackend,
335
+ inputDisplayValue,
336
+ setSendFormAmount,
337
+ ])
338
+
339
+ // Debug logging for component mount
340
+ useEffect(() => {
341
+ logger.console.log("[trails-sdk] Fund component mounted/updated", {
342
+ originToken: originToken?.symbol,
343
+ originChainId: originToken?.chainId,
344
+ globalDestinationToken: globalDestinationToken?.symbol,
345
+ globalDestinationChainId: globalDestinationToken?.chainId,
346
+ selectedDestToken: selectedDestToken?.symbol,
347
+ toChainId,
348
+ globalAmount,
349
+ tokenAmountForBackend,
350
+ })
351
+ }, [
352
+ originToken,
353
+ globalDestinationToken,
354
+ selectedDestToken,
355
+ globalAmount,
356
+ tokenAmountForBackend,
357
+ toChainId,
358
+ ])
359
+
360
+ // Auto-focus input field on component mount
361
+ useEffect(() => {
362
+ const timer = setTimeout(() => {
363
+ if (inputRef.current) {
364
+ inputRef.current.focus()
365
+ }
366
+ }, 100)
367
+
368
+ return () => clearTimeout(timer)
369
+ }, [])
370
+
371
+ // Get source token price for USD conversions
372
+ const sourceTokenPrice = (originToken as any)?.tokenPriceUsd || 0
373
+
374
+ // Sync display value with token amount only when mode changes (not during typing)
375
+ const [lastInputMode, setLastInputMode] = useState(isInputTypeUsd)
376
+
377
+ useEffect(() => {
378
+ // Only sync when mode actually changes, not during normal typing
379
+ if (lastInputMode !== isInputTypeUsd && tokenAmountForBackend) {
380
+ const tokenAmount = parseFloat(tokenAmountForBackend) || 0
381
+ if (isInputTypeUsd && sourceTokenPrice > 0) {
382
+ // Show USD with max 2 decimals
383
+ const usdAmount = tokenAmount * sourceTokenPrice
384
+ setInputDisplayValue(Number(usdAmount.toFixed(2)).toString())
385
+ } else {
386
+ // Show token with max 8 decimals
387
+ setInputDisplayValue(Number(tokenAmount.toFixed(8)).toString())
388
+ }
389
+ setLastInputMode(isInputTypeUsd)
390
+ }
391
+ }, [isInputTypeUsd, sourceTokenPrice, tokenAmountForBackend, lastInputMode])
392
+
393
+ // Handle input amount changes with 8 decimal limit and 16 char total limit
394
+ const handleAmountChange = useCallback(
395
+ (value: string) => {
396
+ // Allow empty string
397
+ if (value === "") {
398
+ setInputDisplayValue("")
399
+ setTokenAmountForBackend("")
400
+ setSendFormAmount("")
401
+ setGlobalAmount("")
402
+ return
403
+ }
404
+
405
+ // Limit total length to 16 characters
406
+ if (value.length > 16) {
407
+ return
408
+ }
409
+
410
+ // Validate decimal places (max 8 decimals) and allow single decimal point
411
+ const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
412
+ if (!decimalMatch) {
413
+ return // Don't update if invalid format
414
+ }
415
+
416
+ // Store the display value
417
+ setInputDisplayValue(value)
418
+
419
+ // Update the token amount for backend and useSendForm
420
+ if (isInputTypeUsd && sourceTokenPrice > 0) {
421
+ const usdAmount = parseFloat(value) || 0
422
+ const tokenAmount = usdAmount / sourceTokenPrice
423
+ const tokenAmountStr = tokenAmount.toString()
424
+ setTokenAmountForBackend(tokenAmountStr)
425
+ setSendFormAmount(tokenAmountStr)
426
+ setGlobalAmount(tokenAmountStr)
427
+ } else {
428
+ setTokenAmountForBackend(value)
429
+ setSendFormAmount(value)
430
+ setGlobalAmount(value)
431
+ }
432
+ },
433
+ [setSendFormAmount, isInputTypeUsd, sourceTokenPrice, setGlobalAmount],
434
+ )
435
+
436
+ // Get display values based on input type
437
+ const displayAmount = useMemo(() => {
438
+ return inputDisplayValue
439
+ }, [inputDisplayValue])
440
+
441
+ const displayUsdValue = useMemo(() => {
442
+ if (isInputTypeUsd && sourceTokenPrice > 0) {
443
+ // Show token amount when in USD mode
444
+ const tokenAmount = parseFloat(tokenAmountForBackend) || 0
445
+ return `${tokenAmount.toFixed(8)} ${originToken?.symbol || "TOKEN"}`
446
+ }
447
+ return amountUsdDisplay || "$0.00"
448
+ }, [
449
+ tokenAmountForBackend,
450
+ isInputTypeUsd,
451
+ sourceTokenPrice,
452
+ originToken?.symbol,
453
+ amountUsdDisplay,
454
+ ])
455
+
456
+ // Handle percentage clicks for quick amounts
457
+ const handlePercentageClick = useCallback(
458
+ (percentage: number) => {
459
+ if (!balanceFormatted) {
460
+ return
461
+ }
462
+
463
+ const balance = parseFloat(balanceFormatted)
464
+ if (Number.isNaN(balance)) return
465
+
466
+ const amount = (balance * percentage) / 100
467
+ // Cap decimals to 8 places
468
+ const cappedAmount = parseFloat(amount.toFixed(8))
469
+ const tokenAmountStr = cappedAmount.toString()
470
+
471
+ // Update all states consistently
472
+ setTokenAmountForBackend(tokenAmountStr)
473
+ setSendFormAmount(tokenAmountStr)
474
+ setGlobalAmount(tokenAmountStr)
475
+
476
+ // Update display based on current mode
477
+ if (isInputTypeUsd && sourceTokenPrice > 0) {
478
+ const usdAmount = cappedAmount * sourceTokenPrice
479
+ setInputDisplayValue(Number(usdAmount.toFixed(2)).toString())
480
+ } else {
481
+ setInputDisplayValue(tokenAmountStr)
482
+ }
483
+ },
484
+ [
485
+ balanceFormatted,
486
+ setSendFormAmount,
487
+ isInputTypeUsd,
488
+ sourceTokenPrice,
489
+ setGlobalAmount,
490
+ ],
491
+ )
492
+
493
+ // Handle input type toggle (USD ↔ Token)
494
+ const handleInputTypeToggle = useCallback(() => {
495
+ // Use tokenAmountForBackend as the source of truth for conversion
496
+ const currentTokenAmount = parseFloat(tokenAmountForBackend) || 0
497
+
498
+ if (isInputTypeUsd && sourceTokenPrice > 0) {
499
+ // Switching from USD to token mode
500
+ // Display the token amount (limit to 8 decimals)
501
+ const tokenAmountStr = Number(currentTokenAmount.toFixed(8)).toString()
502
+ setInputDisplayValue(tokenAmountStr)
503
+ } else if (!isInputTypeUsd && sourceTokenPrice > 0) {
504
+ // Switching from token to USD mode
505
+ // Display USD amount (limit to 2 decimals)
506
+ const usdAmount = currentTokenAmount * sourceTokenPrice
507
+ const usdAmountStr = Number(usdAmount.toFixed(2)).toString()
508
+ setInputDisplayValue(usdAmountStr)
509
+ }
510
+
511
+ // Toggle the mode
512
+ setIsInputTypeUsd(!isInputTypeUsd)
513
+
514
+ // Focus the input field after toggling
515
+ setTimeout(() => {
516
+ if (inputRef.current) {
517
+ inputRef.current.focus()
518
+ // Select all text for easy replacement
519
+ inputRef.current.select()
520
+ }
521
+ }, 0)
522
+ }, [tokenAmountForBackend, isInputTypeUsd, sourceTokenPrice])
523
+
524
+ // Dynamic font size based on input length - matching Earn.tsx
525
+ const inputStyles = useMemo(() => {
526
+ const inputLength = displayAmount.length
527
+ let fontSize: string
528
+
529
+ if (inputLength > 12) {
530
+ fontSize = "0.875rem"
531
+ } else if (inputLength > 9) {
532
+ fontSize = "1rem"
533
+ } else if (inputLength > 6) {
534
+ fontSize = "1.125rem"
535
+ } else if (inputLength > 3) {
536
+ fontSize = "1.25rem"
537
+ } else {
538
+ fontSize = "1.5rem"
539
+ }
540
+
541
+ return {
542
+ fontSize,
543
+ transition: "all 0.1s ease-in-out",
544
+ }
545
+ }, [displayAmount.length])
546
+
547
+ const handleOriginTokenSelect = useCallback(
548
+ (token: any) => {
549
+ const formattedToken = {
550
+ ...token,
551
+ decimals: token.contractInfo?.decimals || token.decimals,
552
+ contractInfo: {
553
+ decimals: token.contractInfo?.decimals || token.decimals,
554
+ contractAddress: token.contractAddress,
555
+ symbol: token.symbol,
556
+ name: token.name,
557
+ },
558
+ } as any
559
+ setOriginToken(formattedToken)
560
+ logger.console.log("[trails-sdk] selected origin token", token)
561
+ setShowOriginTokenSelector(false)
562
+ // Track the token selection
563
+ onTrackToken?.(token)
564
+ },
565
+ [setOriginToken, onTrackToken],
566
+ )
567
+
568
+ const handleDestinationTokenSelect = useCallback(
569
+ (token: any) => {
570
+ const formattedToken = {
571
+ ...token,
572
+ decimals: token.contractInfo?.decimals || token.decimals,
573
+ contractInfo: {
574
+ decimals: token.contractInfo?.decimals || token.decimals,
575
+ contractAddress: token.contractAddress,
576
+ symbol: token.symbol,
577
+ name: token.name,
578
+ },
579
+ }
580
+
581
+ // Update both global destination token state and useSendForm state
582
+ setDestinationToken(formattedToken as any)
583
+ setSelectedDestToken(formattedToken as any)
584
+
585
+ // Update destination chain to match the selected token's chain
586
+ if (setSelectedDestinationChain && token.chainId) {
587
+ const chainInfo = getChainInfo(token.chainId)
588
+ if (chainInfo) {
589
+ setSelectedDestinationChain(chainInfo)
590
+ }
591
+ }
592
+
593
+ setShowDestinationTokenSelector(false)
594
+ logger.console.log("[trails-sdk] selected destination token", token)
595
+ // Track the token selection
596
+ onTrackToken?.(token)
597
+ },
598
+ [
599
+ setDestinationToken,
600
+ setSelectedDestToken,
601
+ setSelectedDestinationChain,
602
+ onTrackToken,
603
+ ],
604
+ )
605
+
606
+ // Show origin chain list screen
607
+ if (showOriginChainList) {
608
+ return (
609
+ <ChainList
610
+ onBack={() => {
611
+ setShowOriginChainList(false)
612
+ setShowOriginTokenSelector(true)
613
+ }}
614
+ />
615
+ )
616
+ }
617
+
618
+ // Show destination chain list screen
619
+ if (showDestinationChainList) {
620
+ return (
621
+ <ChainList
622
+ onBack={() => {
623
+ setShowDestinationChainList(false)
624
+ setShowDestinationTokenSelector(true)
625
+ }}
626
+ />
627
+ )
628
+ }
629
+
630
+ // Show origin token selector screen
631
+ if (showOriginTokenSelector) {
632
+ return (
633
+ <div className="space-y-2">
634
+ <ScreenHeader
635
+ onBack={() => setShowOriginTokenSelector(false)}
636
+ headerContent="Select From Token"
637
+ headerContentAlign="left"
638
+ showAccountActions={true}
639
+ />
640
+ <TokenSelector
641
+ onTokenSelect={handleOriginTokenSelect}
642
+ targetAmountUsd={targetAmountUsd}
643
+ onError={onError}
644
+ fundMethod={fundMethod}
645
+ showContinueButton={false}
646
+ compactMode={false}
647
+ recentTokens={recentTokens}
648
+ onRecentTokenSelect={(token) => {
649
+ const formattedToken = {
650
+ ...token,
651
+ decimals: token.decimals,
652
+ contractInfo: {
653
+ decimals: token.decimals,
654
+ contractAddress: token.contractAddress,
655
+ symbol: token.symbol,
656
+ name: token.name,
657
+ },
658
+ } as any
659
+ setOriginToken(formattedToken)
660
+ setShowOriginTokenSelector(false)
661
+ onRecentTokenSelect?.(token)
662
+ }}
663
+ allSupportedTokens={false}
664
+ chainListScreen={true}
665
+ onNavigateToChainList={() => {
666
+ setShowOriginTokenSelector(false)
667
+ setShowOriginChainList(true)
668
+ }}
669
+ onNavigateToFundMethods={() => {
670
+ setCurrentScreen("fund-methods")
671
+ }}
672
+ />
673
+ </div>
674
+ )
675
+ }
676
+
677
+ // Show destination token selector screen
678
+ if (showDestinationTokenSelector) {
679
+ return (
680
+ <div className="space-y-2">
681
+ <ScreenHeader
682
+ onBack={() => setShowDestinationTokenSelector(false)}
683
+ headerContent="Select To Token"
684
+ headerContentAlign="left"
685
+ showAccountActions={true}
686
+ />
687
+ <TokenSelector
688
+ onTokenSelect={handleDestinationTokenSelect}
689
+ targetAmountUsd={targetAmountUsd}
690
+ onError={onError}
691
+ fundMethod={fundMethod}
692
+ showContinueButton={false}
693
+ compactMode={false}
694
+ recentTokens={recentTokens}
695
+ onRecentTokenSelect={(token) => {
696
+ const formattedToken = {
697
+ ...token,
698
+ decimals: token.decimals,
699
+ contractInfo: {
700
+ decimals: token.decimals,
701
+ contractAddress: token.contractAddress,
702
+ symbol: token.symbol,
703
+ name: token.name,
704
+ },
705
+ } as any
706
+ setDestinationToken(formattedToken)
707
+ setSelectedDestToken(formattedToken)
708
+ setShowDestinationTokenSelector(false)
709
+ onRecentTokenSelect?.(token)
710
+ }}
711
+ allSupportedTokens={true}
712
+ chainListScreen={true}
713
+ onNavigateToChainList={() => {
714
+ setShowDestinationTokenSelector(false)
715
+ setShowDestinationChainList(true)
716
+ }}
717
+ />
718
+ </div>
719
+ )
720
+ }
721
+
722
+ if (!originToken) {
723
+ return (
724
+ <div className="space-y-2">
725
+ <ScreenHeader
726
+ onBack={onBack}
727
+ headerContent="Fund"
728
+ headerContentAlign="left"
729
+ showAccountActions={true}
730
+ />
731
+
732
+ <div className="space-y-2">
733
+ {/* Token Selector */}
734
+ <div className="relative">
735
+ <button
736
+ type="button"
737
+ onClick={() => setShowOriginTokenSelector(true)}
738
+ className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-hover-bg trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
739
+ >
740
+ <div className="flex items-center space-x-3 flex-1">
741
+ <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
742
+ <Search className="w-4 h-4 text-gray-400" />
743
+ </div>
744
+ <div className="text-left flex-1">
745
+ <div className="font-medium text-gray-900 dark:text-white text-sm">
746
+ From token
747
+ </div>
748
+ <div className="text-xs trails-text-muted">Select token</div>
749
+ </div>
750
+ </div>
751
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
752
+ </button>
753
+ </div>
754
+ </div>
755
+ </div>
756
+ )
757
+ }
758
+
759
+ return (
760
+ <div className="space-y-1">
761
+ <ScreenHeader
762
+ onBack={onBack}
763
+ headerContent="Fund"
764
+ headerContentAlign="left"
765
+ showAccountActions={true}
766
+ />
767
+
768
+ <div className="space-y-1">
769
+ {/* Origin Amount Input Section */}
770
+ <div className="trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 group transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 focus-within:border-gray-400 dark:focus-within:border-gray-500">
771
+ {/* Deposit Label and Percentage Buttons */}
772
+ <div className="flex justify-between items-center mb-2">
773
+ <div className="text-sm font-medium trails-text-secondary text-left">
774
+ Deposit
775
+ {fundMethod === "qr-code" ? (
776
+ <span className="text-xs"> with QR Code</span>
777
+ ) : fundMethod === "exchange" ? (
778
+ <span className="text-xs"> with Exchange</span>
779
+ ) : (
780
+ ""
781
+ )}
782
+ </div>
783
+
784
+ {/* Percentage Buttons */}
785
+ {originToken && (
786
+ <div className="flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
787
+ <button
788
+ type="button"
789
+ onClick={() => handlePercentageClick(25)}
790
+ className="py-1 px-2 text-xs font-medium trails-border-radius-container border border-solid transition-colors cursor-pointer border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:trails-hover-bg hover:border-gray-400 dark:hover:border-gray-500"
791
+ >
792
+ 25%
793
+ </button>
794
+ <button
795
+ type="button"
796
+ onClick={() => handlePercentageClick(50)}
797
+ className="py-1 px-2 text-xs font-medium trails-border-radius-container border border-solid transition-colors cursor-pointer border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:trails-hover-bg hover:border-gray-400 dark:hover:border-gray-500"
798
+ >
799
+ 50%
800
+ </button>
801
+ <button
802
+ type="button"
803
+ onClick={() => handlePercentageClick(75)}
804
+ className="py-1 px-2 text-xs font-medium trails-border-radius-container border border-solid transition-colors cursor-pointer border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:trails-hover-bg hover:border-gray-400 dark:hover:border-gray-500"
805
+ >
806
+ 75%
807
+ </button>
808
+ <button
809
+ type="button"
810
+ onClick={() => handlePercentageClick(100)}
811
+ className="py-1 px-2 text-xs font-medium trails-border-radius-container border border-solid transition-colors cursor-pointer border-gray-300 text-gray-600 dark:border-gray-600 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 hover:trails-hover-bg hover:border-gray-400 dark:hover:border-gray-500"
812
+ >
813
+ Max
814
+ </button>
815
+ </div>
816
+ )}
817
+ </div>
818
+
819
+ <div className="flex items-center space-x-2">
820
+ {/* Amount Input */}
821
+ <div className="flex-1">
822
+ <div
823
+ className="flex items-center justify-start cursor-text"
824
+ onClick={() => inputRef.current?.focus()}
825
+ >
826
+ <div className="flex items-center">
827
+ <input
828
+ ref={inputRef}
829
+ type="text"
830
+ value={displayAmount}
831
+ onChange={(e) => handleAmountChange(e.target.value)}
832
+ placeholder="0"
833
+ className={`bg-transparent border-none outline-none font-bold text-left trails-text-primary placeholder-trails-text-primary ${
834
+ isLoadingQuote ? "animate-pulse" : ""
835
+ }`}
836
+ style={{
837
+ fontSize: inputStyles.fontSize,
838
+ width: `${Math.max((displayAmount || "0").length, 1)}ch`,
839
+ minWidth: "1ch",
840
+ maxWidth: "270px",
841
+ padding: "0",
842
+ margin: "0",
843
+ transition: "all 0.1s ease-in-out",
844
+ }}
845
+ inputMode="decimal"
846
+ />
847
+ <span
848
+ className="font-bold text-gray-400 dark:text-gray-500"
849
+ style={{
850
+ fontSize: inputStyles.fontSize,
851
+ marginLeft: "0.1em",
852
+ padding: "0",
853
+ transition: "all 0.2s ease-in-out",
854
+ }}
855
+ >
856
+ {isInputTypeUsd
857
+ ? "USD"
858
+ : originToken?.symbol.slice(0, 4) || ""}
859
+ </span>
860
+ {isLoadingQuote && (
861
+ <div className="ml-2 animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
862
+ )}
863
+ </div>
864
+ </div>
865
+ </div>
866
+
867
+ {/* Token Selection Button */}
868
+ <button
869
+ type="button"
870
+ onClick={() => setShowOriginTokenSelector(true)}
871
+ className="flex items-center space-x-2 trails-bg-card hover:trails-hover-bg trails-border-radius-input px-2.5 py-1.5 border trails-border-primary transition-colors cursor-pointer"
872
+ >
873
+ {originToken ? (
874
+ <>
875
+ <TokenImage
876
+ symbol={originToken.symbol}
877
+ imageUrl={originToken.imageUrl}
878
+ chainId={originToken.chainId}
879
+ size={20}
880
+ />
881
+ <span className="font-medium trails-text-primary text-sm">
882
+ {originToken.symbol}
883
+ </span>
884
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
885
+ </>
886
+ ) : (
887
+ <>
888
+ <span className="font-medium trails-text-muted text-sm">
889
+ Select Token
890
+ </span>
891
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
892
+ </>
893
+ )}
894
+ </button>
895
+ </div>
896
+
897
+ {/* Bottom Info Row */}
898
+ <div className="mt-2 flex justify-between items-center">
899
+ {/* USD Amount */}
900
+ <div className="text-xs trails-text-muted">
901
+ {originToken?.symbol && displayAmount ? (
902
+ <>≈ {amountUsdDisplay || "$0.00"}</>
903
+ ) : (
904
+ <span>&nbsp;</span>
905
+ )}
906
+ </div>
907
+
908
+ {/* Origin Token Balance */}
909
+ <div className="text-xs trails-text-muted text-right">
910
+ {originToken ? (
911
+ <button
912
+ type="button"
913
+ className="text-xs trails-text-muted cursor-pointer hover:trails-hover-text transition-colors bg-transparent border-none p-0"
914
+ onClick={() => handlePercentageClick(100)}
915
+ onKeyDown={(e) => {
916
+ if (e.key === "Enter" || e.key === " ") {
917
+ e.preventDefault()
918
+ handlePercentageClick(100)
919
+ }
920
+ }}
921
+ title="Click to use full balance"
922
+ >
923
+ Balance:{" "}
924
+ {isBalanceVisible
925
+ ? `${balanceFormatted || "0.00"} ${originToken.symbol}`
926
+ : "••••••"}
927
+ </button>
928
+ ) : (
929
+ <span>&nbsp;</span>
930
+ )}
931
+ </div>
932
+ </div>
933
+ </div>
934
+
935
+ {/* USD Value Toggle */}
936
+ <div
937
+ className="flex items-center justify-start"
938
+ style={{ display: "none" }}
939
+ >
940
+ <button
941
+ type="button"
942
+ onClick={handleInputTypeToggle}
943
+ className="flex items-center justify-start gap-2 px-3 py-1.5 rounded-md transition-colors cursor-pointer text-blue-600 hover:bg-gray-50 dark:hover:bg-gray-700 hover:text-blue-700"
944
+ style={{ color: "#155DFC" }}
945
+ >
946
+ <svg
947
+ width="20"
948
+ height="21"
949
+ viewBox="0 0 20 21"
950
+ fill="none"
951
+ xmlns="http://www.w3.org/2000/svg"
952
+ aria-label="Toggle between USD and token amount"
953
+ >
954
+ <title>Toggle between USD and token amount</title>
955
+ <path
956
+ d="M11.3879 14.7175V10.4181C11.3879 10.2209 11.4547 10.0554 11.5884 9.9217C11.7216 9.78848 11.8868 9.72187 12.0841 9.72187C12.2814 9.72187 12.4469 9.78848 12.5805 9.9217C12.7138 10.0554 12.7804 10.2209 12.7804 10.4181V14.7175L14.0859 13.412C14.2135 13.2844 14.3729 13.2175 14.5642 13.2115C14.7559 13.2059 14.9214 13.2728 15.0606 13.412C15.1999 13.5397 15.2725 13.6991 15.2785 13.8904C15.2841 14.0821 15.2173 14.2475 15.078 14.3868L12.5715 16.8933C12.5019 16.963 12.4264 17.0122 12.3452 17.0409C12.264 17.0702 12.1769 17.0848 12.0841 17.0848C11.9913 17.0848 11.9042 17.0702 11.823 17.0409C11.7418 17.0122 11.6664 16.963 11.5967 16.8933L9.09021 14.3868C8.96256 14.2592 8.89874 14.0997 8.89874 13.9085C8.89874 13.7168 8.96836 13.5513 9.10761 13.412C9.24687 13.2844 9.40933 13.2175 9.59499 13.2115C9.78066 13.2059 9.94312 13.2728 10.0824 13.412L11.3879 14.7175ZM7.21031 6.11874L5.90483 7.42422C5.75397 7.57508 5.58571 7.6447 5.40004 7.6331C5.21437 7.6215 5.05772 7.55187 4.93007 7.42422C4.79082 7.28497 4.72119 7.11949 4.72119 6.92779C4.72119 6.73655 4.78501 6.57711 4.91266 6.44946L7.41919 3.94294C7.48882 3.87331 7.56424 3.82388 7.64547 3.79463C7.7267 3.76585 7.81374 3.75146 7.90657 3.75146C7.9994 3.75146 8.08644 3.76585 8.16767 3.79463C8.2489 3.82388 8.32432 3.87331 8.39395 3.94294L10.9005 6.44946C11.0281 6.57711 11.0919 6.73957 11.0919 6.93684C11.0919 7.13412 11.0281 7.29658 10.9005 7.42422C10.7612 7.56347 10.596 7.6331 10.4047 7.6331C10.213 7.6331 10.0476 7.56347 9.90831 7.42422L8.60283 6.11874V10.4181C8.60283 10.6154 8.53622 10.7806 8.403 10.9139C8.26932 11.0475 8.10384 11.1144 7.90657 11.1144C7.7093 11.1144 7.54405 11.0475 7.41083 10.9139C7.27715 10.7806 7.21031 10.6154 7.21031 10.4181V6.11874Z"
957
+ fill="#155DFC"
958
+ />
959
+ </svg>
960
+ <div
961
+ className="text-sm font-extrabold leading-[130%]"
962
+ style={{ color: "#155DFC" }}
963
+ >
964
+ {isBalanceVisible ? displayUsdValue : "••••••"}
965
+ </div>
966
+ </button>
967
+ </div>
968
+
969
+ {/* Arrow Down Between Sections */}
970
+ <div className="relative">
971
+ <div className="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 p-1.5 trails-border-radius-button trails-bg-tertiary transition-colors border-2 border-white dark:border-gray-800 z-1">
972
+ <ArrowDown
973
+ className="w-5 h-5 text-gray-900 dark:text-white"
974
+ strokeWidth={2.5}
975
+ />
976
+ </div>
977
+ </div>
978
+ </div>
979
+
980
+ {/* Destination Section */}
981
+ <div className="space-y-1 trails-bg-secondary trails-border-radius-container p-3">
982
+ {/* Recipient Button */}
983
+ <div className="relative">
984
+ {toRecipient ? (
985
+ /* Display only - recipient is fixed */
986
+ <div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2">
987
+ <div className="text-left">
988
+ <div className="font-medium trails-text-primary text-sm">
989
+ Recipient address
990
+ </div>
991
+ </div>
992
+ <div className="flex items-center space-x-2">
993
+ <div
994
+ className="font-medium trails-text-primary text-sm"
995
+ title={toRecipient}
996
+ >
997
+ {truncateAddress(toRecipient)}
998
+ </div>
999
+ <Identicon
1000
+ value={toRecipient}
1001
+ size={24}
1002
+ className="flex-shrink-0"
1003
+ />
1004
+ </div>
1005
+ </div>
1006
+ ) : (
1007
+ /* Interactive button - user can select recipient */
1008
+ <button
1009
+ type="button"
1010
+ onClick={() => setCurrentScreen("recipients")}
1011
+ className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
1012
+ >
1013
+ <div className="text-left">
1014
+ <div className="font-medium trails-text-primary text-sm">
1015
+ Recipient address
1016
+ </div>
1017
+ </div>
1018
+ <div className="flex items-center space-x-2">
1019
+ {selectedRecipient ? (
1020
+ <>
1021
+ <div
1022
+ className="font-medium trails-text-primary text-sm"
1023
+ title={selectedRecipient}
1024
+ >
1025
+ {truncateAddress(selectedRecipient)}
1026
+ </div>
1027
+ <Identicon
1028
+ value={selectedRecipient}
1029
+ size={24}
1030
+ className="flex-shrink-0"
1031
+ />
1032
+ </>
1033
+ ) : (
1034
+ <>
1035
+ <div className="font-medium text-gray-900 dark:text-white text-sm">
1036
+ Select address
1037
+ </div>
1038
+ <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
1039
+ <div className="w-4 h-4 rounded-full bg-gray-400 dark:bg-gray-500"></div>
1040
+ </div>
1041
+ </>
1042
+ )}
1043
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
1044
+ </div>
1045
+ </button>
1046
+ )}
1047
+ </div>
1048
+
1049
+ {/* Destination Token Selector */}
1050
+ <div className="relative">
1051
+ {toToken ? (
1052
+ /* Display only - destination token is fixed */
1053
+ <div className="w-full flex items-center justify-between space-x-3 trails-bg-secondary trails-border-radius-list-button px-4 py-2">
1054
+ <div className="text-left">
1055
+ <div className="font-medium trails-text-primary text-sm whitespace-nowrap">
1056
+ Receiving amount
1057
+ </div>
1058
+ </div>
1059
+ <div className="flex items-center space-x-2">
1060
+ {selectedDestToken ? (
1061
+ <>
1062
+ <div className="font-medium trails-text-primary text-sm">
1063
+ {isLoadingQuote ? (
1064
+ <div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
1065
+ ) : prepareSendQuote?.destinationAmountFormatted ? (
1066
+ <span className="whitespace-nowrap">
1067
+ {prepareSendQuote.destinationAmountFormatted}{" "}
1068
+ {selectedDestToken.symbol}
1069
+ </span>
1070
+ ) : (
1071
+ ""
1072
+ )}
1073
+ </div>
1074
+ <TokenImage
1075
+ symbol={selectedDestToken.symbol}
1076
+ imageUrl={selectedDestToken.imageUrl}
1077
+ chainId={globalDestinationToken?.chainId || toChainId}
1078
+ size={24}
1079
+ />
1080
+ </>
1081
+ ) : (
1082
+ <>
1083
+ <div className="font-medium text-gray-900 dark:text-white text-sm">
1084
+ Select token
1085
+ </div>
1086
+ <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
1087
+ <Search className="w-4 h-4 text-gray-400" />
1088
+ </div>
1089
+ </>
1090
+ )}
1091
+ </div>
1092
+ </div>
1093
+ ) : (
1094
+ /* Interactive button - user can select destination token */
1095
+ <button
1096
+ type="button"
1097
+ onClick={() => setShowDestinationTokenSelector(true)}
1098
+ className="w-full flex items-center justify-between space-x-3 hover:trails-hover-bg hover:bg-gray-50 dark:hover:bg-gray-700 trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
1099
+ >
1100
+ <div className="text-left">
1101
+ <div className="font-medium trails-text-primary text-sm whitespace-nowrap">
1102
+ Receiving amount
1103
+ </div>
1104
+ </div>
1105
+
1106
+ <div className="flex items-center space-x-2">
1107
+ {selectedDestToken ? (
1108
+ <>
1109
+ <div className="font-medium trails-text-primary text-sm">
1110
+ {isLoadingQuote ? (
1111
+ <div className="h-4 w-20 bg-gray-200 dark:bg-gray-700 rounded animate-pulse"></div>
1112
+ ) : prepareSendQuote?.destinationAmountFormatted ? (
1113
+ <span className="whitespace-nowrap">
1114
+ {prepareSendQuote.destinationAmountFormatted}{" "}
1115
+ {selectedDestToken.symbol}
1116
+ </span>
1117
+ ) : (
1118
+ ""
1119
+ )}
1120
+ </div>
1121
+ <TokenImage
1122
+ symbol={selectedDestToken.symbol}
1123
+ imageUrl={selectedDestToken.imageUrl}
1124
+ chainId={globalDestinationToken?.chainId || toChainId}
1125
+ size={24}
1126
+ />
1127
+ </>
1128
+ ) : (
1129
+ <>
1130
+ <div className="font-medium text-gray-900 dark:text-white text-sm">
1131
+ Select token
1132
+ </div>
1133
+ <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
1134
+ <Search className="w-4 h-4 text-gray-400" />
1135
+ </div>
1136
+ </>
1137
+ )}
1138
+ <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
1139
+ </div>
1140
+ </button>
1141
+ )}
1142
+ </div>
1143
+ </div>
1144
+
1145
+ <div className="py-2"></div>
1146
+
1147
+ {/* Exchange/Contract Warning */}
1148
+ <RefundWarning
1149
+ fundMethod={fundMethod}
1150
+ isSenderContractOnOrigin={isSenderContractOnOrigin}
1151
+ isSenderContractOnDestination={isSenderContractOnDestination}
1152
+ />
1153
+
1154
+ {/* Error Display */}
1155
+
1156
+ <ErrorDisplay
1157
+ errorPrettified={quoteErrorPrettified}
1158
+ error={quoteError}
1159
+ severity="warning"
1160
+ />
1161
+
1162
+ {prepareSendQuote?.noSufficientBalance ? (
1163
+ <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
1164
+ <div className="flex items-center space-x-2">
1165
+ <svg
1166
+ className="w-4 h-4 text-amber-500 flex-shrink-0"
1167
+ fill="none"
1168
+ stroke="currentColor"
1169
+ viewBox="0 0 24 24"
1170
+ aria-hidden="true"
1171
+ >
1172
+ <path
1173
+ strokeLinecap="round"
1174
+ strokeLinejoin="round"
1175
+ strokeWidth={2}
1176
+ d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.964-.833-2.732 0L3.732 16.5c-.77.833.192 2.5 1.732 2.5z"
1177
+ />
1178
+ </svg>
1179
+ <p className="text-sm text-amber-600 dark:text-amber-400">
1180
+ Insufficient balance to complete this transaction
1181
+ </p>
1182
+ </div>
1183
+ </div>
1184
+ ) : null}
1185
+
1186
+ {/* Fund Button */}
1187
+ <form onSubmit={handleSubmit}>
1188
+ <button
1189
+ type="submit"
1190
+ disabled={
1191
+ !tokenAmountForBackend ||
1192
+ parseFloat(tokenAmountForBackend) <= 0 ||
1193
+ isSubmitting ||
1194
+ isLoadingQuote ||
1195
+ !isValidRecipient ||
1196
+ !destinationTokenAddress ||
1197
+ !isValidCustomToken ||
1198
+ prepareSendQuote?.noSufficientBalance ||
1199
+ isSameTokenWithoutCustomCalldata ||
1200
+ !prepareSendQuote
1201
+ }
1202
+ className="w-full font-semibold py-4 px-4 trails-border-radius-button transition-colors bg-blue-500 hover:bg-blue-600 disabled:bg-gray-300 text-white disabled:text-gray-500 disabled:cursor-not-allowed cursor-pointer relative"
1203
+ >
1204
+ {isSubmitting ? (
1205
+ <div className="flex items-center justify-center">
1206
+ <Loader2 className="w-5 h-5 animate-spin mr-2" />
1207
+ <span>{buttonText}</span>
1208
+ </div>
1209
+ ) : isSameTokenWithoutCustomCalldata ? (
1210
+ "Select Different Tokens"
1211
+ ) : prepareSendQuote?.noSufficientBalance ? (
1212
+ "Insufficient Balance"
1213
+ ) : !selectedRecipient ? (
1214
+ "Select recipient address"
1215
+ ) : !selectedDestToken ? (
1216
+ "Select destination token"
1217
+ ) : !tokenAmountForBackend ||
1218
+ parseFloat(tokenAmountForBackend) <= 0 ? (
1219
+ "Enter an amount"
1220
+ ) : (
1221
+ buttonText || "Fund"
1222
+ )}
1223
+ </button>
1224
+ </form>
1225
+
1226
+ {/* Quote Details */}
1227
+ {prepareSendQuote && (
1228
+ <div className="space-y-2">
1229
+ <QuoteDetails quote={prepareSendQuote} showContent={true} />
1230
+ </div>
1231
+ )}
1232
+ </div>
1233
+ )
1234
+ }
1235
+
1236
+ export default Fund