0xtrails 0.1.3 → 0.1.4
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.
- package/dist/address.d.ts.map +1 -1
- package/dist/analytics.d.ts +18 -0
- package/dist/analytics.d.ts.map +1 -1
- package/dist/apiClient.d.ts +1 -1
- package/dist/apiClient.d.ts.map +1 -1
- package/dist/{ccip-CWd4g9uZ.js → ccip-dLSEJjCf.js} +55 -55
- package/dist/cctpqueue.d.ts +1 -1
- package/dist/cctpqueue.d.ts.map +1 -1
- package/dist/constants.d.ts +1 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/decoders.d.ts +58 -0
- package/dist/decoders.d.ts.map +1 -0
- package/dist/ens.d.ts +6 -0
- package/dist/ens.d.ts.map +1 -1
- package/dist/error.d.ts +7 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/{index-BTUBzx4R.js → index-BXbaLmtt.js} +25694 -24507
- package/dist/index.js +2 -2
- package/dist/intents.d.ts +4 -4
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxns.d.ts +1 -1
- package/dist/metaTxns.d.ts.map +1 -1
- package/dist/mode.d.ts +1 -1
- package/dist/mode.d.ts.map +1 -1
- package/dist/preconditions.d.ts +1 -1
- package/dist/preconditions.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +30 -24
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +1 -1
- package/dist/prices.d.ts.map +1 -1
- package/dist/proxyCaller.d.ts +0 -1
- package/dist/proxyCaller.d.ts.map +1 -1
- package/dist/tokenBalances.d.ts +1 -1
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/trails.d.ts +1 -1
- package/dist/trails.d.ts.map +1 -1
- package/dist/transactions.d.ts +4 -0
- package/dist/transactions.d.ts.map +1 -1
- package/dist/utils.d.ts +6 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/widget/components/DebugScreensDropdown.d.ts.map +1 -1
- package/dist/widget/components/ErrorDisplay.d.ts +9 -0
- package/dist/widget/components/ErrorDisplay.d.ts.map +1 -0
- package/dist/widget/components/FundSendForm.d.ts +1 -1
- package/dist/widget/components/FundSendForm.d.ts.map +1 -1
- package/dist/widget/components/OriginTransferInformation.d.ts +10 -0
- package/dist/widget/components/OriginTransferInformation.d.ts.map +1 -0
- package/dist/widget/components/PaySendForm.d.ts +1 -1
- package/dist/widget/components/PaySendForm.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/Receive.d.ts +12 -0
- package/dist/widget/components/Receive.d.ts.map +1 -0
- package/dist/widget/components/Swap.d.ts +5 -1
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapDisplay.d.ts +9 -0
- package/dist/widget/components/SwapDisplay.d.ts.map +1 -0
- package/dist/widget/components/TransferPendingVertical.d.ts +2 -0
- package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
- package/dist/widget/components/YellowWarningAnimation.d.ts +2 -0
- package/dist/widget/components/YellowWarningAnimation.d.ts.map +1 -0
- package/dist/widget/hooks/useDebugScreens.d.ts +22 -0
- package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -0
- package/dist/widget/hooks/useSendForm.d.ts +7 -3
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +16 -15
- package/src/address.ts +3 -0
- package/src/analytics.ts +105 -4
- package/src/apiClient.ts +1 -1
- package/src/cctpqueue.ts +1 -1
- package/src/constants.ts +3 -0
- package/src/decoders.ts +310 -0
- package/src/ens.ts +16 -1
- package/src/error.ts +86 -1
- package/src/intents.ts +10 -2
- package/src/metaTxns.ts +1 -1
- package/src/mode.ts +1 -1
- package/src/preconditions.ts +1 -1
- package/src/prepareSend.ts +432 -114
- package/src/prices.ts +5 -1
- package/src/proxyCaller.ts +2 -14
- package/src/tokenBalances.ts +21 -17
- package/src/tokens.ts +62 -3
- package/src/trails.ts +2 -2
- package/src/transactions.ts +35 -18
- package/src/utils.ts +28 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/DebugScreensDropdown.tsx +3 -0
- package/src/widget/components/ErrorDisplay.tsx +150 -0
- package/src/widget/components/FundSendForm.tsx +62 -9
- package/src/widget/components/OriginTransferInformation.tsx +59 -0
- package/src/widget/components/PaySendForm.tsx +65 -12
- package/src/widget/components/QRCodeDeposit.tsx +5 -5
- package/src/widget/components/QrCode.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +93 -25
- package/src/widget/components/Receipt.tsx +296 -103
- package/src/widget/components/Receive.tsx +146 -0
- package/src/widget/components/RecentTokens.tsx +1 -1
- package/src/widget/components/Swap.tsx +206 -85
- package/src/widget/components/SwapDisplay.tsx +68 -0
- package/src/widget/components/TokenSelector.tsx +1 -1
- package/src/widget/components/TransferPendingVertical.tsx +162 -112
- package/src/widget/components/YellowWarningAnimation.tsx +146 -0
- package/src/widget/hooks/useDebugScreens.ts +583 -0
- package/src/widget/hooks/useSendForm.ts +46 -4
- package/src/widget/hooks/useTokenList.ts +8 -27
- package/src/widget/widget.tsx +201 -484
|
@@ -16,6 +16,7 @@ import { formatUsdAmountDisplay } from "../../tokenBalances.js"
|
|
|
16
16
|
import { MINIMUM_USD_AMOUNT_FOR_SWAP } from "../../constants.js"
|
|
17
17
|
import { ScreenHeader } from "./ScreenHeader.js"
|
|
18
18
|
import { TokenSelector } from "./TokenSelector.js"
|
|
19
|
+
import { ErrorDisplay } from "./ErrorDisplay.js"
|
|
19
20
|
|
|
20
21
|
interface SwapProps {
|
|
21
22
|
selectedToken: Token | null
|
|
@@ -48,8 +49,11 @@ interface SwapProps {
|
|
|
48
49
|
quote?: PrepareSendQuote | null,
|
|
49
50
|
) => void
|
|
50
51
|
onAmountUpdate?: (amount: string) => void
|
|
51
|
-
mode?: "pay" | "fund" | "earn" | "swap"
|
|
52
|
+
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
52
53
|
checkoutOnHandlers?: CheckoutOnHandlers
|
|
54
|
+
recentTokens?: SupportedToken[]
|
|
55
|
+
onRecentTokenSelect?: (token: SupportedToken) => void
|
|
56
|
+
onTrackToken?: (token: any) => void
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
export const Swap: React.FC<SwapProps> = ({
|
|
@@ -76,6 +80,9 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
76
80
|
onAmountUpdate,
|
|
77
81
|
mode,
|
|
78
82
|
checkoutOnHandlers,
|
|
83
|
+
recentTokens,
|
|
84
|
+
onRecentTokenSelect,
|
|
85
|
+
onTrackToken,
|
|
79
86
|
}) => {
|
|
80
87
|
const [isFlipped, setIsFlipped] = useState(false)
|
|
81
88
|
const [originChainId, setOriginChainId] = useState<number | null | undefined>(
|
|
@@ -84,6 +91,10 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
84
91
|
const [originToken, setOriginToken] = useState<SupportedToken | null>(
|
|
85
92
|
initialSelectedToken as SupportedToken | null,
|
|
86
93
|
)
|
|
94
|
+
const [tradeType, setTradeType] = useState<TradeType>(TradeType.EXACT_INPUT)
|
|
95
|
+
const [sellAmount, setSellAmount] = useState("")
|
|
96
|
+
const [buyAmount, setBuyAmount] = useState("")
|
|
97
|
+
const [lastInputType, setLastInputType] = useState<"sell" | "buy">("sell")
|
|
87
98
|
|
|
88
99
|
const {
|
|
89
100
|
amount,
|
|
@@ -108,9 +119,12 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
108
119
|
destinationTokenAddress,
|
|
109
120
|
isValidCustomToken,
|
|
110
121
|
prepareSendQuote,
|
|
122
|
+
quoteError,
|
|
123
|
+
quoteErrorPrettified,
|
|
124
|
+
isSameTokenWithoutCustomCalldata,
|
|
111
125
|
} = useSendForm({
|
|
112
126
|
account,
|
|
113
|
-
toAmount,
|
|
127
|
+
toAmount: tradeType === TradeType.EXACT_OUTPUT ? buyAmount : toAmount,
|
|
114
128
|
toRecipient: account.address,
|
|
115
129
|
toChainId,
|
|
116
130
|
toToken,
|
|
@@ -126,7 +140,7 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
126
140
|
onSend,
|
|
127
141
|
selectedToken: originToken as any,
|
|
128
142
|
setWalletConfirmRetryHandler,
|
|
129
|
-
tradeType:
|
|
143
|
+
tradeType: tradeType,
|
|
130
144
|
quoteProvider,
|
|
131
145
|
fundMethod,
|
|
132
146
|
mode,
|
|
@@ -134,18 +148,57 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
134
148
|
checkoutOnHandlers,
|
|
135
149
|
})
|
|
136
150
|
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
151
|
+
// Update amount based on last input type
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
if (lastInputType === "sell") {
|
|
154
|
+
setAmount(sellAmount)
|
|
155
|
+
} else {
|
|
156
|
+
setAmount(buyAmount)
|
|
157
|
+
}
|
|
158
|
+
}, [sellAmount, buyAmount, lastInputType, setAmount])
|
|
159
|
+
|
|
160
|
+
// Handle sell amount input changes with decimal validation
|
|
161
|
+
const handleSellAmountChange = useCallback((value: string) => {
|
|
162
|
+
// Validate decimal places (max 8 decimals)
|
|
163
|
+
const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
|
|
164
|
+
if (!decimalMatch && value !== "") {
|
|
165
|
+
return // Don't update if more than 8 decimals
|
|
166
|
+
}
|
|
167
|
+
setTradeType(TradeType.EXACT_INPUT)
|
|
168
|
+
setSellAmount(value)
|
|
169
|
+
setBuyAmount("")
|
|
170
|
+
setLastInputType("sell")
|
|
171
|
+
}, [])
|
|
172
|
+
|
|
173
|
+
// Handle buy amount input changes with decimal validation
|
|
174
|
+
const handleBuyAmountChange = useCallback((value: string) => {
|
|
175
|
+
// Validate decimal places (max 8 decimals)
|
|
176
|
+
const decimalMatch = value.match(/^\d*\.?\d{0,8}$/)
|
|
177
|
+
if (!decimalMatch && value !== "") {
|
|
178
|
+
return // Don't update if more than 8 decimals
|
|
179
|
+
}
|
|
180
|
+
setTradeType(TradeType.EXACT_OUTPUT)
|
|
181
|
+
setBuyAmount(value)
|
|
182
|
+
setSellAmount("")
|
|
183
|
+
setLastInputType("buy")
|
|
184
|
+
}, [])
|
|
185
|
+
|
|
186
|
+
// Update amounts when quote is received
|
|
187
|
+
useEffect(() => {
|
|
188
|
+
if (!prepareSendQuote) return
|
|
189
|
+
|
|
190
|
+
if (
|
|
191
|
+
tradeType === TradeType.EXACT_OUTPUT &&
|
|
192
|
+
prepareSendQuote.originAmountDisplay
|
|
193
|
+
) {
|
|
194
|
+
setSellAmount(prepareSendQuote.originAmountDisplay)
|
|
195
|
+
} else if (
|
|
196
|
+
tradeType === TradeType.EXACT_INPUT &&
|
|
197
|
+
prepareSendQuote.destinationAmountDisplay
|
|
198
|
+
) {
|
|
199
|
+
setBuyAmount(prepareSendQuote.destinationAmountDisplay)
|
|
200
|
+
}
|
|
201
|
+
}, [prepareSendQuote, tradeType])
|
|
149
202
|
|
|
150
203
|
// Handle percentage button clicks
|
|
151
204
|
const handlePercentageClick = useCallback(
|
|
@@ -157,9 +210,12 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
157
210
|
if (Number.isNaN(balance)) return
|
|
158
211
|
|
|
159
212
|
const amount = (balance * percentage) / 100
|
|
160
|
-
|
|
213
|
+
setTradeType(TradeType.EXACT_INPUT)
|
|
214
|
+
setSellAmount(amount.toFixed(6))
|
|
215
|
+
setBuyAmount("")
|
|
216
|
+
setLastInputType("sell")
|
|
161
217
|
},
|
|
162
|
-
[originToken, balanceFormatted
|
|
218
|
+
[originToken, balanceFormatted],
|
|
163
219
|
)
|
|
164
220
|
|
|
165
221
|
// Call onAmountUpdate when amountRaw changes
|
|
@@ -222,12 +278,11 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
222
278
|
// Store current values
|
|
223
279
|
const tempOriginToken = originToken
|
|
224
280
|
const tempOriginChainId = originChainId
|
|
281
|
+
const tempBuyAmount = buyAmount || toAmountDisplay || ""
|
|
225
282
|
|
|
226
283
|
// Swap origin and destination
|
|
227
284
|
setOriginToken(selectedDestToken as any)
|
|
228
285
|
setOriginChainId((selectedDestToken as any)?.chainId as any)
|
|
229
|
-
|
|
230
|
-
// Set destination to previous origin (convert to TokenInfo)
|
|
231
286
|
setSelectedDestToken(tempOriginToken as any)
|
|
232
287
|
|
|
233
288
|
// Update destination chain
|
|
@@ -235,6 +290,21 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
235
290
|
if (newChain) {
|
|
236
291
|
setSelectedDestinationChain(newChain)
|
|
237
292
|
}
|
|
293
|
+
|
|
294
|
+
// When flipping, always set the current visible amount as the new sell amount
|
|
295
|
+
if (tradeType === TradeType.EXACT_INPUT) {
|
|
296
|
+
// If we were in EXACT_INPUT, use the buy amount (from quote) as the new sell amount
|
|
297
|
+
setSellAmount(tempBuyAmount)
|
|
298
|
+
setBuyAmount("")
|
|
299
|
+
setTradeType(TradeType.EXACT_INPUT)
|
|
300
|
+
setLastInputType("sell")
|
|
301
|
+
} else {
|
|
302
|
+
// If we were in EXACT_OUTPUT, use the current buy amount as the new sell amount
|
|
303
|
+
setSellAmount(tempBuyAmount)
|
|
304
|
+
setBuyAmount("")
|
|
305
|
+
setTradeType(TradeType.EXACT_INPUT)
|
|
306
|
+
setLastInputType("sell")
|
|
307
|
+
}
|
|
238
308
|
}
|
|
239
309
|
|
|
240
310
|
return (
|
|
@@ -250,7 +320,7 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
250
320
|
<div className="trails-bg-secondary trails-border-radius-container p-3 group">
|
|
251
321
|
{/* Sell Label and Percentage Buttons */}
|
|
252
322
|
<div className="flex justify-between items-center mb-2">
|
|
253
|
-
<div className="text-sm font-medium trails-text-secondary">
|
|
323
|
+
<div className="text-sm font-medium trails-text-secondary text-left">
|
|
254
324
|
Sell
|
|
255
325
|
</div>
|
|
256
326
|
|
|
@@ -292,15 +362,27 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
292
362
|
<div className="flex items-center space-x-2">
|
|
293
363
|
{/* Amount Input */}
|
|
294
364
|
<div className="flex-1">
|
|
295
|
-
<
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
365
|
+
<div className="flex items-center space-x-2">
|
|
366
|
+
<input
|
|
367
|
+
ref={inputRef}
|
|
368
|
+
id="amount"
|
|
369
|
+
type="text"
|
|
370
|
+
value={sellAmount}
|
|
371
|
+
onChange={(e) => handleSellAmountChange(e.target.value)}
|
|
372
|
+
placeholder="0.00"
|
|
373
|
+
className={`w-full bg-transparent text-xl font-medium trails-text-primary placeholder:trails-text-muted border-none outline-none ${
|
|
374
|
+
isLoadingQuote && tradeType === TradeType.EXACT_OUTPUT
|
|
375
|
+
? "animate-pulse"
|
|
376
|
+
: ""
|
|
377
|
+
}`}
|
|
378
|
+
readOnly={
|
|
379
|
+
tradeType === TradeType.EXACT_OUTPUT && isLoadingQuote
|
|
380
|
+
}
|
|
381
|
+
/>
|
|
382
|
+
{isLoadingQuote && tradeType === TradeType.EXACT_OUTPUT && (
|
|
383
|
+
<div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
|
|
384
|
+
)}
|
|
385
|
+
</div>
|
|
304
386
|
</div>
|
|
305
387
|
|
|
306
388
|
{/* Token Selection Button */}
|
|
@@ -353,14 +435,33 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
353
435
|
name: token.name,
|
|
354
436
|
},
|
|
355
437
|
} as any)
|
|
438
|
+
console.log("selected origin token", token)
|
|
356
439
|
setOriginChainId(token.chainId)
|
|
357
440
|
setIsOriginTokenDropdownOpen(false)
|
|
441
|
+
// Track the token selection
|
|
442
|
+
onTrackToken?.(token)
|
|
358
443
|
}}
|
|
359
444
|
onError={onError}
|
|
360
445
|
mode="swap"
|
|
361
446
|
fundMethod={fundMethod}
|
|
362
447
|
showContinueButton={false}
|
|
363
448
|
compactMode={false}
|
|
449
|
+
recentTokens={recentTokens}
|
|
450
|
+
onRecentTokenSelect={(token) => {
|
|
451
|
+
setOriginToken({
|
|
452
|
+
...token,
|
|
453
|
+
decimals: token.decimals,
|
|
454
|
+
contractInfo: {
|
|
455
|
+
decimals: token.decimals,
|
|
456
|
+
contractAddress: token.contractAddress,
|
|
457
|
+
symbol: token.symbol,
|
|
458
|
+
name: token.name,
|
|
459
|
+
},
|
|
460
|
+
} as any)
|
|
461
|
+
setOriginChainId(token.chainId)
|
|
462
|
+
setIsOriginTokenDropdownOpen(false)
|
|
463
|
+
onRecentTokenSelect?.(token)
|
|
464
|
+
}}
|
|
364
465
|
/>
|
|
365
466
|
</div>
|
|
366
467
|
</div>
|
|
@@ -368,12 +469,17 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
368
469
|
</div>
|
|
369
470
|
</div>
|
|
370
471
|
|
|
371
|
-
{/* Bottom Info Row */}
|
|
472
|
+
{/* Bottom Info Row for sell */}
|
|
372
473
|
<div className="mt-2 flex justify-between items-center">
|
|
373
474
|
{/* USD Amount */}
|
|
374
|
-
{
|
|
475
|
+
{originToken?.symbol && (
|
|
375
476
|
<div className="text-xs trails-text-muted">
|
|
376
|
-
≈
|
|
477
|
+
≈{" "}
|
|
478
|
+
{tradeType === TradeType.EXACT_INPUT
|
|
479
|
+
? amountUsdDisplay || "$0.00"
|
|
480
|
+
: isLoadingQuote
|
|
481
|
+
? "$0.00"
|
|
482
|
+
: prepareSendQuote?.originAmountUsdDisplay || "$0.00"}
|
|
377
483
|
</div>
|
|
378
484
|
)}
|
|
379
485
|
|
|
@@ -386,7 +492,10 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
386
492
|
if (balanceFormatted) {
|
|
387
493
|
const balance = parseFloat(balanceFormatted)
|
|
388
494
|
if (!Number.isNaN(balance)) {
|
|
389
|
-
|
|
495
|
+
setTradeType(TradeType.EXACT_INPUT)
|
|
496
|
+
setSellAmount(balance.toFixed(6))
|
|
497
|
+
setBuyAmount("")
|
|
498
|
+
setLastInputType("sell")
|
|
390
499
|
}
|
|
391
500
|
}
|
|
392
501
|
}}
|
|
@@ -396,7 +505,10 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
396
505
|
if (balanceFormatted) {
|
|
397
506
|
const balance = parseFloat(balanceFormatted)
|
|
398
507
|
if (!Number.isNaN(balance)) {
|
|
399
|
-
|
|
508
|
+
setTradeType(TradeType.EXACT_INPUT)
|
|
509
|
+
setSellAmount(balance.toFixed(6))
|
|
510
|
+
setBuyAmount("")
|
|
511
|
+
setLastInputType("sell")
|
|
400
512
|
}
|
|
401
513
|
}
|
|
402
514
|
}
|
|
@@ -423,7 +535,7 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
423
535
|
{/* Output Section - Amount + Token Selection */}
|
|
424
536
|
<div className="trails-bg-secondary trails-border-radius-container p-3">
|
|
425
537
|
{/* Buy Label */}
|
|
426
|
-
<div className="text-sm font-medium trails-text-secondary mb-2">
|
|
538
|
+
<div className="text-sm font-medium trails-text-secondary mb-2 text-left">
|
|
427
539
|
Buy
|
|
428
540
|
</div>
|
|
429
541
|
|
|
@@ -431,12 +543,25 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
431
543
|
{/* Output Amount */}
|
|
432
544
|
<div className="flex-1">
|
|
433
545
|
<div className="flex items-center space-x-2">
|
|
434
|
-
<
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
546
|
+
<input
|
|
547
|
+
type="text"
|
|
548
|
+
value={
|
|
549
|
+
tradeType === TradeType.EXACT_OUTPUT
|
|
550
|
+
? buyAmount
|
|
551
|
+
: toAmountDisplay || ""
|
|
552
|
+
}
|
|
553
|
+
onChange={(e) => handleBuyAmountChange(e.target.value)}
|
|
554
|
+
placeholder="0.00"
|
|
555
|
+
className={`w-full bg-transparent text-xl font-medium placeholder:trails-text-muted border-none outline-none ${
|
|
556
|
+
!amount
|
|
557
|
+
? "text-gray-400 dark:text-gray-500"
|
|
558
|
+
: "trails-text-primary"
|
|
559
|
+
} ${isLoadingQuote && tradeType === TradeType.EXACT_INPUT ? "animate-pulse" : ""}`}
|
|
560
|
+
readOnly={
|
|
561
|
+
tradeType === TradeType.EXACT_INPUT && isLoadingQuote
|
|
562
|
+
}
|
|
563
|
+
/>
|
|
564
|
+
{isLoadingQuote && tradeType === TradeType.EXACT_INPUT && (
|
|
440
565
|
<div className="animate-spin rounded-full h-4 w-4 border-solid border-b-2 trails-primary" />
|
|
441
566
|
)}
|
|
442
567
|
</div>
|
|
@@ -487,7 +612,11 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
487
612
|
token.contractInfo?.decimals || token.decimals,
|
|
488
613
|
},
|
|
489
614
|
} as any)
|
|
615
|
+
console.log("selected destination token", token)
|
|
616
|
+
|
|
490
617
|
setIsTokenDropdownOpen(false)
|
|
618
|
+
// Track the token selection
|
|
619
|
+
onTrackToken?.(token)
|
|
491
620
|
}}
|
|
492
621
|
onError={onError}
|
|
493
622
|
mode="swap"
|
|
@@ -495,6 +624,18 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
495
624
|
allSupportedTokens={true}
|
|
496
625
|
showContinueButton={false}
|
|
497
626
|
compactMode={false}
|
|
627
|
+
recentTokens={recentTokens}
|
|
628
|
+
onRecentTokenSelect={(token) => {
|
|
629
|
+
setSelectedDestToken({
|
|
630
|
+
...token,
|
|
631
|
+
decimals: token.decimals,
|
|
632
|
+
contractInfo: {
|
|
633
|
+
decimals: token.decimals,
|
|
634
|
+
},
|
|
635
|
+
} as any)
|
|
636
|
+
setIsTokenDropdownOpen(false)
|
|
637
|
+
onRecentTokenSelect?.(token)
|
|
638
|
+
}}
|
|
498
639
|
/>
|
|
499
640
|
</div>
|
|
500
641
|
</div>
|
|
@@ -505,54 +646,18 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
505
646
|
{/* Bottom Info Row */}
|
|
506
647
|
<div className="mt-2 flex justify-between items-center">
|
|
507
648
|
{/* Destination Amount USD from Quote */}
|
|
508
|
-
{prepareSendQuote?.destinationAmountUsdDisplay
|
|
649
|
+
{(prepareSendQuote?.destinationAmountUsdDisplay ||
|
|
650
|
+
(isLoadingQuote && tradeType === TradeType.EXACT_INPUT)) && (
|
|
509
651
|
<div className="text-xs trails-text-muted">
|
|
510
|
-
≈
|
|
652
|
+
≈{" "}
|
|
653
|
+
{isLoadingQuote && tradeType === TradeType.EXACT_INPUT
|
|
654
|
+
? "$0.00"
|
|
655
|
+
: prepareSendQuote?.destinationAmountUsdDisplay}
|
|
511
656
|
</div>
|
|
512
657
|
)}
|
|
513
658
|
</div>
|
|
514
659
|
</div>
|
|
515
660
|
|
|
516
|
-
{/* Receive Section - Show what user will receive */}
|
|
517
|
-
{(toAmount || toChainId || toToken) && selectedDestinationChain && (
|
|
518
|
-
<div className="space-y-1">
|
|
519
|
-
<div className="text-lg font-semibold text-left trails-text-primary">
|
|
520
|
-
You Will Receive
|
|
521
|
-
</div>
|
|
522
|
-
|
|
523
|
-
<div className="p-2">
|
|
524
|
-
<div className="flex items-center space-x-3">
|
|
525
|
-
<TokenImage
|
|
526
|
-
symbol={selectedDestToken?.symbol}
|
|
527
|
-
imageUrl={selectedDestToken?.imageUrl}
|
|
528
|
-
chainId={selectedDestinationChain.id}
|
|
529
|
-
size={32}
|
|
530
|
-
/>
|
|
531
|
-
<div>
|
|
532
|
-
<div className="flex items-center space-x-2">
|
|
533
|
-
<div
|
|
534
|
-
className={`text-lg font-semibold trails-text-primary ${isLoadingQuote ? "animate-pulse" : ""}`}
|
|
535
|
-
>
|
|
536
|
-
{toAmountDisplay} {selectedDestToken?.symbol}
|
|
537
|
-
</div>
|
|
538
|
-
{isLoadingQuote && (
|
|
539
|
-
<div className="animate-spin trails-border-radius-button h-4 w-4 border-solid border-b-2 trails-primary" />
|
|
540
|
-
)}
|
|
541
|
-
</div>
|
|
542
|
-
<div
|
|
543
|
-
className={`text-xs trails-text-muted ${isLoadingQuote ? "animate-pulse" : ""}`}
|
|
544
|
-
>
|
|
545
|
-
≈ {amountUsdDisplay}{" "}
|
|
546
|
-
{selectedDestinationChain
|
|
547
|
-
? `on ${selectedDestinationChain.name}`
|
|
548
|
-
: ""}
|
|
549
|
-
</div>
|
|
550
|
-
</div>
|
|
551
|
-
</div>
|
|
552
|
-
</div>
|
|
553
|
-
</div>
|
|
554
|
-
)}
|
|
555
|
-
|
|
556
661
|
{/* Fee Options */}
|
|
557
662
|
<FeeOptions
|
|
558
663
|
options={FEE_TOKENS}
|
|
@@ -561,6 +666,18 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
561
666
|
/>
|
|
562
667
|
|
|
563
668
|
{/* Warning Messages - Show only one at a time */}
|
|
669
|
+
{isSameTokenWithoutCustomCalldata ? (
|
|
670
|
+
<ErrorDisplay
|
|
671
|
+
errorPrettified="Cannot swap to the same token on the same chain without custom calldata. Please select a different destination token."
|
|
672
|
+
severity="error"
|
|
673
|
+
/>
|
|
674
|
+
) : (
|
|
675
|
+
<ErrorDisplay
|
|
676
|
+
errorPrettified={quoteErrorPrettified}
|
|
677
|
+
error={quoteError}
|
|
678
|
+
severity="warning"
|
|
679
|
+
/>
|
|
680
|
+
)}
|
|
564
681
|
{prepareSendQuote?.noSufficientBalance ? (
|
|
565
682
|
<div className="px-2 py-3 rounded-lg bg-amber-500/10 border border-solid border-amber-500/30">
|
|
566
683
|
<div className="flex items-center space-x-2">
|
|
@@ -579,7 +696,8 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
579
696
|
/>
|
|
580
697
|
</svg>
|
|
581
698
|
<p className="text-sm text-amber-600 dark:text-amber-400">
|
|
582
|
-
Insufficient balance to complete this transaction
|
|
699
|
+
Insufficient balance to complete this transaction. Required{" "}
|
|
700
|
+
{prepareSendQuote?.originAmountDisplay} {originToken?.symbol}
|
|
583
701
|
</p>
|
|
584
702
|
</div>
|
|
585
703
|
</div>
|
|
@@ -618,7 +736,8 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
618
736
|
!isValidCustomToken ||
|
|
619
737
|
isLoadingQuote ||
|
|
620
738
|
!prepareSendQuote ||
|
|
621
|
-
prepareSendQuote?.noSufficientBalance
|
|
739
|
+
prepareSendQuote?.noSufficientBalance ||
|
|
740
|
+
isSameTokenWithoutCustomCalldata
|
|
622
741
|
}
|
|
623
742
|
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"
|
|
624
743
|
>
|
|
@@ -627,6 +746,8 @@ export const Swap: React.FC<SwapProps> = ({
|
|
|
627
746
|
<Loader2 className="w-5 h-5 animate-spin mr-2 trails-text-muted" />
|
|
628
747
|
<span>{buttonText}</span>
|
|
629
748
|
</div>
|
|
749
|
+
) : isSameTokenWithoutCustomCalldata ? (
|
|
750
|
+
"Select Different Tokens"
|
|
630
751
|
) : prepareSendQuote?.noSufficientBalance ? (
|
|
631
752
|
"Insufficient Balance"
|
|
632
753
|
) : (
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type React from "react"
|
|
2
|
+
import { TokenImage } from "./TokenImage.js"
|
|
3
|
+
import type { PrepareSendQuote } from "../../prepareSend.js"
|
|
4
|
+
import { formatElapsed } from "../../utils.js"
|
|
5
|
+
|
|
6
|
+
interface SwapDisplayProps {
|
|
7
|
+
quote: PrepareSendQuote
|
|
8
|
+
elapsedSeconds: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export const SwapDisplay: React.FC<SwapDisplayProps> = ({
|
|
12
|
+
quote,
|
|
13
|
+
elapsedSeconds,
|
|
14
|
+
}) => {
|
|
15
|
+
return (
|
|
16
|
+
<div className="w-full max-w-sm p-3 flex justify-center">
|
|
17
|
+
<div className="flex items-center space-x-4">
|
|
18
|
+
{/* Origin Token */}
|
|
19
|
+
<div className="flex flex-col items-start space-y-1">
|
|
20
|
+
<div style={{ width: "28px", height: "28px" }}>
|
|
21
|
+
<TokenImage
|
|
22
|
+
imageUrl={quote.originToken.imageUrl}
|
|
23
|
+
symbol={quote.originToken.symbol}
|
|
24
|
+
chainId={quote.originChain.id}
|
|
25
|
+
size={28}
|
|
26
|
+
/>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="text-left">
|
|
29
|
+
<div className="text-base font-bold text-gray-900 dark:text-white">
|
|
30
|
+
{quote.originToken.symbol}
|
|
31
|
+
</div>
|
|
32
|
+
<div className="text-xs text-gray-600 dark:text-gray-400">
|
|
33
|
+
{quote.originAmountFormatted}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</div>
|
|
37
|
+
|
|
38
|
+
{/* Arrow with elapsed time below */}
|
|
39
|
+
<div className="flex flex-col items-center space-y-1">
|
|
40
|
+
<div className="text-xl text-gray-400 dark:text-gray-500">→</div>
|
|
41
|
+
<div className="text-xs text-gray-500 dark:text-gray-400">
|
|
42
|
+
{formatElapsed(elapsedSeconds)}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
{/* Destination Token */}
|
|
47
|
+
<div className="flex flex-col items-start space-y-1">
|
|
48
|
+
<div style={{ width: "28px", height: "28px" }}>
|
|
49
|
+
<TokenImage
|
|
50
|
+
imageUrl={quote.destinationToken.imageUrl}
|
|
51
|
+
symbol={quote.destinationToken.symbol}
|
|
52
|
+
chainId={quote.destinationChain.id}
|
|
53
|
+
size={28}
|
|
54
|
+
/>
|
|
55
|
+
</div>
|
|
56
|
+
<div className="text-left">
|
|
57
|
+
<div className="text-base font-bold text-gray-900 dark:text-white">
|
|
58
|
+
{quote.destinationToken.symbol}
|
|
59
|
+
</div>
|
|
60
|
+
<div className="text-xs text-gray-600 dark:text-gray-400">
|
|
61
|
+
{quote.destinationAmountFormatted}
|
|
62
|
+
</div>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
)
|
|
68
|
+
}
|
|
@@ -211,7 +211,7 @@ export const TokenSelector: React.FC<TokenSelectorProps> = ({
|
|
|
211
211
|
{/* Token List */}
|
|
212
212
|
{chainFilteredTokens.length > 0 && (
|
|
213
213
|
<div
|
|
214
|
-
className={`${compactMode ? "max-h-48" : "max-h-[24vh]"} overflow-y-auto trails-scrollbar space-y-1`}
|
|
214
|
+
className={`${compactMode ? "max-h-48" : filteredRecentTokens.length > 0 ? "max-h-[17vh]" : "max-h-[24vh]"} overflow-y-auto trails-scrollbar space-y-1`}
|
|
215
215
|
>
|
|
216
216
|
<AnimatePresence mode="popLayout">
|
|
217
217
|
{chainFilteredTokens.map((token: TokenFormatted) => {
|