0xtrails 0.1.13 → 0.2.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 (216) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +11 -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/{proxyCaller.d.ts → balanceInjector.d.ts} +5 -4
  7. package/dist/balanceInjector.d.ts.map +1 -0
  8. package/dist/{ccip-D3gTQONK.js → ccip-D6ToCrWc.js} +12 -12
  9. package/dist/cctp.d.ts.map +1 -1
  10. package/dist/cctpqueue.d.ts +3 -3
  11. package/dist/cctpqueue.d.ts.map +1 -1
  12. package/dist/chains.d.ts.map +1 -1
  13. package/dist/config.d.ts +17 -3
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/constants.d.ts +5 -4
  16. package/dist/constants.d.ts.map +1 -1
  17. package/dist/contractUtils.d.ts +2 -0
  18. package/dist/contractUtils.d.ts.map +1 -1
  19. package/dist/customChains.d.ts +24 -0
  20. package/dist/customChains.d.ts.map +1 -0
  21. package/dist/{index-CnUM7lKf.js → index-BqgeTLL8.js} +34072 -30146
  22. package/dist/index.d.ts +5 -3
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +411 -400
  25. package/dist/intentEntrypoint.d.ts +96 -0
  26. package/dist/intentEntrypoint.d.ts.map +1 -0
  27. package/dist/intents.d.ts +5 -3
  28. package/dist/intents.d.ts.map +1 -1
  29. package/dist/metaTxnMonitor.d.ts.map +1 -1
  30. package/dist/morpho.d.ts.map +1 -1
  31. package/dist/pools.d.ts +3 -1
  32. package/dist/pools.d.ts.map +1 -1
  33. package/dist/prepareSend.d.ts +8 -2
  34. package/dist/prepareSend.d.ts.map +1 -1
  35. package/dist/prices.d.ts +1 -1
  36. package/dist/prices.d.ts.map +1 -1
  37. package/dist/relaySdk.d.ts.map +1 -1
  38. package/dist/relayer.d.ts.map +1 -1
  39. package/dist/toast.d.ts +9 -0
  40. package/dist/toast.d.ts.map +1 -0
  41. package/dist/tokenBalances.d.ts +6 -2
  42. package/dist/tokenBalances.d.ts.map +1 -1
  43. package/dist/tokens.d.ts.map +1 -1
  44. package/dist/trails.d.ts +6 -5
  45. package/dist/trails.d.ts.map +1 -1
  46. package/dist/trailsClient.d.ts +12 -0
  47. package/dist/trailsClient.d.ts.map +1 -0
  48. package/dist/transactions.d.ts +8 -0
  49. package/dist/transactions.d.ts.map +1 -1
  50. package/dist/wallets.d.ts.map +1 -1
  51. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  52. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  53. package/dist/widget/components/AccountSettings.d.ts +7 -0
  54. package/dist/widget/components/AccountSettings.d.ts.map +1 -0
  55. package/dist/widget/components/ChainList.d.ts +0 -1
  56. package/dist/widget/components/ChainList.d.ts.map +1 -1
  57. package/dist/widget/components/ClassicSwap.d.ts +46 -0
  58. package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
  59. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  60. package/dist/widget/components/ConnectedWallets.d.ts +9 -0
  61. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
  62. package/dist/widget/components/DebugMenu.d.ts.map +1 -1
  63. package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
  64. package/dist/widget/components/DebugToast.d.ts +3 -0
  65. package/dist/widget/components/DebugToast.d.ts.map +1 -0
  66. package/dist/widget/components/Earn.d.ts.map +1 -1
  67. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  68. package/dist/widget/components/Fund.d.ts +44 -0
  69. package/dist/widget/components/Fund.d.ts.map +1 -0
  70. package/dist/widget/components/Identicon.d.ts +9 -0
  71. package/dist/widget/components/Identicon.d.ts.map +1 -0
  72. package/dist/widget/components/Pay.d.ts +46 -0
  73. package/dist/widget/components/Pay.d.ts.map +1 -0
  74. package/dist/widget/components/Receive.d.ts.map +1 -1
  75. package/dist/widget/components/RecentTokens.d.ts.map +1 -1
  76. package/dist/widget/components/Recipients.d.ts +9 -0
  77. package/dist/widget/components/Recipients.d.ts.map +1 -0
  78. package/dist/widget/components/RefundWarning.d.ts +9 -0
  79. package/dist/widget/components/RefundWarning.d.ts.map +1 -0
  80. package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
  81. package/dist/widget/components/Swap.d.ts.map +1 -1
  82. package/dist/widget/components/SwapSettings.d.ts +1 -5
  83. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  84. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  85. package/dist/widget/components/ThemeSyncer.d.ts +6 -0
  86. package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
  87. package/dist/widget/components/Toast.d.ts +24 -0
  88. package/dist/widget/components/Toast.d.ts.map +1 -0
  89. package/dist/widget/components/TokenList.d.ts.map +1 -1
  90. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  91. package/dist/widget/components/TruncatedAddress.d.ts +2 -0
  92. package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
  93. package/dist/widget/components/UserPreferences.d.ts +7 -0
  94. package/dist/widget/components/UserPreferences.d.ts.map +1 -0
  95. package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
  96. package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
  97. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  98. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  99. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  100. package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
  101. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  102. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
  103. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
  104. package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
  105. package/dist/widget/hooks/usePayMessage.d.ts +34 -0
  106. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
  107. package/dist/widget/hooks/useRecipients.d.ts +17 -0
  108. package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
  109. package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
  110. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
  111. package/dist/widget/hooks/useSendForm.d.ts +2 -0
  112. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  113. package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
  114. package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
  115. package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
  116. package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
  117. package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
  118. package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
  119. package/dist/widget/hooks/useTheme.d.ts +14 -0
  120. package/dist/widget/hooks/useTheme.d.ts.map +1 -0
  121. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  122. package/dist/widget/index.js +2 -2
  123. package/dist/widget/widget.d.ts +9 -0
  124. package/dist/widget/widget.d.ts.map +1 -1
  125. package/package.json +29 -28
  126. package/src/aave.ts +6 -1
  127. package/src/analytics.ts +103 -53
  128. package/src/apiClient.ts +1 -1
  129. package/src/{proxyCaller.ts → balanceInjector.ts} +22 -17
  130. package/src/cctp.ts +6 -2
  131. package/src/cctpqueue.ts +7 -7
  132. package/src/chains.ts +8 -0
  133. package/src/config.ts +40 -9
  134. package/src/constants.ts +11 -8
  135. package/src/contractUtils.ts +33 -2
  136. package/src/customChains.ts +24 -0
  137. package/src/index.ts +11 -1
  138. package/src/intentEntrypoint.ts +253 -0
  139. package/src/intents.ts +87 -54
  140. package/src/metaTxnMonitor.ts +1 -0
  141. package/src/morpho.ts +13 -2
  142. package/src/pools.ts +68 -86
  143. package/src/prepareSend.ts +437 -207
  144. package/src/prices.ts +51 -7
  145. package/src/relaySdk.ts +6 -4
  146. package/src/relayer.ts +2 -0
  147. package/src/toast.ts +110 -0
  148. package/src/tokenBalances.ts +112 -20
  149. package/src/tokens.ts +70 -7
  150. package/src/trails.ts +80 -77
  151. package/src/trailsClient.ts +45 -0
  152. package/src/transactions.ts +27 -35
  153. package/src/umd.tsx +1 -1
  154. package/src/wallets.ts +2 -1
  155. package/src/widget/assets/sequence-logo.svg +15 -0
  156. package/src/widget/compiled.css +2 -2
  157. package/src/widget/components/AccountActionsDropdown.tsx +18 -159
  158. package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
  159. package/src/widget/components/AccountSettings.tsx +96 -0
  160. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  161. package/src/widget/components/ChainList.tsx +10 -20
  162. package/src/widget/components/ClassicSwap.tsx +923 -0
  163. package/src/widget/components/ConfigDisplay.tsx +8 -5
  164. package/src/widget/components/ConnectedWallets.tsx +260 -0
  165. package/src/widget/components/DebugMenu.tsx +2 -0
  166. package/src/widget/components/DebugScreensList.tsx +3 -0
  167. package/src/widget/components/DebugToast.tsx +63 -0
  168. package/src/widget/components/Earn.tsx +108 -116
  169. package/src/widget/components/EarnPools.tsx +2 -4
  170. package/src/widget/components/EarnPoolsFilters.tsx +6 -6
  171. package/src/widget/components/Fund.tsx +1245 -0
  172. package/src/widget/components/FundMethods.tsx +1 -1
  173. package/src/widget/components/FundSendForm.tsx +1 -1
  174. package/src/widget/components/Identicon.tsx +158 -0
  175. package/src/widget/components/Pay.tsx +1088 -0
  176. package/src/widget/components/PaySendForm.tsx +1 -1
  177. package/src/widget/components/QuoteDetails.tsx +1 -1
  178. package/src/widget/components/Receipt.tsx +1 -1
  179. package/src/widget/components/Receive.tsx +4 -2
  180. package/src/widget/components/RecentTokens.tsx +2 -1
  181. package/src/widget/components/Recipients.tsx +448 -0
  182. package/src/widget/components/RefundWarning.tsx +61 -0
  183. package/src/widget/components/ScreenHeader.tsx +1 -1
  184. package/src/widget/components/SimpleSwap.tsx +74 -58
  185. package/src/widget/components/Swap.tsx +35 -853
  186. package/src/widget/components/SwapSettings.tsx +5 -11
  187. package/src/widget/components/ThemeProvider.tsx +32 -0
  188. package/src/widget/components/ThemeSyncer.tsx +47 -0
  189. package/src/widget/components/Toast.tsx +315 -0
  190. package/src/widget/components/TokenList.tsx +2 -34
  191. package/src/widget/components/TokenSelector.tsx +3 -3
  192. package/src/widget/components/TransactionDetails.tsx +153 -13
  193. package/src/widget/components/TruncatedAddress.tsx +5 -1
  194. package/src/widget/components/UserPreferences.tsx +156 -0
  195. package/src/widget/components/WalletList.tsx +1 -1
  196. package/src/widget/hooks/useBalanceVisible.tsx +40 -2
  197. package/src/widget/hooks/useCheckout.ts +13 -0
  198. package/src/widget/hooks/useCurrentScreen.tsx +3 -0
  199. package/src/widget/hooks/useDebugScreens.ts +12 -2
  200. package/src/widget/hooks/useDefaultTokenSelection.tsx +475 -0
  201. package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
  202. package/src/widget/hooks/usePayMessage.tsx +370 -0
  203. package/src/widget/hooks/useRecipients.ts +168 -0
  204. package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
  205. package/src/widget/hooks/useSendForm.ts +179 -26
  206. package/src/widget/hooks/useSwapAmount.tsx +50 -0
  207. package/src/widget/hooks/useSwapSettings.tsx +100 -0
  208. package/src/widget/hooks/useTargetAmount.ts +23 -0
  209. package/src/widget/hooks/useTheme.tsx +80 -0
  210. package/src/widget/hooks/useTokenList.ts +20 -11
  211. package/src/widget/index.css +45 -21
  212. package/src/widget/widget.tsx +164 -68
  213. package/dist/address.d.ts +0 -2
  214. package/dist/address.d.ts.map +0 -1
  215. package/dist/proxyCaller.d.ts.map +0 -1
  216. package/src/address.ts +0 -6
@@ -0,0 +1,923 @@
1
+ import { ChevronDown, Loader2, ArrowDown } from "lucide-react"
2
+ import type React from "react"
3
+ import { useCallback, useEffect, useRef, useState, useMemo } from "react"
4
+ import type { Account, WalletClient } from "viem"
5
+ import type { TransactionState } from "../../transactions.js"
6
+ import type { OnCompleteProps, Token } from "../hooks/useSendForm.js"
7
+ import type { SupportedToken } from "../../tokens.js"
8
+ import { useSendForm } from "../hooks/useSendForm.js"
9
+ import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
10
+ import { FeeOptions } from "./FeeOptions.js"
11
+ import { TokenImage } from "./TokenImage.js"
12
+ import { QuoteDetails } from "./QuoteDetails.js"
13
+ import { type PrepareSendQuote, TradeType } from "../../prepareSend.js"
14
+ import { getChainInfo } from "../../chains.js"
15
+ import { formatUsdAmountDisplay } from "../../tokenBalances.js"
16
+ import { MINIMUM_USD_AMOUNT_FOR_SWAP } from "../../constants.js"
17
+ import { ScreenHeader } from "./ScreenHeader.js"
18
+ import { TokenSelector } from "./TokenSelector.js"
19
+ import { ErrorDisplay } from "./ErrorDisplay.js"
20
+ import { useMode } from "../hooks/useMode.js"
21
+ import { logger } from "../../logger.js"
22
+ import { SwapSettings } from "./SwapSettings.js"
23
+ import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
24
+ import { useDestinationSelectedToken } from "../hooks/useDestinationSelectedToken.js"
25
+ import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
26
+ import { useDefaultTokenSelection } from "../hooks/useDefaultTokenSelection.js"
27
+
28
+ interface ClassicSwapProps {
29
+ selectedToken: Token | null
30
+ onSend: (amount: string, recipient: string) => void
31
+ onBack?: () => void
32
+ onConfirm: () => void
33
+ onComplete: (result: OnCompleteProps) => void
34
+ account: Account
35
+ toRecipient?: string
36
+ toAmount?: string
37
+ toChainId?: number
38
+ toToken?: string
39
+ toCalldata?: string
40
+ walletClient: WalletClient
41
+ onTransactionStateChange: (transactionStates: TransactionState[]) => void
42
+ onError: (error: Error | string | null) => void
43
+ onWaitingForWalletConfirm: (props: PrepareSendQuote) => void
44
+ paymasterUrls?: Array<{ chainId: number; url: string }>
45
+ gasless?: boolean
46
+ setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
47
+ quoteProvider?: string
48
+ fundMethod?: string
49
+ onNavigateToMeshConnect?: (
50
+ props: {
51
+ toTokenSymbol: string
52
+ toTokenAmount: string
53
+ toChainId: number
54
+ toRecipientAddress: string
55
+ },
56
+ quote?: PrepareSendQuote | null,
57
+ ) => void
58
+ onAmountUpdate?: (amount: string) => void
59
+ checkoutOnHandlers?: CheckoutOnHandlers
60
+ recentTokens?: SupportedToken[]
61
+ onRecentTokenSelect?: (token: SupportedToken) => void
62
+ onTrackToken?: (token: any) => void
63
+ }
64
+
65
+ export const ClassicSwap: React.FC<ClassicSwapProps> = ({
66
+ selectedToken: initialSelectedToken,
67
+ onSend,
68
+ onBack,
69
+ onConfirm,
70
+ onComplete,
71
+ account,
72
+ toAmount,
73
+ toChainId,
74
+ toToken,
75
+ toCalldata,
76
+ walletClient,
77
+ onTransactionStateChange,
78
+ onError,
79
+ onWaitingForWalletConfirm,
80
+ paymasterUrls,
81
+ gasless,
82
+ setWalletConfirmRetryHandler,
83
+ quoteProvider,
84
+ fundMethod,
85
+ onNavigateToMeshConnect,
86
+ onAmountUpdate,
87
+ checkoutOnHandlers,
88
+ recentTokens,
89
+ onRecentTokenSelect,
90
+ onTrackToken,
91
+ }) => {
92
+ const { mode } = useMode()
93
+ const { isBalanceVisible } = useBalanceVisible()
94
+ const { selectedToken: originToken, setSelectedToken: setOriginToken } =
95
+ useOriginSelectedToken()
96
+ const {
97
+ selectedToken: destinationToken,
98
+ setSelectedToken: setDestinationToken,
99
+ } = useDestinationSelectedToken()
100
+ const [isFlipped, setIsFlipped] = useState(false)
101
+
102
+ // Use new default token selection hook
103
+ const {
104
+ defaultOriginToken,
105
+ defaultDestinationToken,
106
+ isLoading: isLoadingDefaults,
107
+ } = useDefaultTokenSelection()
108
+
109
+ const [originChainId, setOriginChainId] = useState<number | null | undefined>(
110
+ initialSelectedToken?.chainId || originToken?.chainId,
111
+ )
112
+ const [tradeType, setTradeType] = useState<TradeType>(TradeType.EXACT_INPUT)
113
+ const [sellAmount, setSellAmount] = useState("")
114
+ const [buyAmount, setBuyAmount] = useState("")
115
+ const [lastInputType, setLastInputType] = useState<"sell" | "buy">("sell")
116
+
117
+ const {
118
+ amount,
119
+ amountRaw,
120
+ amountUsdDisplay,
121
+ balanceFormatted,
122
+ handleSubmit,
123
+ isSubmitting,
124
+ isLoadingQuote,
125
+ isTokenDropdownOpen,
126
+ selectedDestinationChain,
127
+ selectedDestToken,
128
+ setAmount,
129
+ setSelectedDestinationChain,
130
+ setSelectedDestToken,
131
+ buttonText,
132
+ selectedFeeToken,
133
+ setSelectedFeeToken,
134
+ feeOptions,
135
+ setIsTokenDropdownOpen,
136
+ toAmountDisplay,
137
+ destinationTokenAddress,
138
+ isValidCustomToken,
139
+ prepareSendQuote,
140
+ quoteError,
141
+ quoteErrorPrettified,
142
+ isSameTokenWithoutCustomCalldata,
143
+ } = useSendForm({
144
+ account,
145
+ toAmount: tradeType === TradeType.EXACT_OUTPUT ? buyAmount : toAmount,
146
+ toRecipient: account.address,
147
+ toChainId,
148
+ toToken,
149
+ toCalldata,
150
+ walletClient,
151
+ onTransactionStateChange,
152
+ onError,
153
+ onWaitingForWalletConfirm,
154
+ paymasterUrls,
155
+ gasless,
156
+ onConfirm,
157
+ onComplete,
158
+ onSend,
159
+ selectedToken: originToken as any,
160
+ setWalletConfirmRetryHandler,
161
+ tradeType: tradeType,
162
+ quoteProvider,
163
+ fundMethod,
164
+ mode,
165
+ onNavigateToMeshConnect,
166
+ checkoutOnHandlers,
167
+ })
168
+
169
+ // Update amount based on last input type
170
+ useEffect(() => {
171
+ if (lastInputType === "sell") {
172
+ setAmount(sellAmount)
173
+ } else {
174
+ setAmount(buyAmount)
175
+ }
176
+ }, [sellAmount, buyAmount, lastInputType, setAmount])
177
+
178
+ // Handle sell amount input changes with decimal validation
179
+ const handleSellAmountChange = useCallback((value: string) => {
180
+ // Validate decimal places (max 8 decimals)
181
+ const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
182
+ if (!decimalMatch && value !== "") {
183
+ return // Don't update if more than 8 decimals
184
+ }
185
+ setTradeType(TradeType.EXACT_INPUT)
186
+ setSellAmount(value)
187
+ setBuyAmount("")
188
+ setLastInputType("sell")
189
+ }, [])
190
+
191
+ // Handle buy amount input changes with decimal validation
192
+ const handleBuyAmountChange = useCallback((value: string) => {
193
+ // Validate decimal places (max 8 decimals)
194
+ const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
195
+ if (!decimalMatch && value !== "") {
196
+ return // Don't update if more than 8 decimals
197
+ }
198
+ setTradeType(TradeType.EXACT_OUTPUT)
199
+ setBuyAmount(value)
200
+ setSellAmount("")
201
+ setLastInputType("buy")
202
+ }, [])
203
+
204
+ // Handle buy input focus - clear field when user wants to type
205
+ const handleBuyInputFocus = useCallback(() => {
206
+ // When focusing on buy field and it's in EXACT_INPUT mode (showing calculated value), switch to EXACT_OUTPUT mode
207
+ if (tradeType === TradeType.EXACT_INPUT && !buyAmount) {
208
+ setBuyAmount("")
209
+ setTradeType(TradeType.EXACT_OUTPUT)
210
+ setLastInputType("buy")
211
+ }
212
+ }, [tradeType, buyAmount])
213
+
214
+ // Update amounts when quote is received
215
+ useEffect(() => {
216
+ if (!prepareSendQuote) return
217
+
218
+ if (
219
+ tradeType === TradeType.EXACT_OUTPUT &&
220
+ prepareSendQuote.originAmountDisplay
221
+ ) {
222
+ setSellAmount(prepareSendQuote.originAmountDisplay)
223
+ } else if (
224
+ tradeType === TradeType.EXACT_INPUT &&
225
+ prepareSendQuote.destinationAmountDisplay
226
+ ) {
227
+ setBuyAmount(prepareSendQuote.destinationAmountDisplay)
228
+ }
229
+ }, [prepareSendQuote, tradeType])
230
+
231
+ // Handle percentage button clicks
232
+ const handlePercentageClick = useCallback(
233
+ (percentage: number) => {
234
+ if (!originToken || !balanceFormatted) return
235
+
236
+ // Parse the balance and calculate percentage
237
+ const balance = parseFloat(balanceFormatted)
238
+ if (Number.isNaN(balance)) return
239
+
240
+ const amount = (balance * percentage) / 100
241
+ setTradeType(TradeType.EXACT_INPUT)
242
+ setSellAmount(amount.toFixed(6))
243
+ setBuyAmount("")
244
+ setLastInputType("sell")
245
+ },
246
+ [originToken, balanceFormatted],
247
+ )
248
+
249
+ // Call onAmountUpdate when amountRaw changes
250
+ useEffect(() => {
251
+ if (onAmountUpdate) {
252
+ onAmountUpdate(amountRaw)
253
+ }
254
+ }, [amountRaw, onAmountUpdate])
255
+
256
+ // Auto-select origin token using new hook
257
+ useEffect(() => {
258
+ if (!originToken && !isLoadingDefaults && defaultOriginToken) {
259
+ logger.console.log(
260
+ "[trails-sdk] Auto-selecting origin token:",
261
+ defaultOriginToken,
262
+ )
263
+ setOriginToken(defaultOriginToken as any)
264
+ setOriginChainId(defaultOriginToken.chainId)
265
+ }
266
+ }, [originToken, isLoadingDefaults, defaultOriginToken, setOriginToken])
267
+
268
+ // Auto-select destination token using new hook
269
+ // Note: In swap mode, we still auto-select a destination even if toToken is provided,
270
+ // since the user needs a starting point to modify
271
+ useEffect(() => {
272
+ if (!destinationToken && !isLoadingDefaults && defaultDestinationToken) {
273
+ logger.console.log(
274
+ "[trails-sdk] Auto-selecting destination token:",
275
+ defaultDestinationToken,
276
+ )
277
+ setDestinationToken(defaultDestinationToken as any)
278
+ setSelectedDestToken(defaultDestinationToken as any)
279
+
280
+ // Also set the destination chain
281
+ const chainInfo = getChainInfo(defaultDestinationToken.chainId)
282
+ if (chainInfo) {
283
+ setSelectedDestinationChain(chainInfo)
284
+ }
285
+ }
286
+ }, [
287
+ destinationToken,
288
+ isLoadingDefaults,
289
+ defaultDestinationToken,
290
+ setDestinationToken,
291
+ setSelectedDestToken,
292
+ setSelectedDestinationChain,
293
+ ])
294
+
295
+ // Auto-focus the input field when component mounts
296
+ useEffect(() => {
297
+ if (inputRef.current) {
298
+ inputRef.current.focus()
299
+ }
300
+ }, [])
301
+
302
+ const originTokenDropdownRef = useRef<HTMLDivElement>(null)
303
+ const tokenDropdownRef = useRef<HTMLInputElement>(null)
304
+ const inputRef = useRef<HTMLInputElement>(null)
305
+
306
+ const [isOriginTokenDropdownOpen, setIsOriginTokenDropdownOpen] =
307
+ useState(false)
308
+
309
+ useEffect(() => {
310
+ if (selectedDestToken) {
311
+ setSelectedDestinationChain(
312
+ getChainInfo((selectedDestToken as any)?.chainId as any) as any,
313
+ )
314
+ }
315
+ }, [selectedDestToken, setSelectedDestinationChain])
316
+
317
+ // Sync destination token from hook with useSendForm
318
+ // This ensures the destination token auto-selection flows into useSendForm
319
+ useEffect(() => {
320
+ if (destinationToken) {
321
+ logger.console.log(
322
+ "[trails-sdk] Syncing destination token to useSendForm:",
323
+ destinationToken,
324
+ )
325
+ setSelectedDestToken(destinationToken as any)
326
+ }
327
+ }, [destinationToken, setSelectedDestToken])
328
+
329
+ useEffect(() => {
330
+ const handleClickOutside = (event: MouseEvent) => {
331
+ if (
332
+ tokenDropdownRef.current &&
333
+ !tokenDropdownRef.current.contains(event.target as Node)
334
+ ) {
335
+ setIsTokenDropdownOpen(false)
336
+ }
337
+ if (
338
+ originTokenDropdownRef.current &&
339
+ !originTokenDropdownRef.current.contains(event.target as Node)
340
+ ) {
341
+ setIsOriginTokenDropdownOpen(false)
342
+ }
343
+ }
344
+
345
+ if (isTokenDropdownOpen || isOriginTokenDropdownOpen) {
346
+ document.addEventListener("click", handleClickOutside)
347
+ return () => document.removeEventListener("click", handleClickOutside)
348
+ }
349
+ }, [isTokenDropdownOpen, isOriginTokenDropdownOpen, setIsTokenDropdownOpen])
350
+
351
+ // Handle flip functionality
352
+ const handleFlip = () => {
353
+ if (!selectedDestinationChain || !originToken || !selectedDestToken) return
354
+
355
+ setIsFlipped(!isFlipped)
356
+
357
+ // Store current values
358
+ const tempOriginToken = originToken
359
+ const tempOriginChainId = originChainId
360
+ const tempBuyAmount = buyAmount || toAmountDisplay || ""
361
+
362
+ // Swap origin and destination using hooks
363
+ setOriginToken(selectedDestToken as any)
364
+ setDestinationToken(tempOriginToken as any)
365
+ setOriginChainId((selectedDestToken as any)?.chainId as any)
366
+ setSelectedDestToken(tempOriginToken as any)
367
+
368
+ // Update destination chain
369
+ const newChain = getChainInfo(tempOriginChainId as any)
370
+ if (newChain) {
371
+ setSelectedDestinationChain(newChain)
372
+ }
373
+
374
+ // When flipping, always set the current visible amount as the new sell amount
375
+ if (tradeType === TradeType.EXACT_INPUT) {
376
+ // If we were in EXACT_INPUT, use the buy amount (from quote) as the new sell amount
377
+ setSellAmount(tempBuyAmount)
378
+ setBuyAmount("")
379
+ setTradeType(TradeType.EXACT_INPUT)
380
+ setLastInputType("sell")
381
+ } else {
382
+ // If we were in EXACT_OUTPUT, use the current buy amount as the new sell amount
383
+ setSellAmount(tempBuyAmount)
384
+ setBuyAmount("")
385
+ setTradeType(TradeType.EXACT_INPUT)
386
+ setLastInputType("sell")
387
+ }
388
+ }
389
+
390
+ // Dynamic font size based on input length - matching SimpleSwap.tsx
391
+ const sellInputStyles = useMemo(() => {
392
+ const inputLength = sellAmount.length
393
+ let fontSize: string
394
+
395
+ if (inputLength > 12) {
396
+ fontSize = "1rem"
397
+ } else if (inputLength > 9) {
398
+ fontSize = "1.25rem"
399
+ } else if (inputLength > 6) {
400
+ fontSize = "1.5rem"
401
+ } else if (inputLength > 3) {
402
+ fontSize = "1.75rem"
403
+ } else {
404
+ fontSize = "2rem"
405
+ }
406
+
407
+ return {
408
+ fontSize,
409
+ transition: "all 0.1s ease-in-out",
410
+ }
411
+ }, [sellAmount.length])
412
+
413
+ const buyInputStyles = useMemo(() => {
414
+ const inputValue =
415
+ tradeType === TradeType.EXACT_OUTPUT ? buyAmount : toAmountDisplay || ""
416
+ const inputLength = inputValue.length
417
+ let fontSize: string
418
+
419
+ if (inputLength > 12) {
420
+ fontSize = "1rem"
421
+ } else if (inputLength > 9) {
422
+ fontSize = "1.25rem"
423
+ } else if (inputLength > 6) {
424
+ fontSize = "1.5rem"
425
+ } else if (inputLength > 3) {
426
+ fontSize = "1.75rem"
427
+ } else {
428
+ fontSize = "2rem"
429
+ }
430
+
431
+ return {
432
+ fontSize,
433
+ transition: "all 0.1s ease-in-out",
434
+ }
435
+ }, [tradeType, buyAmount, toAmountDisplay])
436
+
437
+ return (
438
+ <div className="space-y-2">
439
+ <ScreenHeader
440
+ onBack={onBack}
441
+ headerContent="Swap"
442
+ headerContentAlign="left"
443
+ showAccountActions={true}
444
+ rightSideContent={<SwapSettings />}
445
+ />
446
+
447
+ <form onSubmit={handleSubmit} className="space-y-1">
448
+ {/* Input Section - Amount + Token Selection */}
449
+ <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 trails-focus-border-secondary min-h-[120px] flex flex-col">
450
+ {/* Sell Label and Percentage Buttons */}
451
+ <div className="flex justify-between items-center mb-2">
452
+ <div className="text-sm font-medium trails-text-secondary text-left">
453
+ Sell
454
+ </div>
455
+
456
+ {/* Percentage Buttons */}
457
+ {originToken && (
458
+ <div className="flex space-x-1 opacity-0 group-hover:opacity-100 transition-opacity duration-200">
459
+ <button
460
+ type="button"
461
+ onClick={() => handlePercentageClick(25)}
462
+ 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"
463
+ >
464
+ 25%
465
+ </button>
466
+ <button
467
+ type="button"
468
+ onClick={() => handlePercentageClick(50)}
469
+ 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"
470
+ >
471
+ 50%
472
+ </button>
473
+ <button
474
+ type="button"
475
+ onClick={() => handlePercentageClick(75)}
476
+ 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"
477
+ >
478
+ 75%
479
+ </button>
480
+ <button
481
+ type="button"
482
+ onClick={() => handlePercentageClick(100)}
483
+ 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"
484
+ >
485
+ Max
486
+ </button>
487
+ </div>
488
+ )}
489
+ </div>
490
+
491
+ <div className="flex items-center space-x-2 flex-1">
492
+ {/* Amount Input */}
493
+ <div className="flex-1">
494
+ <div className="flex items-center space-x-2">
495
+ <input
496
+ ref={inputRef}
497
+ id="amount"
498
+ type="text"
499
+ value={sellAmount}
500
+ onChange={(e) => handleSellAmountChange(e.target.value)}
501
+ placeholder={`0 ${originToken?.symbol || ""}`}
502
+ className={`w-full bg-transparent font-bold trails-text-primary placeholder:trails-text-muted border-none outline-none ${
503
+ isLoadingQuote && tradeType === TradeType.EXACT_OUTPUT
504
+ ? "animate-pulse"
505
+ : ""
506
+ }`}
507
+ style={sellInputStyles}
508
+ readOnly={
509
+ tradeType === TradeType.EXACT_OUTPUT && isLoadingQuote
510
+ }
511
+ />
512
+ {isLoadingQuote && tradeType === TradeType.EXACT_OUTPUT && (
513
+ <div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
514
+ )}
515
+ </div>
516
+ </div>
517
+
518
+ {/* Token Selection Button */}
519
+ <div className="relative" ref={originTokenDropdownRef}>
520
+ <button
521
+ type="button"
522
+ onClick={() =>
523
+ setIsOriginTokenDropdownOpen(!isOriginTokenDropdownOpen)
524
+ }
525
+ className={`flex items-center space-x-2 trails-border-radius-input px-2.5 py-1.5 border transition-colors cursor-pointer ${
526
+ originToken
527
+ ? "trails-bg-card hover:trails-hover-bg trails-border-primary"
528
+ : "bg-blue-500 hover:bg-blue-600 border-blue-500 text-white"
529
+ }`}
530
+ >
531
+ {originToken ? (
532
+ <>
533
+ <TokenImage
534
+ symbol={originToken.symbol}
535
+ imageUrl={originToken.imageUrl}
536
+ chainId={originChainId}
537
+ size={20}
538
+ />
539
+ <span className="font-medium trails-text-primary text-sm">
540
+ {originToken.symbol}
541
+ </span>
542
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
543
+ </>
544
+ ) : (
545
+ <>
546
+ <span className="font-medium text-sm text-white">
547
+ Select Token
548
+ </span>
549
+ <ChevronDown className="w-3.5 h-3.5 text-white" />
550
+ </>
551
+ )}
552
+ </button>
553
+
554
+ {/* Token Selector Modal */}
555
+ {isOriginTokenDropdownOpen && (
556
+ <div className="absolute right-0 top-full mt-2 w-80 trails-bg-card rounded-lg shadow-lg border trails-border-primary max-h-80 overflow-hidden z-10 -translate-y-28">
557
+ <div className="p-2">
558
+ <TokenSelector
559
+ onTokenSelect={(token: any) => {
560
+ const formattedToken = {
561
+ ...token,
562
+ decimals:
563
+ token.contractInfo?.decimals || token.decimals,
564
+ contractInfo: {
565
+ decimals:
566
+ token.contractInfo?.decimals || token.decimals,
567
+ contractAddress: token.contractAddress,
568
+ symbol: token.symbol,
569
+ name: token.name,
570
+ },
571
+ } as any
572
+ setOriginToken(formattedToken)
573
+ logger.console.log(
574
+ "[trails-sdk] selected origin token",
575
+ token,
576
+ )
577
+ setOriginChainId(token.chainId)
578
+ setIsOriginTokenDropdownOpen(false)
579
+ // Track the token selection
580
+ onTrackToken?.(token)
581
+ }}
582
+ onError={onError}
583
+ fundMethod={fundMethod}
584
+ showContinueButton={false}
585
+ compactMode={false}
586
+ recentTokens={recentTokens}
587
+ onRecentTokenSelect={(token) => {
588
+ const formattedToken = {
589
+ ...token,
590
+ decimals: token.decimals,
591
+ contractInfo: {
592
+ decimals: token.decimals,
593
+ contractAddress: token.contractAddress,
594
+ symbol: token.symbol,
595
+ name: token.name,
596
+ },
597
+ } as any
598
+ setOriginToken(formattedToken)
599
+ setOriginChainId(token.chainId)
600
+ setIsOriginTokenDropdownOpen(false)
601
+ onRecentTokenSelect?.(token)
602
+ }}
603
+ />
604
+ </div>
605
+ </div>
606
+ )}
607
+ </div>
608
+ </div>
609
+
610
+ {/* Bottom Info Row for sell */}
611
+ <div className="mt-2 flex justify-between items-center">
612
+ {/* USD Amount */}
613
+ {originToken?.symbol && (
614
+ <div className="text-xs trails-text-muted">
615
+ ≈{" "}
616
+ {tradeType === TradeType.EXACT_INPUT
617
+ ? amountUsdDisplay || "$0.00"
618
+ : isLoadingQuote
619
+ ? "$0.00"
620
+ : prepareSendQuote?.originAmountUsdDisplay || "$0.00"}
621
+ </div>
622
+ )}
623
+
624
+ {/* Origin Token Balance */}
625
+ {originToken && (
626
+ <button
627
+ type="button"
628
+ className="text-xs trails-text-muted cursor-pointer hover:trails-hover-text transition-colors bg-transparent border-none p-0"
629
+ onClick={() => {
630
+ if (balanceFormatted) {
631
+ const balance = parseFloat(balanceFormatted)
632
+ if (!Number.isNaN(balance)) {
633
+ setTradeType(TradeType.EXACT_INPUT)
634
+ setSellAmount(balance.toFixed(6))
635
+ setBuyAmount("")
636
+ setLastInputType("sell")
637
+ }
638
+ }
639
+ }}
640
+ onKeyDown={(e) => {
641
+ if (e.key === "Enter" || e.key === " ") {
642
+ e.preventDefault()
643
+ if (balanceFormatted) {
644
+ const balance = parseFloat(balanceFormatted)
645
+ if (!Number.isNaN(balance)) {
646
+ setTradeType(TradeType.EXACT_INPUT)
647
+ setSellAmount(balance.toFixed(6))
648
+ setBuyAmount("")
649
+ setLastInputType("sell")
650
+ }
651
+ }
652
+ }
653
+ }}
654
+ title="Click to use full balance"
655
+ >
656
+ Balance:{" "}
657
+ {isBalanceVisible
658
+ ? `${balanceFormatted || "0.00"} ${originToken.symbol}`
659
+ : "••••••"}
660
+ </button>
661
+ )}
662
+ </div>
663
+ </div>
664
+
665
+ {/* Flip Button - Absolutely Positioned */}
666
+ <div className="relative">
667
+ <button
668
+ type="button"
669
+ onClick={handleFlip}
670
+ 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 hover:trails-hover-bg transition-colors cursor-pointer border-2 border-white dark:border-gray-800"
671
+ >
672
+ <ArrowDown
673
+ className="w-5 h-5 text-gray-900 dark:text-white"
674
+ strokeWidth={2.5}
675
+ />
676
+ </button>
677
+ </div>
678
+
679
+ {/* Output Section - Amount + Token Selection */}
680
+ <div className="trails-bg-secondary trails-bg-secondary-hover trails-border-radius-container p-3 transition-all duration-200 border border-transparent focus-within:!bg-white dark:focus-within:!bg-gray-800 trails-focus-border-secondary min-h-[120px] flex flex-col">
681
+ {/* Buy Label */}
682
+ <div className="text-sm font-medium trails-text-secondary mb-2 text-left">
683
+ Buy
684
+ </div>
685
+
686
+ <div className="flex items-center space-x-2 flex-1">
687
+ {/* Output Amount */}
688
+ <div className="flex-1">
689
+ <div className="flex items-center space-x-2">
690
+ <input
691
+ type="text"
692
+ value={
693
+ tradeType === TradeType.EXACT_OUTPUT
694
+ ? buyAmount
695
+ : toAmountDisplay || ""
696
+ }
697
+ onChange={(e) => handleBuyAmountChange(e.target.value)}
698
+ onFocus={handleBuyInputFocus}
699
+ placeholder={`0 ${selectedDestToken?.symbol || ""}`}
700
+ className={`w-full bg-transparent font-bold placeholder:trails-text-muted border-none outline-none ${
701
+ !amount
702
+ ? "text-gray-400 dark:text-gray-500"
703
+ : "trails-text-primary"
704
+ } ${isLoadingQuote && tradeType === TradeType.EXACT_INPUT ? "animate-pulse" : ""}`}
705
+ style={buyInputStyles}
706
+ readOnly={
707
+ tradeType === TradeType.EXACT_INPUT && isLoadingQuote
708
+ }
709
+ />
710
+ {isLoadingQuote && tradeType === TradeType.EXACT_INPUT && (
711
+ <div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
712
+ )}
713
+ </div>
714
+ </div>
715
+
716
+ {/* Destination Token Selection */}
717
+ <div className="relative" ref={tokenDropdownRef}>
718
+ <button
719
+ type="button"
720
+ onClick={() => setIsTokenDropdownOpen(!isTokenDropdownOpen)}
721
+ className={`flex items-center space-x-2 trails-border-radius-input px-2.5 py-1.5 border transition-colors cursor-pointer ${
722
+ selectedDestToken
723
+ ? "trails-bg-card hover:trails-hover-bg trails-border-primary"
724
+ : "bg-blue-500 hover:bg-blue-600 border-blue-500 text-white"
725
+ }`}
726
+ >
727
+ {selectedDestToken ? (
728
+ <>
729
+ <TokenImage
730
+ symbol={selectedDestToken.symbol}
731
+ imageUrl={selectedDestToken.imageUrl}
732
+ chainId={(selectedDestToken as any)?.chainId}
733
+ size={20}
734
+ />
735
+ <span className="font-medium trails-text-primary text-sm">
736
+ {selectedDestToken.symbol}
737
+ </span>
738
+ <ChevronDown className="w-3.5 h-3.5 trails-text-muted" />
739
+ </>
740
+ ) : (
741
+ <>
742
+ <span className="font-medium text-sm text-white">
743
+ Select Token
744
+ </span>
745
+ <ChevronDown className="w-3.5 h-3.5 text-white" />
746
+ </>
747
+ )}
748
+ </button>
749
+
750
+ {/* Token Selector Modal */}
751
+ {isTokenDropdownOpen && (
752
+ <div className="absolute right-0 bottom-full mb-1 w-80 trails-bg-card rounded-lg shadow-lg border trails-border-primary max-h-80 overflow-hidden z-10 translate-y-32">
753
+ <div className="p-2">
754
+ <TokenSelector
755
+ onTokenSelect={(token: any) => {
756
+ const formattedToken = {
757
+ ...token,
758
+ decimals:
759
+ token.contractInfo?.decimals || token.decimals,
760
+ contractInfo: {
761
+ decimals:
762
+ token.contractInfo?.decimals || token.decimals,
763
+ },
764
+ } as any
765
+ setDestinationToken(formattedToken)
766
+ setSelectedDestToken(formattedToken)
767
+ logger.console.log(
768
+ "[trails-sdk] selected destination token",
769
+ token,
770
+ )
771
+
772
+ setIsTokenDropdownOpen(false)
773
+ // Track the token selection
774
+ onTrackToken?.(token)
775
+ }}
776
+ onError={onError}
777
+ fundMethod={fundMethod}
778
+ allSupportedTokens={true}
779
+ showContinueButton={false}
780
+ compactMode={false}
781
+ recentTokens={recentTokens}
782
+ onRecentTokenSelect={(token) => {
783
+ const formattedToken = {
784
+ ...token,
785
+ decimals: token.decimals,
786
+ contractInfo: {
787
+ decimals: token.decimals,
788
+ },
789
+ } as any
790
+ setDestinationToken(formattedToken)
791
+ setSelectedDestToken(formattedToken)
792
+ setIsTokenDropdownOpen(false)
793
+ onRecentTokenSelect?.(token)
794
+ }}
795
+ />
796
+ </div>
797
+ </div>
798
+ )}
799
+ </div>
800
+ </div>
801
+
802
+ {/* Bottom Info Row */}
803
+ <div className="mt-2 flex justify-between items-center">
804
+ {/* Destination Amount USD from Quote */}
805
+ {(prepareSendQuote?.destinationAmountUsdDisplay ||
806
+ (isLoadingQuote && tradeType === TradeType.EXACT_INPUT)) && (
807
+ <div className="text-xs trails-text-muted">
808
+ ≈{" "}
809
+ {isLoadingQuote && tradeType === TradeType.EXACT_INPUT
810
+ ? "$0.00"
811
+ : prepareSendQuote?.destinationAmountUsdDisplay}
812
+ </div>
813
+ )}
814
+ </div>
815
+ </div>
816
+
817
+ {/* Fee Options */}
818
+ <FeeOptions
819
+ options={feeOptions}
820
+ selectedOption={selectedFeeToken ?? undefined}
821
+ onSelect={setSelectedFeeToken}
822
+ />
823
+
824
+ {/* Warning Messages - Show only one at a time */}
825
+ <ErrorDisplay
826
+ errorPrettified={quoteErrorPrettified}
827
+ error={quoteError}
828
+ severity="warning"
829
+ />
830
+
831
+ {prepareSendQuote?.noSufficientBalance ? (
832
+ <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
833
+ <div className="flex items-center space-x-2">
834
+ <svg
835
+ className="w-4 h-4 text-amber-500 flex-shrink-0"
836
+ fill="none"
837
+ stroke="currentColor"
838
+ viewBox="0 0 24 24"
839
+ aria-hidden="true"
840
+ >
841
+ <path
842
+ strokeLinecap="round"
843
+ strokeLinejoin="round"
844
+ strokeWidth={2}
845
+ 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"
846
+ />
847
+ </svg>
848
+ <p className="text-sm text-amber-600 dark:text-amber-400">
849
+ Insufficient balance to complete this transaction. Required{" "}
850
+ {prepareSendQuote?.originAmountDisplay} {originToken?.symbol}
851
+ </p>
852
+ </div>
853
+ </div>
854
+ ) : prepareSendQuote?.minimumNotMet ? (
855
+ <div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
856
+ <div className="flex items-center space-x-2">
857
+ <svg
858
+ className="w-4 h-4 text-amber-500 flex-shrink-0"
859
+ fill="none"
860
+ stroke="currentColor"
861
+ viewBox="0 0 24 24"
862
+ aria-hidden="true"
863
+ >
864
+ <path
865
+ strokeLinecap="round"
866
+ strokeLinejoin="round"
867
+ strokeWidth={2}
868
+ 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"
869
+ />
870
+ </svg>
871
+ <p className="text-sm text-amber-600 dark:text-amber-400">
872
+ Please enter an amount above{" "}
873
+ {formatUsdAmountDisplay(MINIMUM_USD_AMOUNT_FOR_SWAP)} otherwise
874
+ transfer may fail
875
+ </p>
876
+ </div>
877
+ </div>
878
+ ) : null}
879
+
880
+ {/* Quote Details */}
881
+ {prepareSendQuote && (
882
+ <div className="space-y-2">
883
+ <QuoteDetails
884
+ quote={prepareSendQuote}
885
+ showContent={true}
886
+ swapMode={true}
887
+ />
888
+ </div>
889
+ )}
890
+
891
+ <button
892
+ type="submit"
893
+ disabled={
894
+ !amount ||
895
+ isSubmitting ||
896
+ !destinationTokenAddress ||
897
+ !isValidCustomToken ||
898
+ isLoadingQuote ||
899
+ !prepareSendQuote ||
900
+ prepareSendQuote?.noSufficientBalance ||
901
+ isSameTokenWithoutCustomCalldata
902
+ }
903
+ 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"
904
+ >
905
+ {isSubmitting ? (
906
+ <div className="flex items-center justify-center">
907
+ <Loader2 className="w-5 h-5 animate-spin mr-2 trails-text-muted" />
908
+ <span>{buttonText}</span>
909
+ </div>
910
+ ) : isSameTokenWithoutCustomCalldata ? (
911
+ "Select Different Tokens"
912
+ ) : prepareSendQuote?.noSufficientBalance ? (
913
+ "Insufficient Balance"
914
+ ) : (
915
+ buttonText
916
+ )}
917
+ </button>
918
+ </form>
919
+ </div>
920
+ )
921
+ }
922
+
923
+ export default ClassicSwap