0xtrails 0.2.4 → 0.2.6

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 (212) hide show
  1. package/dist/aave.d.ts +8 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/abortController.d.ts +8 -0
  4. package/dist/abortController.d.ts.map +1 -0
  5. package/dist/{ccip-BlV1Mry3.js → ccip-Xjh9d1gb.js} +7 -7
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/constants.d.ts +3 -0
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/error.d.ts +1 -0
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/estimate.d.ts +52 -0
  13. package/dist/estimate.d.ts.map +1 -1
  14. package/dist/fees.d.ts +19 -0
  15. package/dist/fees.d.ts.map +1 -0
  16. package/dist/{index-BNWCIGfQ.js → index-BnhdZ8Ho.js} +76406 -75798
  17. package/dist/index.js +726 -520
  18. package/dist/intents.d.ts +40 -0
  19. package/dist/intents.d.ts.map +1 -1
  20. package/dist/metaTxnMonitor.d.ts +3 -3
  21. package/dist/metaTxnMonitor.d.ts.map +1 -1
  22. package/dist/metaTxns.d.ts +3 -3
  23. package/dist/metaTxns.d.ts.map +1 -1
  24. package/dist/morpho.d.ts +8 -0
  25. package/dist/morpho.d.ts.map +1 -1
  26. package/dist/prepareSend.d.ts +19 -75
  27. package/dist/prepareSend.d.ts.map +1 -1
  28. package/dist/queryParams.d.ts.map +1 -1
  29. package/dist/relayer.d.ts +6 -6
  30. package/dist/relayer.d.ts.map +1 -1
  31. package/dist/sequenceWallet.d.ts +2 -2
  32. package/dist/sequenceWallet.d.ts.map +1 -1
  33. package/dist/tokens.d.ts.map +1 -1
  34. package/dist/transactions.d.ts +4 -2
  35. package/dist/transactions.d.ts.map +1 -1
  36. package/dist/wallets.d.ts.map +1 -1
  37. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  38. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
  39. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
  40. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  41. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  42. package/dist/widget/components/ClassicSwap.d.ts +4 -2
  43. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  44. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  45. package/dist/widget/components/ConnectedWallets.d.ts +4 -0
  46. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  47. package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
  48. package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
  49. package/dist/widget/components/Earn.d.ts +2 -2
  50. package/dist/widget/components/Earn.d.ts.map +1 -1
  51. package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
  52. package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
  53. package/dist/widget/components/FeeBreakdown.d.ts +9 -0
  54. package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
  55. package/dist/widget/components/Fund.d.ts +2 -2
  56. package/dist/widget/components/Fund.d.ts.map +1 -1
  57. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  58. package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +13 -7
  59. package/dist/widget/components/FundSwap.d.ts.map +1 -0
  60. package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
  61. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
  62. package/dist/widget/components/Identicon.d.ts.map +1 -1
  63. package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
  64. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  65. package/dist/widget/components/Modal.d.ts.map +1 -1
  66. package/dist/widget/components/Pay.d.ts +2 -2
  67. package/dist/widget/components/Pay.d.ts.map +1 -1
  68. package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
  69. package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
  70. package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +14 -36
  71. package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
  72. package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +19 -10
  73. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
  74. package/dist/widget/components/QuoteDetails.d.ts +1 -0
  75. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  76. package/dist/widget/components/Receipt.d.ts.map +1 -1
  77. package/dist/widget/components/Receive.d.ts.map +1 -1
  78. package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
  79. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
  80. package/dist/widget/components/Recipients.d.ts.map +1 -1
  81. package/dist/widget/components/RequiredPropsError.d.ts +8 -0
  82. package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
  83. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  84. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  85. package/dist/widget/components/Swap.d.ts +3 -2
  86. package/dist/widget/components/Swap.d.ts.map +1 -1
  87. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  88. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  89. package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
  90. package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
  91. package/dist/widget/components/TokenImage.d.ts +1 -0
  92. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  93. package/dist/widget/components/TokenList.d.ts.map +1 -1
  94. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  95. package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
  96. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
  97. package/dist/widget/components/Tooltip.d.ts +9 -0
  98. package/dist/widget/components/Tooltip.d.ts.map +1 -0
  99. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  100. package/dist/widget/components/WaasFeeOptions.d.ts +9 -0
  101. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
  102. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  103. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  104. package/dist/widget/components/WalletList.d.ts.map +1 -1
  105. package/dist/widget/css/compiled.css +2 -0
  106. package/dist/widget/css/index.css +554 -0
  107. package/dist/widget/hooks/useBack.d.ts +1 -0
  108. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  109. package/dist/widget/hooks/useCheckout.d.ts +1 -1
  110. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  111. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  112. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  113. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
  114. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  115. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
  116. package/dist/widget/hooks/useQuote.d.ts +83 -0
  117. package/dist/widget/hooks/useQuote.d.ts.map +1 -0
  118. package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
  119. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
  120. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
  121. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  122. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  123. package/dist/widget/index.js +2 -2
  124. package/dist/widget/widget.d.ts +9 -4
  125. package/dist/widget/widget.d.ts.map +1 -1
  126. package/package.json +18 -12
  127. package/src/aave.ts +32 -0
  128. package/src/abortController.ts +35 -0
  129. package/src/config.ts +12 -4
  130. package/src/constants.ts +5 -0
  131. package/src/error.ts +19 -1
  132. package/src/estimate.ts +416 -5
  133. package/src/fees.ts +199 -0
  134. package/src/intents.ts +161 -11
  135. package/src/metaTxnMonitor.ts +3 -3
  136. package/src/metaTxns.ts +3 -5
  137. package/src/morpho.ts +32 -0
  138. package/src/prepareSend.ts +714 -550
  139. package/src/queryParams.ts +2 -1
  140. package/src/relayer.ts +11 -11
  141. package/src/sequenceWallet.ts +2 -2
  142. package/src/tokens.ts +7 -1
  143. package/src/trails.ts +3 -3
  144. package/src/transactions.ts +62 -18
  145. package/src/wallets.ts +8 -0
  146. package/src/widget/compiled.css +2 -2
  147. package/src/widget/components/AccountActionsDropdown.tsx +3 -13
  148. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
  149. package/src/widget/components/AccountSettings.tsx +48 -54
  150. package/src/widget/components/ChainFilterDropdown.tsx +24 -3
  151. package/src/widget/components/ClassicSwap.tsx +131 -213
  152. package/src/widget/components/ConnectWallet.tsx +8 -38
  153. package/src/widget/components/ConnectedWallets.tsx +132 -77
  154. package/src/widget/components/DynamicInputStyles.tsx +76 -0
  155. package/src/widget/components/Earn.tsx +82 -593
  156. package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
  157. package/src/widget/components/FeeBreakdown.tsx +155 -0
  158. package/src/widget/components/Fund.tsx +41 -108
  159. package/src/widget/components/FundMethods.tsx +82 -159
  160. package/src/widget/components/FundSwap.tsx +52 -0
  161. package/src/widget/components/FundingMethodSelectorButton.tsx +70 -0
  162. package/src/widget/components/Identicon.tsx +164 -95
  163. package/src/widget/components/MeshConnectExchanges.tsx +2 -15
  164. package/src/widget/components/Modal.tsx +0 -8
  165. package/src/widget/components/Pay.tsx +214 -237
  166. package/src/widget/components/PercentageMaxButtons.tsx +77 -0
  167. package/src/widget/components/PoolDeposit.tsx +569 -0
  168. package/src/widget/components/PoolWithdraw.tsx +884 -0
  169. package/src/widget/components/PriceImpactWarning.tsx +1 -1
  170. package/src/widget/components/QuoteDetails.tsx +43 -12
  171. package/src/widget/components/Receipt.tsx +16 -2
  172. package/src/widget/components/Receive.tsx +0 -2
  173. package/src/widget/components/RecipientSelectorButton.tsx +44 -0
  174. package/src/widget/components/Recipients.tsx +63 -157
  175. package/src/widget/components/RequiredPropsError.tsx +33 -0
  176. package/src/widget/components/ScreenHeader.tsx +62 -34
  177. package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
  178. package/src/widget/components/Swap.tsx +4 -45
  179. package/src/widget/components/SwapSettings.tsx +2 -14
  180. package/src/widget/components/ThemeProvider.tsx +2 -1
  181. package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
  182. package/src/widget/components/TokenImage.tsx +22 -5
  183. package/src/widget/components/TokenList.tsx +0 -1
  184. package/src/widget/components/TokenSelector.tsx +63 -53
  185. package/src/widget/components/TokenSelectorButton.tsx +98 -0
  186. package/src/widget/components/Tooltip.tsx +51 -0
  187. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  188. package/src/widget/components/UserPreferences.tsx +6 -24
  189. package/src/widget/components/WaasFeeOptions.tsx +450 -0
  190. package/src/widget/components/WalletConfirmation.tsx +76 -14
  191. package/src/widget/components/WalletConnect.tsx +93 -29
  192. package/src/widget/components/WalletList.tsx +4 -2
  193. package/src/widget/hooks/useBack.tsx +2 -0
  194. package/src/widget/hooks/useCheckout.ts +36 -20
  195. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  196. package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
  197. package/src/widget/hooks/usePayMessage.tsx +86 -11
  198. package/src/widget/hooks/useQuote.ts +413 -0
  199. package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
  200. package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
  201. package/src/widget/hooks/useSendForm.ts +32 -6
  202. package/src/widget/index.css +27 -0
  203. package/src/widget/widget.tsx +326 -283
  204. package/dist/widget/components/FundSendForm.d.ts.map +0 -1
  205. package/dist/widget/components/PaySendForm.d.ts.map +0 -1
  206. package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
  207. package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
  208. package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
  209. package/src/widget/components/FundSendForm.tsx +0 -903
  210. package/src/widget/components/PaySendForm.tsx +0 -869
  211. package/src/widget/components/SimpleSwap.tsx +0 -983
  212. package/src/widget/hooks/useSwapSettings.tsx +0 -100
@@ -1,983 +0,0 @@
1
- import type React from "react"
2
- import { useEffect, useRef, useState, useCallback, useMemo } from "react"
3
- import type { Account, WalletClient } from "viem"
4
- import { ChevronRight, ArrowDown, Loader2, Search } from "lucide-react"
5
- import { useOriginSelectedToken } from "../hooks/useOriginSelectedToken.js"
6
- import { useDestinationSelectedToken } from "../hooks/useDestinationSelectedToken.js"
7
- import { useBalanceVisible } from "../hooks/useBalanceVisible.js"
8
- import { useTokenList } from "../hooks/useTokenList.js"
9
- import { useRecentTokens } from "../hooks/useRecentTokens.js"
10
- import { useDefaultTokenSelection } from "../hooks/useDefaultTokenSelection.js" // Context version
11
- import { ScreenHeader } from "./ScreenHeader.js"
12
- import { TokenSelector } from "./TokenSelector.js"
13
- import { ChainList } from "./ChainList.js"
14
- import { TokenImage } from "./TokenImage.js"
15
- import { QuoteDetails } from "./QuoteDetails.js"
16
- import { ErrorDisplay } from "./ErrorDisplay.js"
17
- import { formatAmount, formatUsdAmountDisplay } from "../../tokenBalances.js"
18
- import { getChainInfo } from "../../chains.js"
19
- import { logger } from "../../logger.js"
20
- import { useSendForm } from "../hooks/useSendForm.js"
21
- import { TradeType } from "../../prepareSend.js"
22
- import type { TransactionState } from "../../transactions.js"
23
- import type { OnCompleteProps } from "../hooks/useSendForm.js"
24
- import type { CheckoutOnHandlers } from "../hooks/useCheckout.js"
25
- import { useMode } from "../hooks/useMode.js"
26
-
27
- interface SimpleSwapProps {
28
- onBack?: () => void
29
- account: Account
30
- walletClient: WalletClient
31
- onTransactionStateChange: (transactionStates: TransactionState[]) => void
32
- onError: (error: Error | string | null) => void
33
- onWaitingForWalletConfirm: (props: any) => void
34
- onConfirm: () => void
35
- onComplete: (result: OnCompleteProps) => void
36
- onSend: (amount: string, recipient: string) => void
37
- paymasterUrls?: Array<{ chainId: number; url: string }>
38
- gasless?: boolean
39
- setWalletConfirmRetryHandler: (handler: () => Promise<void>) => void
40
- quoteProvider?: string
41
- fundMethod?: string
42
- checkoutOnHandlers?: CheckoutOnHandlers
43
- showHeader?: boolean
44
- onTokenSelectorVisibilityChange?: (isVisible: boolean) => void
45
- }
46
-
47
- export const SimpleSwap: React.FC<SimpleSwapProps> = ({
48
- onBack,
49
- account,
50
- walletClient,
51
- onTransactionStateChange,
52
- onError,
53
- onWaitingForWalletConfirm,
54
- onConfirm,
55
- onComplete,
56
- onSend,
57
- paymasterUrls,
58
- gasless,
59
- setWalletConfirmRetryHandler,
60
- quoteProvider,
61
- fundMethod,
62
- checkoutOnHandlers,
63
- showHeader = true,
64
- onTokenSelectorVisibilityChange,
65
- }) => {
66
- const { mode } = useMode()
67
- const { selectedToken, setSelectedToken } = useOriginSelectedToken()
68
- const { setSelectedToken: setDestinationTokenFromHook } =
69
- useDestinationSelectedToken()
70
- const { isBalanceVisible } = useBalanceVisible()
71
- const { recentTokens, addRecentToken } = useRecentTokens(account?.address)
72
-
73
- // Use new default token selection hook
74
- const { defaultOriginToken, isLoading: isLoadingDefaults } =
75
- useDefaultTokenSelection()
76
-
77
- // Get sorted tokens to auto-select the highest USD value token
78
- const { filteredTokensFormatted } = useTokenList({
79
- onContinue: () => {}, // Not used for auto-selection
80
- onError: () => {}, // Not used for auto-selection
81
- fundMethod: undefined,
82
- allSupportedTokens: false,
83
- })
84
- const [isInputTypeUsd, setIsInputTypeUsd] = useState(false)
85
- const [tokenAmountForBackend, setTokenAmountForBackend] = useState("")
86
- const [inputDisplayValue, setInputDisplayValue] = useState("")
87
- const [_destinationToken, setDestinationToken] = useState<any>(null)
88
- const [showSourceTokenSelector, setShowSourceTokenSelector] = useState(false)
89
- const [showDestinationTokenSelector, setShowDestinationTokenSelector] =
90
- useState(false)
91
- const [showSourceChainList, setShowSourceChainList] = useState(false)
92
- const [showDestinationChainList, setShowDestinationChainList] =
93
- useState(false)
94
- const [showPercentageButtons, setShowPercentageButtons] = useState(false)
95
- const inputRef = useRef<HTMLInputElement>(null)
96
-
97
- // Use the same useSendForm hook as Swap.tsx for real quote functionality
98
- const {
99
- amount,
100
- amountUsdDisplay,
101
- balanceFormatted,
102
- handleSubmit,
103
- isSubmitting,
104
- isLoadingQuote,
105
- selectedDestToken,
106
- setAmount,
107
- setSelectedDestToken,
108
- setSelectedDestinationChain,
109
- buttonText,
110
- destinationTokenAddress,
111
- isValidCustomToken,
112
- prepareSendQuote,
113
- quoteError,
114
- quoteErrorPrettified,
115
- isSameTokenWithoutCustomCalldata,
116
- } = useSendForm({
117
- account,
118
- toAmount: undefined,
119
- toRecipient: account.address,
120
- toChainId: undefined, // Let the hook manage this internally
121
- toToken: undefined, // Let the hook manage this internally
122
- toCalldata: undefined,
123
- walletClient,
124
- onTransactionStateChange,
125
- onError,
126
- onWaitingForWalletConfirm,
127
- paymasterUrls,
128
- gasless,
129
- onConfirm,
130
- onComplete,
131
- onSend,
132
- selectedToken: selectedToken as any,
133
- setWalletConfirmRetryHandler,
134
- tradeType: TradeType.EXACT_INPUT,
135
- quoteProvider,
136
- fundMethod,
137
- mode,
138
- onNavigateToMeshConnect: undefined,
139
- checkoutOnHandlers,
140
- })
141
-
142
- // Get real token prices from useSendForm (no more mock prices)
143
- // The useSendForm hook provides real amountUsdDisplay
144
-
145
- // Auto-select source token using new hook
146
- useEffect(() => {
147
- if (!selectedToken && !isLoadingDefaults && defaultOriginToken) {
148
- logger.console.log(
149
- "[trails-sdk] Auto-selecting source token:",
150
- defaultOriginToken,
151
- )
152
- setSelectedToken(defaultOriginToken as any)
153
-
154
- // Add to recent tokens
155
- addRecentToken(defaultOriginToken as any)
156
- }
157
- }, [
158
- selectedToken,
159
- isLoadingDefaults,
160
- defaultOriginToken,
161
- setSelectedToken,
162
- addRecentToken,
163
- ])
164
-
165
- // Auto-focus input field when selectedToken is available
166
- useEffect(() => {
167
- if (selectedToken && inputRef.current) {
168
- inputRef.current.focus()
169
- }
170
- }, [selectedToken])
171
-
172
- // Notify parent when token selector visibility changes
173
- useEffect(() => {
174
- const isTokenSelectorVisible =
175
- showSourceTokenSelector ||
176
- showDestinationTokenSelector ||
177
- showSourceChainList ||
178
- showDestinationChainList
179
- onTokenSelectorVisibilityChange?.(isTokenSelectorVisible)
180
- }, [
181
- showSourceTokenSelector,
182
- showDestinationTokenSelector,
183
- showSourceChainList,
184
- showDestinationChainList,
185
- onTokenSelectorVisibilityChange,
186
- ])
187
-
188
- // Sync display value with token amount only when mode changes (not during typing)
189
- const [lastInputMode, setLastInputMode] = useState(isInputTypeUsd)
190
-
191
- useEffect(() => {
192
- // Only sync when mode actually changes, not during normal typing
193
- if (lastInputMode !== isInputTypeUsd && tokenAmountForBackend) {
194
- const tokenAmount = parseFloat(tokenAmountForBackend) || 0
195
- if (isInputTypeUsd) {
196
- // Show USD with max 2 decimals - we'll calculate from amountUsdDisplay if available
197
- // For now, just show the token amount since we don't have a reliable USD conversion
198
- setInputDisplayValue(Number(tokenAmount.toFixed(8)).toString())
199
- } else {
200
- // Show token with max 8 decimals
201
- setInputDisplayValue(Number(tokenAmount.toFixed(8)).toString())
202
- }
203
- setLastInputMode(isInputTypeUsd)
204
- }
205
- }, [isInputTypeUsd, tokenAmountForBackend, lastInputMode])
206
-
207
- // Handle input amount changes with 8 decimal limit and 16 char total limit
208
- const handleAmountChange = useCallback(
209
- (value: string) => {
210
- // Allow empty string
211
- if (value === "") {
212
- setInputDisplayValue("")
213
- setTokenAmountForBackend("")
214
- setAmount("")
215
- return
216
- }
217
-
218
- // Limit total length to 16 characters
219
- if (value.length > 16) {
220
- return
221
- }
222
-
223
- // Validate decimal places (max 8 decimals) and allow single decimal point
224
- const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
225
- if (!decimalMatch) {
226
- return // Don't update if invalid format
227
- }
228
-
229
- // Store the display value
230
- setInputDisplayValue(value)
231
-
232
- // Update the token amount for backend and useSendForm
233
- const sourceTokenPrice = (selectedToken as any)?.tokenPriceUsd || 0
234
-
235
- if (isInputTypeUsd && sourceTokenPrice > 0) {
236
- // Convert USD input to token amount
237
- const usdAmount = parseFloat(value) || 0
238
- const tokenAmount = usdAmount / sourceTokenPrice
239
- setTokenAmountForBackend(tokenAmount.toString())
240
- setAmount(tokenAmount.toString())
241
- } else {
242
- // Direct token amount input
243
- setTokenAmountForBackend(value)
244
- setAmount(value)
245
- }
246
- },
247
- [setAmount, isInputTypeUsd, selectedToken],
248
- )
249
-
250
- // Get display values based on input type
251
- const displayAmount = useMemo(() => {
252
- return inputDisplayValue
253
- }, [inputDisplayValue])
254
-
255
- const displayUsdValue = useMemo(() => {
256
- const sourceTokenPrice = (selectedToken as any)?.tokenPriceUsd || 0
257
-
258
- if (isInputTypeUsd && sourceTokenPrice > 0) {
259
- // Show token amount when in USD mode
260
- const tokenAmount = parseFloat(tokenAmountForBackend) || 0
261
- return `${formatAmount(tokenAmount)} ${selectedToken?.symbol || "TOKEN"}`
262
- }
263
- // Show USD amount when in token mode
264
- if (sourceTokenPrice > 0) {
265
- const tokenAmount = parseFloat(tokenAmountForBackend) || 0
266
- const usdAmount = tokenAmount * sourceTokenPrice
267
- return formatUsdAmountDisplay(usdAmount)
268
- }
269
- return amountUsdDisplay || "$0.00"
270
- }, [tokenAmountForBackend, isInputTypeUsd, selectedToken, amountUsdDisplay])
271
-
272
- // Handle percentage clicks for quick amounts
273
- const handlePercentageClick = useCallback(
274
- (percentage: number) => {
275
- if (!balanceFormatted) {
276
- return
277
- }
278
-
279
- const balance = parseFloat(balanceFormatted)
280
- if (Number.isNaN(balance)) return
281
-
282
- const amount = (balance * percentage) / 100
283
- // Cap decimals to 8 places
284
- const cappedAmount = parseFloat(amount.toFixed(8))
285
- const tokenAmountStr = cappedAmount.toString()
286
-
287
- // Update all states consistently
288
- setTokenAmountForBackend(tokenAmountStr)
289
- setAmount(tokenAmountStr)
290
-
291
- // Update display - always show token amount
292
- setInputDisplayValue(tokenAmountStr)
293
- },
294
- [balanceFormatted, setAmount],
295
- )
296
-
297
- const handleMaxClick = useCallback(() => {
298
- handlePercentageClick(100)
299
- }, [handlePercentageClick])
300
-
301
- // Handle source token selection from full-screen selector
302
- const handleSourceTokenSelectorSelect = useCallback(
303
- (token: any) => {
304
- const formattedToken = {
305
- ...token,
306
- decimals: token.contractInfo?.decimals || token.decimals,
307
- contractInfo: {
308
- decimals: token.contractInfo?.decimals || token.decimals,
309
- contractAddress: token.contractAddress,
310
- symbol: token.symbol,
311
- name: token.name,
312
- },
313
- }
314
- setSelectedToken(formattedToken)
315
- setShowSourceTokenSelector(false)
316
-
317
- // Add to recent tokens
318
- addRecentToken(formattedToken)
319
-
320
- logger.console.log(
321
- "[trails-sdk] selected source token from selector in SimpleSwap",
322
- token,
323
- )
324
- },
325
- [setSelectedToken, addRecentToken],
326
- )
327
-
328
- // Handle destination token selection from full-screen selector
329
- const handleDestinationTokenSelectorSelect = useCallback(
330
- (token: any) => {
331
- const formattedToken = {
332
- ...token,
333
- decimals: token.contractInfo?.decimals || token.decimals,
334
- contractInfo: {
335
- decimals: token.contractInfo?.decimals || token.decimals,
336
- contractAddress: token.contractAddress,
337
- symbol: token.symbol,
338
- name: token.name,
339
- },
340
- }
341
- setDestinationToken(formattedToken)
342
- setDestinationTokenFromHook(formattedToken as any)
343
- setSelectedDestToken(formattedToken as any)
344
-
345
- // Update destination chain to match the selected token's chain
346
- // This is crucial for proper same-token detection in useSendForm
347
- if (setSelectedDestinationChain && token.chainId) {
348
- const chainInfo = getChainInfo(token.chainId)
349
- if (chainInfo) {
350
- setSelectedDestinationChain(chainInfo)
351
- }
352
- }
353
-
354
- setShowDestinationTokenSelector(false)
355
-
356
- // Add to recent tokens
357
- addRecentToken(formattedToken)
358
-
359
- logger.console.log(
360
- "[trails-sdk] selected destination token from selector in SimpleSwap",
361
- token,
362
- )
363
- },
364
- // eslint-disable-next-line react-hooks/exhaustive-deps
365
- [
366
- setSelectedDestToken,
367
- setDestinationTokenFromHook,
368
- setSelectedDestinationChain,
369
- addRecentToken,
370
- ],
371
- )
372
-
373
- // Handle token swap (reverse from/to tokens)
374
- const handleTokenSwap = useCallback(() => {
375
- if (selectedToken && selectedDestToken) {
376
- const tempToken = selectedToken
377
-
378
- // Find the destination token in the user's token list to get balance info
379
- const destTokenWithBalance = filteredTokensFormatted?.find(
380
- (token) =>
381
- token.contractAddress ===
382
- (selectedDestToken as any).contractAddress &&
383
- token.chainId === (selectedDestToken as any).chainId,
384
- )
385
-
386
- // Create new source token (from destination) - use balance info if available
387
- const newSourceToken = {
388
- ...selectedDestToken,
389
- // Preserve all important properties
390
- imageUrl: (selectedDestToken as any).imageUrl,
391
- chainId: (selectedDestToken as any).chainId,
392
- contractAddress: (selectedDestToken as any).contractAddress,
393
- contractInfo: (selectedDestToken as any).contractInfo,
394
- // Use balance info from user's token list if available, otherwise keep existing
395
- balanceUsdFormatted:
396
- destTokenWithBalance?.balanceUsdFormatted ||
397
- (selectedDestToken as any).balanceUsdFormatted ||
398
- "$0.00",
399
- balance:
400
- destTokenWithBalance?.balance ||
401
- (selectedDestToken as any).balance ||
402
- "0",
403
- tokenPriceUsd:
404
- destTokenWithBalance?.tokenPriceUsd ||
405
- (selectedDestToken as any).tokenPriceUsd ||
406
- 0,
407
- balanceUsd:
408
- destTokenWithBalance?.balanceUsd ||
409
- (selectedDestToken as any).balanceUsd ||
410
- "0",
411
- }
412
-
413
- const newDestToken = {
414
- ...tempToken,
415
- // Preserve all important properties that might be lost
416
- imageUrl: (tempToken as any).imageUrl,
417
- balanceUsdFormatted: (tempToken as any).balanceUsdFormatted,
418
- balance: (tempToken as any).balance,
419
- tokenPriceUsd: (tempToken as any).tokenPriceUsd,
420
- balanceUsd: (tempToken as any).balanceUsd,
421
- chainId: (tempToken as any).chainId,
422
- contractAddress: (tempToken as any).contractAddress,
423
- contractInfo: (tempToken as any).contractInfo,
424
- }
425
-
426
- setSelectedToken(newSourceToken as any)
427
- setDestinationToken(newDestToken)
428
- setDestinationTokenFromHook(newDestToken as any)
429
- setSelectedDestToken(newDestToken as any)
430
-
431
- // Update destination chain to match the swapped token's chain
432
- if (setSelectedDestinationChain && newDestToken.chainId) {
433
- const chainInfo = getChainInfo(newDestToken.chainId)
434
- if (chainInfo) {
435
- setSelectedDestinationChain(chainInfo)
436
- }
437
- }
438
-
439
- // Add both tokens to recent tokens when swapping
440
- addRecentToken(newSourceToken as any)
441
- addRecentToken(newDestToken as any)
442
- logger.console.log("[trails-sdk] swapped tokens in SimpleSwap", {
443
- originalSource: tempToken,
444
- originalDest: selectedDestToken,
445
- destTokenWithBalance,
446
- newSourceToken,
447
- newDestToken,
448
- })
449
- }
450
- }, [
451
- selectedToken,
452
- selectedDestToken,
453
- setSelectedToken,
454
- setSelectedDestToken,
455
- setDestinationTokenFromHook,
456
- filteredTokensFormatted,
457
- setSelectedDestinationChain,
458
- addRecentToken,
459
- ])
460
-
461
- // Handle recent token selection
462
- const handleRecentTokenSelect = useCallback(
463
- (token: any) => {
464
- // For source token selection
465
- if (showSourceTokenSelector) {
466
- handleSourceTokenSelectorSelect(token)
467
- }
468
- // For destination token selection
469
- else if (showDestinationTokenSelector) {
470
- handleDestinationTokenSelectorSelect(token)
471
- }
472
- },
473
- [
474
- showSourceTokenSelector,
475
- showDestinationTokenSelector,
476
- handleSourceTokenSelectorSelect,
477
- handleDestinationTokenSelectorSelect,
478
- ],
479
- )
480
-
481
- // Handle input type toggle (USD vs Token)
482
- const handleInputTypeToggle = useCallback(() => {
483
- // Use tokenAmountForBackend as the source of truth for conversion
484
- const currentTokenAmount = parseFloat(tokenAmountForBackend) || 0
485
- const sourceTokenPrice = (selectedToken as any)?.tokenPriceUsd || 0
486
-
487
- if (isInputTypeUsd && sourceTokenPrice > 0) {
488
- // Switching from USD to token mode
489
- // Display the token amount (limit to 8 decimals)
490
- const tokenAmountStr = Number(currentTokenAmount.toFixed(8)).toString()
491
- setInputDisplayValue(tokenAmountStr)
492
- } else if (!isInputTypeUsd && sourceTokenPrice > 0) {
493
- // Switching from token to USD mode
494
- // Display USD amount (limit to 2 decimals)
495
- const usdAmount = currentTokenAmount * sourceTokenPrice
496
- const usdAmountStr = Number(usdAmount.toFixed(2)).toString()
497
- setInputDisplayValue(usdAmountStr)
498
- }
499
-
500
- // Toggle the mode
501
- setIsInputTypeUsd(!isInputTypeUsd)
502
-
503
- // Focus the input field after toggling
504
- setTimeout(() => {
505
- if (inputRef.current) {
506
- inputRef.current.focus()
507
- // Select all text for easy replacement
508
- inputRef.current.select()
509
- }
510
- }, 0)
511
- }, [isInputTypeUsd, tokenAmountForBackend, selectedToken])
512
-
513
- // Dynamic font size based on input length - matching Earn.tsx
514
- const inputStyles = useMemo(() => {
515
- const inputLength = displayAmount.length
516
- let fontSize: string
517
-
518
- if (inputLength > 12) {
519
- fontSize = "1rem"
520
- } else if (inputLength > 9) {
521
- fontSize = "1.25rem"
522
- } else if (inputLength > 6) {
523
- fontSize = "1.5rem"
524
- } else if (inputLength > 3) {
525
- fontSize = "1.75rem"
526
- } else {
527
- fontSize = "2rem"
528
- }
529
-
530
- return {
531
- fontSize,
532
- transition: "all 0.1s ease-in-out",
533
- }
534
- }, [displayAmount.length])
535
-
536
- logger.console.log("[trails-sdk] SimpleSwap", {
537
- tokenAmountForBackend,
538
- displayAmount,
539
- isInputTypeUsd,
540
- amountUsdDisplay,
541
- selectedToken,
542
- })
543
-
544
- // Debug swap button disabled state
545
- logger.console.log("[trails-sdk] SimpleSwap button state", {
546
- amount,
547
- isSubmitting,
548
- destinationTokenAddress,
549
- isValidCustomToken,
550
- isLoadingQuote,
551
- prepareSendQuote: !!prepareSendQuote,
552
- noSufficientBalance: prepareSendQuote?.noSufficientBalance,
553
- isSameTokenWithoutCustomCalldata,
554
- selectedDestToken: !!selectedDestToken,
555
- buttonDisabled:
556
- !amount ||
557
- isSubmitting ||
558
- !destinationTokenAddress ||
559
- !isValidCustomToken ||
560
- isLoadingQuote ||
561
- !prepareSendQuote ||
562
- prepareSendQuote?.noSufficientBalance ||
563
- isSameTokenWithoutCustomCalldata,
564
- })
565
-
566
- // Show source chain list screen
567
- if (showSourceChainList) {
568
- return (
569
- <ChainList
570
- onBack={() => {
571
- setShowSourceChainList(false)
572
- setShowSourceTokenSelector(true)
573
- }}
574
- />
575
- )
576
- }
577
-
578
- // Show source token selector screen
579
- if (showSourceTokenSelector) {
580
- return (
581
- <div className="space-y-2">
582
- {showHeader && (
583
- <ScreenHeader
584
- onBack={() => setShowSourceTokenSelector(false)}
585
- headerContent="Select From Token"
586
- headerContentAlign="left"
587
- />
588
- )}
589
- <TokenSelector
590
- onTokenSelect={handleSourceTokenSelectorSelect}
591
- onError={onError}
592
- showContinueButton={false}
593
- compactMode={false}
594
- allSupportedTokens={false}
595
- chainListScreen={true}
596
- onNavigateToChainList={() => {
597
- setShowSourceTokenSelector(false)
598
- setShowSourceChainList(true)
599
- }}
600
- recentTokens={recentTokens}
601
- onRecentTokenSelect={handleRecentTokenSelect}
602
- />
603
- </div>
604
- )
605
- }
606
-
607
- // Show destination chain list screen
608
- if (showDestinationChainList) {
609
- return (
610
- <ChainList
611
- onBack={() => {
612
- setShowDestinationChainList(false)
613
- setShowDestinationTokenSelector(true)
614
- }}
615
- />
616
- )
617
- }
618
-
619
- // Show destination token selector screen
620
- if (showDestinationTokenSelector) {
621
- return (
622
- <div className="space-y-2">
623
- {showHeader && (
624
- <ScreenHeader
625
- onBack={() => setShowDestinationTokenSelector(false)}
626
- headerContent="Select To Token"
627
- headerContentAlign="left"
628
- />
629
- )}
630
- <TokenSelector
631
- onTokenSelect={handleDestinationTokenSelectorSelect}
632
- onError={onError}
633
- showContinueButton={false}
634
- compactMode={false}
635
- allSupportedTokens={true}
636
- chainListScreen={true}
637
- onNavigateToChainList={() => {
638
- setShowDestinationTokenSelector(false)
639
- setShowDestinationChainList(true)
640
- }}
641
- recentTokens={recentTokens}
642
- onRecentTokenSelect={handleRecentTokenSelect}
643
- />
644
- </div>
645
- )
646
- }
647
-
648
- if (!selectedToken) {
649
- return (
650
- <div className="space-y-2">
651
- {showHeader && (
652
- <ScreenHeader
653
- onBack={onBack}
654
- headerContent="Swap"
655
- headerContentAlign="left"
656
- />
657
- )}
658
-
659
- <div className="space-y-2">
660
- {/* Token Selector */}
661
- <div className="relative">
662
- <button
663
- type="button"
664
- onClick={() => setShowSourceTokenSelector(true)}
665
- className="w-full flex items-center justify-between space-x-3 trails-list-item trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
666
- >
667
- <div className="flex items-center space-x-3 flex-1">
668
- <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
669
- <Search className="w-4 h-4 text-gray-400" />
670
- </div>
671
- <div className="text-left flex-1">
672
- <div className="font-medium text-gray-900 dark:text-white text-sm">
673
- From token
674
- </div>
675
- <div className="text-xs trails-text-muted">Select token</div>
676
- </div>
677
- </div>
678
- <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
679
- </button>
680
- </div>
681
- </div>
682
- </div>
683
- )
684
- }
685
-
686
- return (
687
- <div className="space-y-2">
688
- {showHeader && (
689
- <ScreenHeader
690
- onBack={onBack}
691
- headerContent="Swap"
692
- headerContentAlign="left"
693
- />
694
- )}
695
-
696
- <div className="space-y-1">
697
- {/* Amount Input Section */}
698
- <div className="space-y-1">
699
- {/* Amount Input with Max Button */}
700
- <div className="flex items-center justify-between relative px-4 py-2">
701
- <div className="flex items-center flex-1">
702
- <input
703
- ref={inputRef}
704
- type="text"
705
- value={displayAmount}
706
- onChange={(e) => handleAmountChange(e.target.value)}
707
- placeholder="0"
708
- className="bg-transparent border-none outline-none font-bold text-left trails-text-primary placeholder-trails-text-primary"
709
- style={{
710
- fontSize: inputStyles.fontSize,
711
- width: `${Math.max((displayAmount || "0").length, 1)}ch`,
712
- minWidth: "1ch",
713
- maxWidth: "270px",
714
- padding: "0",
715
- margin: "0",
716
- transition: "all 0.1s ease-in-out",
717
- }}
718
- inputMode="decimal"
719
- />
720
- <span
721
- className="font-bold text-gray-400 dark:text-gray-500"
722
- style={{
723
- fontSize: inputStyles.fontSize,
724
- marginLeft:
725
- displayAmount && displayAmount !== "0" ? "0.2em" : "0.3em",
726
- padding: "0",
727
- transition: "all 0.2s ease-in-out",
728
- }}
729
- >
730
- {isInputTypeUsd ? "USD" : selectedToken.symbol.slice(0, 4)}
731
- </span>
732
- </div>
733
-
734
- {/* Percentage Buttons */}
735
- <div
736
- className="flex items-center gap-1.5"
737
- onMouseEnter={() => setShowPercentageButtons(true)}
738
- onMouseLeave={() => setShowPercentageButtons(false)}
739
- >
740
- {showPercentageButtons && (
741
- <>
742
- <button
743
- type="button"
744
- onClick={() => handlePercentageClick(25)}
745
- className="flex h-6 px-2 py-0.5 justify-center items-center rounded-full bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 text-xs font-medium transition-all duration-200 hover:bg-gray-300 dark:hover:bg-gray-500 cursor-pointer animate-in slide-in-from-right-2 fade-in"
746
- >
747
- 25%
748
- </button>
749
- <button
750
- type="button"
751
- onClick={() => handlePercentageClick(50)}
752
- className="flex h-6 px-2 py-0.5 justify-center items-center rounded-full bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 text-xs font-medium transition-all duration-200 hover:bg-gray-300 dark:hover:bg-gray-500 cursor-pointer animate-in slide-in-from-right-2 fade-in"
753
- >
754
- 50%
755
- </button>
756
- </>
757
- )}
758
- <button
759
- type="button"
760
- onClick={handleMaxClick}
761
- className="flex h-6 px-2 py-0.5 justify-center items-center rounded-full bg-gray-200 dark:bg-gray-600 text-gray-700 dark:text-gray-300 text-xs font-medium transition-all duration-200 hover:bg-gray-300 dark:hover:bg-gray-500 cursor-pointer"
762
- >
763
- MAX
764
- </button>
765
- </div>
766
- </div>
767
-
768
- {/* USD Value Toggle */}
769
- <div className="flex items-center justify-start">
770
- <button
771
- type="button"
772
- onClick={handleInputTypeToggle}
773
- 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"
774
- style={{ color: "#155DFC" }}
775
- >
776
- <svg
777
- width="20"
778
- height="21"
779
- viewBox="0 0 20 21"
780
- fill="none"
781
- xmlns="http://www.w3.org/2000/svg"
782
- >
783
- <path
784
- 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"
785
- fill="#155DFC"
786
- />
787
- </svg>
788
- <div
789
- className="text-sm font-extrabold leading-[130%]"
790
- style={{ color: "#155DFC" }}
791
- >
792
- {isBalanceVisible ? displayUsdValue : "••••••"}
793
- </div>
794
- </button>
795
- </div>
796
- </div>
797
-
798
- {/* Source Token Selector */}
799
- <div className="relative">
800
- <button
801
- type="button"
802
- onClick={() => setShowSourceTokenSelector(true)}
803
- className="w-full flex items-center justify-between space-x-3 trails-list-item hover:scale-[1.01] trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
804
- >
805
- <div className="flex items-center space-x-3 flex-1">
806
- {selectedToken ? (
807
- <>
808
- <TokenImage
809
- symbol={selectedToken.symbol}
810
- imageUrl={selectedToken.imageUrl}
811
- chainId={selectedToken.chainId}
812
- size={24}
813
- />
814
- <div className="text-left flex-1">
815
- <div className="font-medium trails-text-primary text-sm">
816
- {selectedToken.symbol}
817
- </div>
818
- <div className="text-xs trails-text-muted">
819
- on{" "}
820
- {getChainInfo(selectedToken.chainId)?.name || "Unknown"}
821
- </div>
822
- </div>
823
- </>
824
- ) : (
825
- <>
826
- <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
827
- <Search className="w-4 h-4 text-gray-400" />
828
- </div>
829
- <div className="text-left flex-1">
830
- <div className="font-medium trails-text-muted text-sm">
831
- From
832
- </div>
833
- <div className="text-xs trails-text-muted">
834
- Select token
835
- </div>
836
- </div>
837
- </>
838
- )}
839
- </div>
840
-
841
- {selectedToken && balanceFormatted && (
842
- <div className="text-right">
843
- <div className="font-medium trails-text-primary text-sm">
844
- {isBalanceVisible
845
- ? (selectedToken as any).balanceUsdFormatted || "$0.00"
846
- : "••••••"}
847
- </div>
848
- <div className="text-xs trails-text-muted">available</div>
849
- </div>
850
- )}
851
-
852
- <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
853
- </button>
854
- </div>
855
-
856
- {/* Swap Button */}
857
- <div className="flex justify-start py-0 pl-4">
858
- <button
859
- type="button"
860
- onClick={handleTokenSwap}
861
- className="transition-colors cursor-pointer rounded-full border-2 border-white dark:border-gray-800 hover:bg-gray-100 dark:hover:bg-gray-700"
862
- title="Swap tokens"
863
- disabled={!selectedToken || !selectedDestToken}
864
- >
865
- <ArrowDown
866
- className={`w-5 h-5 ${!selectedToken ? "text-gray-300 dark:text-gray-600" : "text-gray-900 dark:text-white"}`}
867
- strokeWidth={2.5}
868
- />
869
- </button>
870
- </div>
871
-
872
- {/* Destination Token Selector */}
873
- <div className="relative">
874
- <button
875
- type="button"
876
- onClick={() => setShowDestinationTokenSelector(true)}
877
- className="w-full flex items-center justify-between space-x-3 trails-list-item hover:scale-[1.01] trails-border-radius-list-button px-4 py-2 transition-all duration-200 cursor-pointer"
878
- >
879
- <div className="flex items-center space-x-3 flex-1">
880
- {selectedDestToken ? (
881
- <>
882
- <TokenImage
883
- symbol={selectedDestToken.symbol}
884
- imageUrl={selectedDestToken.imageUrl}
885
- chainId={(selectedDestToken as any)?.chainId}
886
- size={24}
887
- />
888
- <div className="text-left flex-1">
889
- <div className="font-medium trails-text-primary text-sm">
890
- {selectedDestToken.symbol}
891
- </div>
892
- <div className="text-xs trails-text-muted">
893
- on{" "}
894
- {getChainInfo((selectedDestToken as any)?.chainId)
895
- ?.name || "Unknown"}
896
- </div>
897
- </div>
898
- </>
899
- ) : (
900
- <>
901
- <div className="w-6 h-6 rounded-full bg-gray-100 dark:bg-gray-700 flex items-center justify-center">
902
- <Search className="w-4 h-4 text-gray-400" />
903
- </div>
904
- <div className="text-left flex-1">
905
- <div className="font-medium text-gray-900 dark:text-white text-sm">
906
- To
907
- </div>
908
- <div className="text-xs trails-text-muted">
909
- Select token
910
- </div>
911
- </div>
912
- </>
913
- )}
914
- </div>
915
-
916
- <ChevronRight className="w-4 h-4 trails-text-muted flex-shrink-0" />
917
- </button>
918
- </div>
919
-
920
- {/* Error Display */}
921
- {isSameTokenWithoutCustomCalldata ? (
922
- <ErrorDisplay
923
- errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different destination token."
924
- severity="error"
925
- />
926
- ) : (
927
- <ErrorDisplay
928
- errorPrettified={quoteErrorPrettified}
929
- error={quoteError}
930
- severity="warning"
931
- />
932
- )}
933
-
934
- <div className="py-2"></div>
935
-
936
- {/* Swap Button */}
937
- <form onSubmit={handleSubmit}>
938
- <button
939
- type="submit"
940
- disabled={
941
- !amount ||
942
- isSubmitting ||
943
- !destinationTokenAddress ||
944
- !isValidCustomToken ||
945
- isLoadingQuote ||
946
- !prepareSendQuote ||
947
- prepareSendQuote?.noSufficientBalance ||
948
- isSameTokenWithoutCustomCalldata
949
- }
950
- 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"
951
- >
952
- {isSubmitting ? (
953
- <div className="flex items-center justify-center">
954
- <Loader2 className="w-5 h-5 animate-spin mr-2" />
955
- <span>{buttonText}</span>
956
- </div>
957
- ) : isSameTokenWithoutCustomCalldata ? (
958
- "Select Different Tokens"
959
- ) : prepareSendQuote?.noSufficientBalance ? (
960
- "Insufficient Balance"
961
- ) : !selectedToken || !selectedDestToken ? (
962
- "Select tokens to swap"
963
- ) : !amount || parseFloat(amount) <= 0 ? (
964
- "Enter an amount"
965
- ) : (
966
- buttonText ||
967
- `Swap ${selectedToken.symbol} for ${selectedDestToken.symbol}`
968
- )}
969
- </button>
970
- </form>
971
-
972
- {/* Quote Details */}
973
- {prepareSendQuote && (
974
- <div className="space-y-2">
975
- <QuoteDetails quote={prepareSendQuote} showContent={true} />
976
- </div>
977
- )}
978
- </div>
979
- </div>
980
- )
981
- }
982
-
983
- export default SimpleSwap