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