0xtrails 0.8.2 → 0.8.3
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/aave.d.ts.map +1 -1
- package/dist/{ccip-ru_Yzdas.js → ccip-Bs-QcZXm.js} +13 -13
- package/dist/constants.d.ts +2 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/fees.d.ts +11 -17
- package/dist/fees.d.ts.map +1 -1
- package/dist/{index-Si7cO9V7.js → index-C_EsqqSn.js} +20320 -20063
- package/dist/index.js +425 -847
- package/dist/intents.d.ts +1 -2
- package/dist/intents.d.ts.map +1 -1
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/recover.d.ts +8 -9
- package/dist/recover.d.ts.map +1 -1
- package/dist/tokenBalances.d.ts +51 -0
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/trailsRouter.d.ts +15 -0
- package/dist/trailsRouter.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +1 -3
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
- package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -3
- package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/crossChain.d.ts +2 -4
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +5 -4
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts +1 -1
- package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
- package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
- package/dist/transactionIntent/types.d.ts +11 -18
- package/dist/transactionIntent/types.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts +2 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +1 -1
- package/dist/widget/hooks/useQuote.d.ts +94 -35
- package/dist/widget/hooks/useQuote.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +2 -2
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
- package/dist/widget/index.js +1 -1
- package/package.json +2 -2
- package/src/aave.ts +4 -0
- package/src/constants.ts +4 -0
- package/src/fees.ts +47 -72
- package/src/intents.ts +1 -3
- package/src/morpho.ts +1 -1
- package/src/prepareSend.ts +42 -6
- package/src/recover.ts +116 -172
- package/src/tokenBalances.ts +301 -1
- package/src/trailsRouter.ts +77 -0
- package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -6
- package/src/transactionIntent/deposits/standardDeposit.ts +167 -184
- package/src/transactionIntent/handlers/crossChain.ts +8 -11
- package/src/transactionIntent/handlers/sameChainSameToken.ts +619 -608
- package/src/transactionIntent/quote/normalizeQuote.ts +32 -46
- package/src/transactionIntent/quote/quoteHelpers.ts +4 -2
- package/src/transactionIntent/types.ts +11 -18
- package/src/widget/compiled.css +1 -1
- package/src/widget/components/AccountIntentTransactionHistory.tsx +50 -18
- package/src/widget/components/ClassicSwap.tsx +25 -30
- package/src/widget/components/QuoteDetails.tsx +18 -27
- package/src/widget/components/SlippageToleranceSettings.tsx +55 -25
- package/src/widget/hooks/useQuote.ts +317 -79
- package/src/widget/hooks/useSendForm.ts +123 -764
- package/src/widget/hooks/useTrailsSendTransaction.ts +0 -2
|
@@ -8,24 +8,13 @@ import {
|
|
|
8
8
|
isAddress,
|
|
9
9
|
parseUnits,
|
|
10
10
|
type WalletClient,
|
|
11
|
-
zeroAddress,
|
|
12
11
|
} from "viem"
|
|
13
12
|
import { useAccount } from "wagmi"
|
|
14
|
-
import {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
useChainRpcClient,
|
|
18
|
-
} from "../../chains.js"
|
|
19
|
-
import { getFullErrorMessage, getPrettifiedErrorMessage } from "../../error.js"
|
|
20
|
-
import {
|
|
21
|
-
prepareSend,
|
|
22
|
-
TradeType,
|
|
23
|
-
type PrepareSendReturn,
|
|
24
|
-
type PrepareSendQuote,
|
|
25
|
-
} from "../../prepareSend.js"
|
|
13
|
+
import { getChainInfo, useSupportedChains } from "../../chains.js"
|
|
14
|
+
import { getFullErrorMessage } from "../../error.js"
|
|
15
|
+
import { TradeType, type PrepareSendQuote } from "../../prepareSend.js"
|
|
26
16
|
import type { TransactionState } from "../../transactions.js"
|
|
27
|
-
import {
|
|
28
|
-
import { useQueryParams } from "../../queryParams.js"
|
|
17
|
+
import { useTokenPrices, normalizeNumber } from "../../prices.js"
|
|
29
18
|
import {
|
|
30
19
|
formatRawAmount,
|
|
31
20
|
formatUsdAmountDisplay,
|
|
@@ -42,9 +31,7 @@ import {
|
|
|
42
31
|
} from "../../tokens.js"
|
|
43
32
|
import type { CheckoutOnHandlers } from "./useCheckout.js"
|
|
44
33
|
import { useResolveEnsAddress } from "../../ens.js"
|
|
45
|
-
import { etherlink } from "viem/chains"
|
|
46
34
|
import { logger } from "../../logger.js"
|
|
47
|
-
import { getIsContract } from "../../contractUtils.js"
|
|
48
35
|
import { useTrailsClient } from "../../trailsClient.js"
|
|
49
36
|
import { useTrails } from "../providers/TrailsProvider.js"
|
|
50
37
|
import { useTokenList } from "./useTokenList.js"
|
|
@@ -54,6 +41,7 @@ import {
|
|
|
54
41
|
} from "./useSelectedFeeOption.js"
|
|
55
42
|
import { useWidgetProps } from "./useWidgetProps.js"
|
|
56
43
|
import { useCustomTokens } from "../../customTokens.js"
|
|
44
|
+
import { useQuote } from "./useQuote.js"
|
|
57
45
|
|
|
58
46
|
type ChainInfo = {
|
|
59
47
|
id: number
|
|
@@ -164,7 +152,7 @@ export function useSendForm({
|
|
|
164
152
|
toChainId, // Custom specified destination chain id
|
|
165
153
|
toToken, // Custom specified destination token address or symbol
|
|
166
154
|
toCalldata, // Custom specified destination calldata
|
|
167
|
-
refundAddress, // Custom specified refund address
|
|
155
|
+
refundAddress: _refundAddress, // Custom specified refund address (unused - useQuote handles this internally)
|
|
168
156
|
walletClient,
|
|
169
157
|
onTransactionStateChange,
|
|
170
158
|
onError,
|
|
@@ -182,39 +170,15 @@ export function useSendForm({
|
|
|
182
170
|
mode,
|
|
183
171
|
onNavigateToOnramp,
|
|
184
172
|
checkoutOnHandlers,
|
|
185
|
-
refetchTrigger = 0,
|
|
173
|
+
refetchTrigger: _refetchTrigger = 0, // Unused - useQuote handles quote refreshing via React Query
|
|
186
174
|
}: UseSendProps): UseSendReturn {
|
|
187
|
-
// Get the active wallet ID from wagmi
|
|
188
|
-
const { connector } = useAccount()
|
|
189
|
-
const walletId = connector?.id
|
|
175
|
+
// Get the active wallet ID from wagmi (currently unused - useQuote handles this internally)
|
|
176
|
+
const { connector: _connector } = useAccount()
|
|
190
177
|
|
|
191
178
|
// Read isSmartWallet from widget props
|
|
192
179
|
const { isSmartWallet } = useWidgetProps()
|
|
193
180
|
|
|
194
|
-
//
|
|
195
|
-
const effectiveSwapProvider = useMemo(() => {
|
|
196
|
-
if (!swapProvider || swapProvider === "auto") {
|
|
197
|
-
if (
|
|
198
|
-
selectedToken?.chainId === etherlink.id ||
|
|
199
|
-
toChainId === etherlink.id
|
|
200
|
-
) {
|
|
201
|
-
return "lifi"
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
return swapProvider
|
|
205
|
-
}, [swapProvider, selectedToken?.chainId, toChainId])
|
|
206
|
-
|
|
207
|
-
const effectiveBridgeProvider = useMemo(() => {
|
|
208
|
-
if (!bridgeProvider || bridgeProvider === "auto") {
|
|
209
|
-
if (
|
|
210
|
-
selectedToken?.chainId === etherlink.id ||
|
|
211
|
-
toChainId === etherlink.id
|
|
212
|
-
) {
|
|
213
|
-
return "lifi"
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return bridgeProvider
|
|
217
|
-
}, [bridgeProvider, selectedToken?.chainId, toChainId])
|
|
181
|
+
// Note: swapProvider and bridgeProvider auto-detection for etherlink is now handled in useQuote
|
|
218
182
|
|
|
219
183
|
const [amount, setAmount] = useState(
|
|
220
184
|
tradeType === TradeType.EXACT_INPUT ? "" : (toAmount ?? ""),
|
|
@@ -222,10 +186,6 @@ export function useSendForm({
|
|
|
222
186
|
const [recipientInput, setRecipientInput] = useState(toRecipient ?? "")
|
|
223
187
|
const [recipient, setRecipient] = useState(toRecipient ?? "")
|
|
224
188
|
const [error, setError] = useState<string | null>(null)
|
|
225
|
-
const [quoteError, setQuoteError] = useState<string | null>(null)
|
|
226
|
-
const [quoteErrorPrettified, setQuoteErrorPrettified] = useState<
|
|
227
|
-
string | null
|
|
228
|
-
>(null)
|
|
229
189
|
const { supportedChains } = useSupportedChains()
|
|
230
190
|
const { ensAddress } = useResolveEnsAddress({
|
|
231
191
|
textInput: recipientInput,
|
|
@@ -282,115 +242,7 @@ export function useSendForm({
|
|
|
282
242
|
refetchOnReconnect: false,
|
|
283
243
|
})
|
|
284
244
|
|
|
285
|
-
//
|
|
286
|
-
const destinationChainPublicClient = useChainRpcClient(
|
|
287
|
-
selectedDestinationChain?.id,
|
|
288
|
-
)
|
|
289
|
-
const originChainPublicClient = useChainRpcClient(selectedToken?.chainId)
|
|
290
|
-
|
|
291
|
-
// Check if recipient is a contract address
|
|
292
|
-
useEffect(() => {
|
|
293
|
-
const checkRecipientContract = async () => {
|
|
294
|
-
if (
|
|
295
|
-
recipient &&
|
|
296
|
-
isAddress(recipient) &&
|
|
297
|
-
selectedDestinationChain?.id &&
|
|
298
|
-
destinationChainPublicClient
|
|
299
|
-
) {
|
|
300
|
-
try {
|
|
301
|
-
const isContract = await getIsContract(
|
|
302
|
-
recipient as `0x${string}`,
|
|
303
|
-
selectedDestinationChain.id,
|
|
304
|
-
destinationChainPublicClient,
|
|
305
|
-
)
|
|
306
|
-
logger.console.log("[trails-sdk] isRecipientContract:", isContract)
|
|
307
|
-
setIsRecipientContract(isContract)
|
|
308
|
-
} catch (error) {
|
|
309
|
-
logger.console.error(
|
|
310
|
-
"[trails-sdk] Error checking if recipient is contract:",
|
|
311
|
-
error,
|
|
312
|
-
)
|
|
313
|
-
setIsRecipientContract(false)
|
|
314
|
-
}
|
|
315
|
-
} else {
|
|
316
|
-
setIsRecipientContract(false)
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
checkRecipientContract()
|
|
321
|
-
}, [recipient, selectedDestinationChain?.id, destinationChainPublicClient])
|
|
322
|
-
|
|
323
|
-
// Check if sender is a contract address on origin chain
|
|
324
|
-
useEffect(() => {
|
|
325
|
-
const checkSenderContractOnOrigin = async () => {
|
|
326
|
-
if (
|
|
327
|
-
account?.address &&
|
|
328
|
-
selectedToken?.chainId &&
|
|
329
|
-
originChainPublicClient
|
|
330
|
-
) {
|
|
331
|
-
try {
|
|
332
|
-
const isContract = await getIsContract(
|
|
333
|
-
account.address as `0x${string}`,
|
|
334
|
-
selectedToken.chainId,
|
|
335
|
-
originChainPublicClient,
|
|
336
|
-
)
|
|
337
|
-
logger.console.log(
|
|
338
|
-
"[trails-sdk] isSenderContractOnOrigin:",
|
|
339
|
-
isContract,
|
|
340
|
-
)
|
|
341
|
-
setIsSenderContractOnOrigin(isContract)
|
|
342
|
-
} catch (error) {
|
|
343
|
-
logger.console.error(
|
|
344
|
-
"[trails-sdk] Error checking if sender is contract on origin:",
|
|
345
|
-
error,
|
|
346
|
-
)
|
|
347
|
-
setIsSenderContractOnOrigin(false)
|
|
348
|
-
}
|
|
349
|
-
} else {
|
|
350
|
-
setIsSenderContractOnOrigin(false)
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
checkSenderContractOnOrigin()
|
|
355
|
-
}, [account?.address, selectedToken?.chainId, originChainPublicClient])
|
|
356
|
-
|
|
357
|
-
// Check if sender is a contract address on destination chain
|
|
358
|
-
useEffect(() => {
|
|
359
|
-
const checkSenderContractOnDestination = async () => {
|
|
360
|
-
if (
|
|
361
|
-
account?.address &&
|
|
362
|
-
selectedDestinationChain?.id &&
|
|
363
|
-
destinationChainPublicClient
|
|
364
|
-
) {
|
|
365
|
-
try {
|
|
366
|
-
const isContract = await getIsContract(
|
|
367
|
-
account.address as `0x${string}`,
|
|
368
|
-
selectedDestinationChain.id,
|
|
369
|
-
destinationChainPublicClient,
|
|
370
|
-
)
|
|
371
|
-
logger.console.log(
|
|
372
|
-
"[trails-sdk] isSenderContractOnDestination:",
|
|
373
|
-
isContract,
|
|
374
|
-
)
|
|
375
|
-
setIsSenderContractOnDestination(isContract)
|
|
376
|
-
} catch (error) {
|
|
377
|
-
logger.console.error(
|
|
378
|
-
"[trails-sdk] Error checking if sender is contract on destination:",
|
|
379
|
-
error,
|
|
380
|
-
)
|
|
381
|
-
setIsSenderContractOnDestination(false)
|
|
382
|
-
}
|
|
383
|
-
} else {
|
|
384
|
-
setIsSenderContractOnDestination(false)
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
checkSenderContractOnDestination()
|
|
389
|
-
}, [
|
|
390
|
-
account?.address,
|
|
391
|
-
selectedDestinationChain?.id,
|
|
392
|
-
destinationChainPublicClient,
|
|
393
|
-
])
|
|
245
|
+
// Note: Contract detection is now handled in useQuote
|
|
394
246
|
|
|
395
247
|
const isCustomToken = useMemo(() => toToken?.startsWith("0x"), [toToken])
|
|
396
248
|
|
|
@@ -538,8 +390,12 @@ export function useSendForm({
|
|
|
538
390
|
}
|
|
539
391
|
}, [selectedDestToken, defaultDestToken])
|
|
540
392
|
|
|
541
|
-
|
|
542
|
-
const
|
|
393
|
+
// These are handled internally by useQuote via TrailsProvider context
|
|
394
|
+
const _trailsClient = useTrailsClient()
|
|
395
|
+
const {
|
|
396
|
+
trailsApiKey: _trailsApiKey,
|
|
397
|
+
sequenceIndexerUrl: _sequenceIndexerUrl,
|
|
398
|
+
} = useTrails()
|
|
543
399
|
|
|
544
400
|
// Get user's token balances for balance checking
|
|
545
401
|
const { filteredTokensFormatted } = useTokenList({
|
|
@@ -645,12 +501,6 @@ export function useSendForm({
|
|
|
645
501
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
|
646
502
|
const [isWaitingForWalletConfirm, setIsWaitingForWalletConfirm] =
|
|
647
503
|
useState(false)
|
|
648
|
-
const [isLoadingQuote, setIsLoadingQuote] = useState(false)
|
|
649
|
-
const [prepareSendResult, setPrepareSendResult] =
|
|
650
|
-
useState<PrepareSendReturn | null>(null)
|
|
651
|
-
const latestResolvedIntentIdRef = useRef<string | null>(null)
|
|
652
|
-
const quoteInputsFingerprintRef = useRef<string | null>(null)
|
|
653
|
-
const preparedQuoteFingerprintRef = useRef<string | null>(null)
|
|
654
504
|
|
|
655
505
|
// Create a stable callback for transaction state changes
|
|
656
506
|
const handleTransactionStateChange = useCallback(
|
|
@@ -696,14 +546,8 @@ export function useSendForm({
|
|
|
696
546
|
setFeeOptions,
|
|
697
547
|
} = useSelectedFeeOption()
|
|
698
548
|
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
useState(false)
|
|
702
|
-
const [isSenderContractOnDestination, setIsSenderContractOnDestination] =
|
|
703
|
-
useState(false)
|
|
704
|
-
|
|
705
|
-
const { hasParam } = useQueryParams()
|
|
706
|
-
const isDryMode = hasParam("dryMode", "true")
|
|
549
|
+
// Note: Contract detection (isRecipientContract, isSenderContractOnOrigin, isSenderContractOnDestination)
|
|
550
|
+
// is now handled in useQuote and returned from there
|
|
707
551
|
|
|
708
552
|
const destinationTokenAddressFromTokenSymbol = useTokenAddress({
|
|
709
553
|
chainId: selectedDestinationChain?.id,
|
|
@@ -765,24 +609,31 @@ export function useSendForm({
|
|
|
765
609
|
return "0"
|
|
766
610
|
}
|
|
767
611
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
612
|
+
// For EXACT_INPUT: we only need source token decimals (user enters source amount)
|
|
613
|
+
// For EXACT_OUTPUT: we only need destination token decimals (user enters destination amount)
|
|
614
|
+
if (tradeType === TradeType.EXACT_INPUT) {
|
|
615
|
+
if (!selectedToken?.decimals) {
|
|
616
|
+
logger.console.warn(
|
|
617
|
+
"[trails-sdk] Missing source token decimals for quote",
|
|
618
|
+
{
|
|
619
|
+
selectedToken,
|
|
620
|
+
tradeType,
|
|
621
|
+
},
|
|
622
|
+
)
|
|
623
|
+
return "0"
|
|
624
|
+
}
|
|
625
|
+
} else {
|
|
626
|
+
// EXACT_OUTPUT mode
|
|
627
|
+
if (!selectedDestToken?.decimals) {
|
|
628
|
+
logger.console.warn(
|
|
629
|
+
"[trails-sdk] Missing destination token decimals for quote",
|
|
630
|
+
{
|
|
631
|
+
selectedDestToken,
|
|
632
|
+
tradeType,
|
|
633
|
+
},
|
|
634
|
+
)
|
|
635
|
+
return "0"
|
|
636
|
+
}
|
|
786
637
|
}
|
|
787
638
|
|
|
788
639
|
// For EXACT_INPUT: use source token decimals (user enters source amount)
|
|
@@ -816,522 +667,76 @@ export function useSendForm({
|
|
|
816
667
|
tradeType,
|
|
817
668
|
])
|
|
818
669
|
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
destinationChainId: selectedDestinationChain?.id ?? "",
|
|
826
|
-
destinationTokenAddress: destinationTokenAddress?.toLowerCase() ?? "",
|
|
827
|
-
amount: amount || "",
|
|
828
|
-
amountRaw: amountRaw || "",
|
|
829
|
-
recipient: recipient?.toLowerCase() ?? "",
|
|
830
|
-
tradeType,
|
|
831
|
-
toCalldata: toCalldata ?? "",
|
|
832
|
-
}),
|
|
833
|
-
[
|
|
834
|
-
amount,
|
|
835
|
-
amountRaw,
|
|
836
|
-
destinationTokenAddress,
|
|
837
|
-
recipient,
|
|
838
|
-
selectedDestinationChain?.id,
|
|
839
|
-
selectedDestToken?.symbol,
|
|
840
|
-
selectedToken?.chainId,
|
|
841
|
-
selectedToken?.contractAddress,
|
|
842
|
-
toCalldata,
|
|
843
|
-
tradeType,
|
|
844
|
-
],
|
|
845
|
-
)
|
|
670
|
+
// Determine paymaster URL for the origin chain
|
|
671
|
+
const paymasterUrl = useMemo(() => {
|
|
672
|
+
return paymasterUrls?.find(
|
|
673
|
+
(p) => p.chainId.toString() === (selectedToken?.chainId || 0).toString(),
|
|
674
|
+
)?.url
|
|
675
|
+
}, [paymasterUrls, selectedToken?.chainId])
|
|
846
676
|
|
|
847
|
-
//
|
|
848
|
-
const
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
name: selectedDestToken.name,
|
|
861
|
-
decimals: selectedDestToken.decimals,
|
|
862
|
-
}
|
|
863
|
-
: null,
|
|
864
|
-
selectedDestinationChain: selectedDestinationChain
|
|
865
|
-
? {
|
|
866
|
-
id: selectedDestinationChain.id,
|
|
867
|
-
name: selectedDestinationChain.name,
|
|
868
|
-
}
|
|
869
|
-
: null,
|
|
870
|
-
selectedToken: selectedToken
|
|
871
|
-
? {
|
|
872
|
-
symbol: selectedToken.symbol,
|
|
873
|
-
contractAddress: selectedToken.contractAddress,
|
|
874
|
-
chainId: selectedToken.chainId || 0,
|
|
875
|
-
}
|
|
876
|
-
: null,
|
|
877
|
-
recipient,
|
|
878
|
-
conditions: {
|
|
879
|
-
hasAccount: !!account,
|
|
880
|
-
hasAmount: !!amount,
|
|
881
|
-
hasAmountRaw: !!amountRaw,
|
|
882
|
-
amountNotZero: amount !== "0",
|
|
883
|
-
amountRawNotZero: amountRaw !== "0",
|
|
884
|
-
hasDestinationTokenAddress: !!destinationTokenAddress,
|
|
885
|
-
hasDestinationTokenAddressFromSymbol:
|
|
886
|
-
!!destinationTokenAddressFromTokenSymbol,
|
|
887
|
-
isValidRecipientValue: isValidRecipient,
|
|
888
|
-
hasSelectedDestToken: !!selectedDestToken,
|
|
889
|
-
hasSelectedDestinationChain: !!selectedDestinationChain,
|
|
890
|
-
hasSelectedToken: !!selectedToken,
|
|
891
|
-
},
|
|
892
|
-
allConditionsMet:
|
|
893
|
-
!!account &&
|
|
894
|
-
!!amount &&
|
|
895
|
-
!!destinationTokenAddress &&
|
|
896
|
-
isValidRecipient &&
|
|
897
|
-
!!selectedDestToken &&
|
|
898
|
-
!!selectedDestinationChain &&
|
|
899
|
-
amount !== "0" &&
|
|
900
|
-
!!amountRaw &&
|
|
901
|
-
amountRaw !== "0" &&
|
|
902
|
-
!!selectedToken,
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
logger.console.log("[trails-sdk] [DEBUG] getQuote state check", debugState)
|
|
906
|
-
|
|
907
|
-
// Only get quote if all required inputs are present
|
|
908
|
-
// Skip quote if origin and destination tokens are the same
|
|
909
|
-
const isSameToken =
|
|
910
|
-
selectedToken?.chainId === selectedDestinationChain?.id &&
|
|
911
|
-
selectedToken?.contractAddress?.toLowerCase() ===
|
|
912
|
-
destinationTokenAddress?.toLowerCase()
|
|
913
|
-
const isSenderSameAsRecipient =
|
|
914
|
-
account?.address?.toLowerCase() === recipient?.toLowerCase()
|
|
915
|
-
|
|
916
|
-
if (
|
|
917
|
-
!account ||
|
|
918
|
-
!amount ||
|
|
919
|
-
!destinationTokenAddress ||
|
|
920
|
-
!isValidRecipient ||
|
|
921
|
-
!selectedDestToken ||
|
|
922
|
-
!selectedDestinationChain ||
|
|
923
|
-
amount === "0" ||
|
|
924
|
-
!amountRaw ||
|
|
925
|
-
amountRaw === "0" ||
|
|
926
|
-
!selectedToken ||
|
|
927
|
-
(isSameToken &&
|
|
928
|
-
isSenderSameAsRecipient &&
|
|
929
|
-
(mode === "swap" || mode === "fund"))
|
|
930
|
-
) {
|
|
931
|
-
logger.console.log(
|
|
932
|
-
"[trails-sdk] Skipping quote because of missing inputs or same token",
|
|
933
|
-
{
|
|
934
|
-
amount,
|
|
935
|
-
destinationTokenAddress,
|
|
936
|
-
isValidRecipient,
|
|
937
|
-
selectedDestToken,
|
|
938
|
-
selectedDestinationChain,
|
|
939
|
-
amountRaw,
|
|
940
|
-
selectedToken,
|
|
941
|
-
isSameToken,
|
|
942
|
-
debugState,
|
|
943
|
-
},
|
|
944
|
-
)
|
|
945
|
-
setQuoteError(null)
|
|
946
|
-
setPrepareSendResult(null)
|
|
947
|
-
preparedQuoteFingerprintRef.current = null
|
|
948
|
-
return
|
|
949
|
-
}
|
|
950
|
-
|
|
951
|
-
latestResolvedIntentIdRef.current = null
|
|
952
|
-
let requestFingerprint = quoteInputsFingerprintRef.current
|
|
953
|
-
if (!requestFingerprint) {
|
|
954
|
-
requestFingerprint = buildQuoteInputsFingerprint()
|
|
955
|
-
quoteInputsFingerprintRef.current = requestFingerprint
|
|
956
|
-
}
|
|
957
|
-
try {
|
|
958
|
-
setIsLoadingQuote(true)
|
|
959
|
-
setError(null)
|
|
960
|
-
setQuoteError(null)
|
|
961
|
-
|
|
962
|
-
const sourceTokenDecimals = selectedToken.decimals || 18
|
|
963
|
-
const destinationTokenDecimals = selectedDestToken.decimals || 18
|
|
964
|
-
|
|
965
|
-
if (!sourceTokenDecimals || !destinationTokenDecimals) {
|
|
966
|
-
logger.console.warn("[trails-sdk] Missing token decimals for quote", {
|
|
967
|
-
sourceTokenDecimals,
|
|
968
|
-
destinationTokenDecimals,
|
|
969
|
-
selectedToken,
|
|
970
|
-
selectedDestToken,
|
|
971
|
-
tradeType,
|
|
972
|
-
})
|
|
973
|
-
setPrepareSendResult(null)
|
|
974
|
-
preparedQuoteFingerprintRef.current = null
|
|
975
|
-
latestResolvedIntentIdRef.current = null
|
|
976
|
-
setIsLoadingQuote(false)
|
|
977
|
-
return
|
|
978
|
-
}
|
|
979
|
-
|
|
980
|
-
let sourceTokenPriceUsd = selectedToken.priceUsd ?? null
|
|
981
|
-
let destinationTokenPriceUsd = destTokenPrices?.[0]?.priceUsd ?? null
|
|
982
|
-
|
|
983
|
-
if (!sourceTokenPriceUsd) {
|
|
984
|
-
try {
|
|
985
|
-
const price = await getTokenPrice(trailsClient, {
|
|
986
|
-
tokenAddress: selectedToken.contractAddress,
|
|
987
|
-
tokenSymbol: selectedToken.symbol,
|
|
988
|
-
chainId: selectedToken.chainId || 0,
|
|
989
|
-
})
|
|
990
|
-
sourceTokenPriceUsd = price?.priceUsd ?? null
|
|
991
|
-
} catch (error) {
|
|
992
|
-
logger.console.error(
|
|
993
|
-
"[trails-sdk] Error getting source token price:",
|
|
994
|
-
error,
|
|
995
|
-
)
|
|
996
|
-
}
|
|
997
|
-
}
|
|
998
|
-
|
|
999
|
-
if (!destinationTokenPriceUsd) {
|
|
1000
|
-
try {
|
|
1001
|
-
const price = await getTokenPrice(trailsClient, {
|
|
1002
|
-
tokenSymbol: selectedDestToken.symbol,
|
|
1003
|
-
tokenAddress: destinationTokenAddress ?? "",
|
|
1004
|
-
chainId: selectedDestinationChain.id,
|
|
1005
|
-
})
|
|
1006
|
-
destinationTokenPriceUsd = price?.priceUsd ?? null
|
|
1007
|
-
} catch (error) {
|
|
1008
|
-
logger.console.error(
|
|
1009
|
-
"[trails-sdk] Error getting destination token price:",
|
|
1010
|
-
error,
|
|
1011
|
-
)
|
|
1012
|
-
}
|
|
1013
|
-
}
|
|
1014
|
-
|
|
1015
|
-
if (
|
|
1016
|
-
!destinationTokenPriceUsd &&
|
|
1017
|
-
selectedToken.symbol === selectedDestToken.symbol
|
|
1018
|
-
) {
|
|
1019
|
-
destinationTokenPriceUsd = sourceTokenPriceUsd
|
|
1020
|
-
}
|
|
1021
|
-
if (
|
|
1022
|
-
!sourceTokenPriceUsd &&
|
|
1023
|
-
selectedToken.symbol === selectedDestToken.symbol
|
|
1024
|
-
) {
|
|
1025
|
-
sourceTokenPriceUsd = destinationTokenPriceUsd
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
if (!sourceTokenPriceUsd || !destinationTokenPriceUsd) {
|
|
1029
|
-
logger.console.warn("[trails-sdk] Missing token prices for quote", {
|
|
1030
|
-
sourceTokenPriceUsd,
|
|
1031
|
-
destinationTokenPriceUsd,
|
|
1032
|
-
})
|
|
1033
|
-
}
|
|
1034
|
-
|
|
1035
|
-
let nativeTokenPriceUsd = 0
|
|
1036
|
-
if (
|
|
1037
|
-
selectedToken.contractAddress === zeroAddress &&
|
|
1038
|
-
sourceTokenPriceUsd
|
|
1039
|
-
) {
|
|
1040
|
-
nativeTokenPriceUsd = sourceTokenPriceUsd
|
|
1041
|
-
} else {
|
|
1042
|
-
const originChain = getChainInfo(selectedToken.chainId || 0)
|
|
1043
|
-
const nativeTokenSymbol = originChain?.nativeCurrency?.symbol ?? ""
|
|
1044
|
-
const nativePrice = await getTokenPrice(trailsClient, {
|
|
1045
|
-
tokenSymbol: nativeTokenSymbol,
|
|
1046
|
-
tokenAddress: zeroAddress,
|
|
1047
|
-
chainId: selectedToken.chainId || 0,
|
|
1048
|
-
})
|
|
1049
|
-
nativeTokenPriceUsd = nativePrice?.priceUsd ?? 0
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
const options = {
|
|
1053
|
-
account,
|
|
1054
|
-
originTokenAddress: selectedToken.contractAddress,
|
|
1055
|
-
originChainId: selectedToken.chainId || 0,
|
|
1056
|
-
originTokenBalance:
|
|
1057
|
-
fundMethod === "qr-code" || fundMethod === "exchange"
|
|
1058
|
-
? parseUnits("100", selectedToken.decimals ?? 18).toString() // needs to be an amount that is greater than the minimum amount for the swap
|
|
1059
|
-
: selectedToken.balance || "0",
|
|
1060
|
-
destinationChainId: selectedDestinationChain.id,
|
|
1061
|
-
recipient,
|
|
1062
|
-
destinationTokenAddress,
|
|
1063
|
-
swapAmount: amountRaw,
|
|
1064
|
-
tradeType,
|
|
1065
|
-
originTokenSymbol: selectedToken.symbol,
|
|
1066
|
-
destinationTokenSymbol: selectedDestToken.symbol,
|
|
1067
|
-
fee: "0",
|
|
1068
|
-
client: walletClient,
|
|
1069
|
-
trailsClient,
|
|
1070
|
-
destinationCalldata: toCalldata,
|
|
1071
|
-
refundAddress,
|
|
1072
|
-
dryMode: isDryMode,
|
|
1073
|
-
onTransactionStateChange: handleTransactionStateChange,
|
|
1074
|
-
sourceTokenPriceUsd,
|
|
1075
|
-
destinationTokenPriceUsd,
|
|
1076
|
-
sourceTokenDecimals,
|
|
1077
|
-
destinationTokenDecimals,
|
|
1078
|
-
paymasterUrl:
|
|
1079
|
-
paymasterUrls?.find(
|
|
1080
|
-
(p) =>
|
|
1081
|
-
p.chainId.toString() === (selectedToken.chainId || 0).toString(),
|
|
1082
|
-
)?.url ?? undefined,
|
|
1083
|
-
originNativeTokenPriceUsd: nativeTokenPriceUsd,
|
|
1084
|
-
swapProvider: effectiveSwapProvider as RouteProvider | null | undefined,
|
|
1085
|
-
bridgeProvider: effectiveBridgeProvider as
|
|
1086
|
-
| RouteProvider
|
|
1087
|
-
| null
|
|
1088
|
-
| undefined,
|
|
1089
|
-
mode,
|
|
1090
|
-
fundMethod,
|
|
1091
|
-
checkoutOnHandlers,
|
|
1092
|
-
selectedFeeOption: selectedFeeOption ?? null,
|
|
1093
|
-
walletId,
|
|
1094
|
-
sequenceIndexerUrl,
|
|
1095
|
-
sequenceProjectAccessKey: trailsApiKey,
|
|
1096
|
-
originPublicClient: originChainPublicClient ?? undefined,
|
|
1097
|
-
destinationPublicClient: destinationChainPublicClient ?? undefined,
|
|
1098
|
-
isSmartWallet,
|
|
1099
|
-
trailsApiKey,
|
|
1100
|
-
trailsApiUrl: trailsConfig.trailsApiUrl,
|
|
1101
|
-
}
|
|
1102
|
-
|
|
1103
|
-
logger.console.log(
|
|
1104
|
-
"[trails-sdk] [FEE-SELECT] getQuote using selectedFeeOption:",
|
|
1105
|
-
{
|
|
1106
|
-
selectedFeeOption,
|
|
1107
|
-
},
|
|
1108
|
-
)
|
|
1109
|
-
|
|
1110
|
-
const result = await prepareSend(options)
|
|
1111
|
-
|
|
1112
|
-
if (requestFingerprint !== quoteInputsFingerprintRef.current) {
|
|
1113
|
-
logger.console.log(
|
|
1114
|
-
"[trails-sdk] Ignoring stale quote result (fingerprint mismatch)",
|
|
1115
|
-
{
|
|
1116
|
-
requestFingerprint,
|
|
1117
|
-
currentFingerprint: quoteInputsFingerprintRef.current,
|
|
1118
|
-
},
|
|
1119
|
-
)
|
|
1120
|
-
return
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
logger.console.log("[trails-sdk] prepareSend quote:", result.quote)
|
|
1124
|
-
|
|
1125
|
-
// Track the intentId returned by the most recent quote so we never execute a stale CommitIntent
|
|
1126
|
-
// even when the quote inputs (fingerprint) are identical between retries.
|
|
1127
|
-
const quoteIntentId = result.quote.intentId ?? null
|
|
1128
|
-
|
|
1129
|
-
logger.console.log("[trails-sdk] Resolved quote intentId:", quoteIntentId)
|
|
1130
|
-
|
|
1131
|
-
latestResolvedIntentIdRef.current = quoteIntentId
|
|
1132
|
-
setPrepareSendResult(result)
|
|
1133
|
-
preparedQuoteFingerprintRef.current = requestFingerprint
|
|
1134
|
-
setIsLoadingQuote(false)
|
|
1135
|
-
} catch (error) {
|
|
1136
|
-
if (requestFingerprint !== quoteInputsFingerprintRef.current) {
|
|
1137
|
-
logger.console.log(
|
|
1138
|
-
"[trails-sdk] Ignoring quote error (fingerprint mismatch)",
|
|
1139
|
-
{
|
|
1140
|
-
requestFingerprint,
|
|
1141
|
-
currentFingerprint: quoteInputsFingerprintRef.current,
|
|
1142
|
-
},
|
|
1143
|
-
)
|
|
1144
|
-
return
|
|
1145
|
-
}
|
|
1146
|
-
|
|
1147
|
-
logger.console.error("[trails-sdk] Error getting quote:", error)
|
|
1148
|
-
const errorMessage = getFullErrorMessage(error)
|
|
1149
|
-
setQuoteError(errorMessage)
|
|
1150
|
-
setPrepareSendResult(null)
|
|
1151
|
-
preparedQuoteFingerprintRef.current = null
|
|
1152
|
-
latestResolvedIntentIdRef.current = null
|
|
1153
|
-
setIsLoadingQuote(false)
|
|
1154
|
-
}
|
|
1155
|
-
}, [
|
|
1156
|
-
trailsApiKey,
|
|
1157
|
-
tradeType,
|
|
1158
|
-
isDryMode,
|
|
1159
|
-
account,
|
|
677
|
+
// Use the centralized useQuote hook for quote fetching
|
|
678
|
+
const {
|
|
679
|
+
quote: quoteResult,
|
|
680
|
+
send: sendFn,
|
|
681
|
+
isLoadingQuote: quoteIsLoading,
|
|
682
|
+
quoteError: rawQuoteError,
|
|
683
|
+
quoteErrorPrettified: rawQuoteErrorPrettified,
|
|
684
|
+
feeOptions: quoteFeeOptions,
|
|
685
|
+
// Contract detection from useQuote
|
|
686
|
+
isRecipientContract: quoteIsRecipientContract,
|
|
687
|
+
isSenderContractOnOrigin: quoteIsSenderContractOnOrigin,
|
|
688
|
+
isSenderContractOnDestination: quoteIsSenderContractOnDestination,
|
|
689
|
+
} = useQuote({
|
|
1160
690
|
walletClient,
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
selectedDestinationChain?.id,
|
|
1169
|
-
selectedToken?.contractAddress,
|
|
1170
|
-
selectedToken?.chainId,
|
|
1171
|
-
selectedToken?.balance,
|
|
1172
|
-
selectedToken?.priceUsd,
|
|
691
|
+
fromTokenAddress: selectedToken?.contractAddress ?? null,
|
|
692
|
+
fromChainId: selectedToken?.chainId ?? null,
|
|
693
|
+
toTokenAddress: destinationTokenAddress ?? null,
|
|
694
|
+
toChainId: selectedDestinationChain?.id ?? null,
|
|
695
|
+
swapAmount: amountRaw !== "0" ? amountRaw : undefined,
|
|
696
|
+
tradeType,
|
|
697
|
+
toAddress: isValidRecipient ? recipient : null,
|
|
1173
698
|
toCalldata,
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
isValidRecipient,
|
|
1178
|
-
destTokenPrices?.[0]?.priceUsd,
|
|
1179
|
-
amount,
|
|
1180
|
-
selectedDestToken,
|
|
1181
|
-
selectedDestinationChain,
|
|
1182
|
-
selectedToken,
|
|
1183
|
-
effectiveSwapProvider,
|
|
1184
|
-
effectiveBridgeProvider,
|
|
1185
|
-
fundMethod,
|
|
1186
|
-
amountRaw,
|
|
699
|
+
onStatusUpdate: handleTransactionStateChange,
|
|
700
|
+
swapProvider: swapProvider as RouteProvider | null | undefined,
|
|
701
|
+
bridgeProvider: bridgeProvider as RouteProvider | null | undefined,
|
|
1187
702
|
checkoutOnHandlers,
|
|
1188
|
-
|
|
1189
|
-
selectedFeeOption,
|
|
1190
|
-
walletId,
|
|
1191
|
-
buildQuoteInputsFingerprint,
|
|
1192
|
-
originChainPublicClient,
|
|
1193
|
-
destinationChainPublicClient,
|
|
703
|
+
paymasterUrl,
|
|
704
|
+
selectedFeeOption: selectedFeeOption ?? null,
|
|
1194
705
|
isSmartWallet,
|
|
1195
|
-
|
|
1196
|
-
|
|
706
|
+
fundMethod,
|
|
707
|
+
})
|
|
1197
708
|
|
|
1198
|
-
//
|
|
1199
|
-
//
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
// Skip quote if origin and destination tokens are the same
|
|
1204
|
-
const isSameToken =
|
|
1205
|
-
selectedToken?.chainId === selectedDestinationChain?.id &&
|
|
1206
|
-
selectedToken?.contractAddress?.toLowerCase() ===
|
|
1207
|
-
destinationTokenAddress?.toLowerCase()
|
|
1208
|
-
|
|
1209
|
-
const isSenderSameAsRecipient =
|
|
1210
|
-
account?.address?.toLowerCase() === recipient?.toLowerCase()
|
|
1211
|
-
|
|
1212
|
-
const conditions = {
|
|
1213
|
-
hasAmount: !!amount,
|
|
1214
|
-
amountNotZero: amount !== "0",
|
|
1215
|
-
hasDestinationTokenAddress: !!destinationTokenAddress,
|
|
1216
|
-
isValidRecipient,
|
|
1217
|
-
hasSelectedDestTokenSymbol: !!selectedDestToken?.symbol,
|
|
1218
|
-
hasSelectedDestinationChain: !!selectedDestinationChain?.id,
|
|
1219
|
-
hasSelectedToken: !!selectedToken,
|
|
1220
|
-
isSameToken,
|
|
1221
|
-
isSenderSameAsRecipient,
|
|
1222
|
-
mode,
|
|
1223
|
-
}
|
|
709
|
+
// Map useQuote outputs for backward compatibility with existing code
|
|
710
|
+
// This allows gradual migration without breaking existing functionality
|
|
711
|
+
const isLoadingQuote = quoteIsLoading
|
|
712
|
+
const quoteError = rawQuoteError ? getFullErrorMessage(rawQuoteError) : null
|
|
713
|
+
const quoteErrorPrettified = rawQuoteErrorPrettified || null
|
|
1224
714
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
selectedDestToken: selectedDestToken
|
|
1230
|
-
? {
|
|
1231
|
-
symbol: selectedDestToken.symbol,
|
|
1232
|
-
decimals: selectedDestToken.decimals,
|
|
1233
|
-
contractAddress: selectedDestToken.contractAddress,
|
|
1234
|
-
}
|
|
1235
|
-
: null,
|
|
1236
|
-
destinationTokenAddress,
|
|
1237
|
-
amount,
|
|
1238
|
-
recipient,
|
|
1239
|
-
},
|
|
1240
|
-
)
|
|
715
|
+
// Create a compatibility layer for prepareSendResult
|
|
716
|
+
// This maps the useQuote output to the shape expected by existing code
|
|
717
|
+
const prepareSendResult = useMemo(() => {
|
|
718
|
+
if (!quoteResult || !sendFn) return null
|
|
1241
719
|
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
amount === "0" ||
|
|
1245
|
-
!destinationTokenAddress ||
|
|
1246
|
-
!isValidRecipient ||
|
|
1247
|
-
!selectedDestToken?.symbol ||
|
|
1248
|
-
!selectedDestinationChain?.id ||
|
|
1249
|
-
!selectedToken ||
|
|
1250
|
-
(isSameToken &&
|
|
1251
|
-
isSenderSameAsRecipient &&
|
|
1252
|
-
(mode === "swap" || mode === "fund"))
|
|
1253
|
-
) {
|
|
1254
|
-
logger.console.log(
|
|
1255
|
-
"[trails-sdk] [CUSTOM-TOKEN] Quote blocked - missing conditions:",
|
|
1256
|
-
{
|
|
1257
|
-
blockedReasons: {
|
|
1258
|
-
noAmount: !amount || amount === "0",
|
|
1259
|
-
noDestinationTokenAddress: !destinationTokenAddress,
|
|
1260
|
-
invalidRecipient: !isValidRecipient,
|
|
1261
|
-
noSelectedDestTokenSymbol: !selectedDestToken?.symbol,
|
|
1262
|
-
noSelectedDestinationChain: !selectedDestinationChain?.id,
|
|
1263
|
-
noSelectedToken: !selectedToken,
|
|
1264
|
-
sameTokenAndRecipient:
|
|
1265
|
-
isSameToken &&
|
|
1266
|
-
isSenderSameAsRecipient &&
|
|
1267
|
-
(mode === "swap" || mode === "fund"),
|
|
1268
|
-
},
|
|
1269
|
-
conditions,
|
|
1270
|
-
},
|
|
1271
|
-
)
|
|
1272
|
-
setPrepareSendResult(null)
|
|
1273
|
-
latestResolvedIntentIdRef.current = null
|
|
1274
|
-
quoteInputsFingerprintRef.current = null
|
|
1275
|
-
preparedQuoteFingerprintRef.current = null
|
|
1276
|
-
setIsLoadingQuote(false)
|
|
1277
|
-
return
|
|
1278
|
-
}
|
|
720
|
+
// Quote type now has all PrepareSendQuote fields with proper defaults
|
|
721
|
+
const quote = { ...quoteResult } as PrepareSendQuote
|
|
1279
722
|
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
nextFingerprint: nextQuoteInputsFingerprint,
|
|
1285
|
-
willTrigger:
|
|
1286
|
-
quoteInputsFingerprintRef.current !== nextQuoteInputsFingerprint,
|
|
1287
|
-
})
|
|
1288
|
-
|
|
1289
|
-
// Fingerprint all quote-driving inputs so we can invalidate debounced quotes immediately when
|
|
1290
|
-
// the user switches tokens; this fixes the stale quote so that it stays loading until the new quote is ready.
|
|
1291
|
-
if (quoteInputsFingerprintRef.current !== nextQuoteInputsFingerprint) {
|
|
1292
|
-
logger.console.log(
|
|
1293
|
-
"[trails-sdk] [CUSTOM-TOKEN] Quote fingerprint changed, triggering quote fetch",
|
|
1294
|
-
)
|
|
1295
|
-
quoteInputsFingerprintRef.current = nextQuoteInputsFingerprint
|
|
1296
|
-
latestResolvedIntentIdRef.current = null
|
|
1297
|
-
setPrepareSendResult(null)
|
|
1298
|
-
preparedQuoteFingerprintRef.current = null
|
|
1299
|
-
setIsLoadingQuote(true)
|
|
723
|
+
return {
|
|
724
|
+
quote,
|
|
725
|
+
feeOptions: { feeOptions: quoteFeeOptions },
|
|
726
|
+
// Note: send function is handled separately via sendFn
|
|
1300
727
|
}
|
|
1301
|
-
|
|
1302
|
-
const timeoutId = setTimeout(() => {
|
|
1303
|
-
logger.console.log(
|
|
1304
|
-
"[trails-sdk] [CUSTOM-TOKEN] Debounce timeout completed, calling getQuote",
|
|
1305
|
-
)
|
|
1306
|
-
getQuote()
|
|
1307
|
-
}, 500) // Debounce by 500ms
|
|
1308
|
-
|
|
1309
|
-
return () => clearTimeout(timeoutId)
|
|
1310
|
-
}, [
|
|
1311
|
-
amount,
|
|
1312
|
-
destinationTokenAddress,
|
|
1313
|
-
isValidRecipient,
|
|
1314
|
-
selectedDestToken?.symbol,
|
|
1315
|
-
selectedDestinationChain?.id,
|
|
1316
|
-
toCalldata,
|
|
1317
|
-
refetchTrigger,
|
|
1318
|
-
amountRaw,
|
|
1319
|
-
tradeType,
|
|
1320
|
-
selectedToken?.contractAddress,
|
|
1321
|
-
selectedToken?.chainId,
|
|
1322
|
-
selectedToken?.balance,
|
|
1323
|
-
selectedToken?.priceUsd,
|
|
1324
|
-
recipient, // Add recipient to trigger quote re-fetch when it changes
|
|
1325
|
-
// selectedFeeOption is passed to send() at execution time, not needed here
|
|
1326
|
-
buildQuoteInputsFingerprint,
|
|
1327
|
-
])
|
|
728
|
+
}, [quoteResult, sendFn, quoteFeeOptions])
|
|
1328
729
|
|
|
1329
730
|
// Calculate destination amount from quote if available
|
|
1330
731
|
const quotedDestinationAmount = useMemo(() => {
|
|
1331
|
-
|
|
1332
|
-
|
|
732
|
+
const currentDestAmount =
|
|
733
|
+
prepareSendResult?.quote?.destinationAmountFormatted
|
|
734
|
+
|
|
735
|
+
if (currentDestAmount && currentDestAmount !== "0") {
|
|
736
|
+
return currentDestAmount
|
|
1333
737
|
}
|
|
1334
738
|
|
|
739
|
+
// Fall back to toAmount prop (for EXACT_OUTPUT mode)
|
|
1335
740
|
return toAmountFormatted
|
|
1336
741
|
}, [prepareSendResult, toAmountFormatted])
|
|
1337
742
|
|
|
@@ -1370,58 +775,24 @@ export function useSendForm({
|
|
|
1370
775
|
logger.console.log("[trails-sdk] processSend called", {
|
|
1371
776
|
fundMethod,
|
|
1372
777
|
hasOnNavigateToOnramp: !!onNavigateToOnramp,
|
|
1373
|
-
|
|
778
|
+
hasQuote: !!prepareSendResult,
|
|
779
|
+
hasSend: !!sendFn,
|
|
1374
780
|
})
|
|
1375
781
|
try {
|
|
1376
|
-
if (!prepareSendResult) {
|
|
782
|
+
if (!prepareSendResult || !sendFn) {
|
|
1377
783
|
setError("No quote available. Please wait for quote to load.")
|
|
1378
784
|
return
|
|
1379
785
|
}
|
|
1380
786
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
!preparedQuoteFingerprintRef.current ||
|
|
1384
|
-
preparedQuoteFingerprintRef.current !== currentQuoteInputsFingerprint
|
|
1385
|
-
) {
|
|
1386
|
-
logger.console.warn(
|
|
1387
|
-
"[trails-sdk] Blocking send because quote fingerprint is stale",
|
|
1388
|
-
{
|
|
1389
|
-
prepared: preparedQuoteFingerprintRef.current,
|
|
1390
|
-
current: currentQuoteInputsFingerprint,
|
|
1391
|
-
},
|
|
1392
|
-
)
|
|
1393
|
-
setPrepareSendResult(null)
|
|
1394
|
-
preparedQuoteFingerprintRef.current = null
|
|
1395
|
-
latestResolvedIntentIdRef.current = null
|
|
1396
|
-
setIsLoadingQuote(true)
|
|
1397
|
-
return
|
|
1398
|
-
}
|
|
1399
|
-
|
|
1400
|
-
const quoteIntentId = prepareSendResult.quote.intentId ?? null
|
|
1401
|
-
if (
|
|
1402
|
-
quoteIntentId &&
|
|
1403
|
-
latestResolvedIntentIdRef.current !== quoteIntentId
|
|
1404
|
-
) {
|
|
1405
|
-
logger.console.error(
|
|
1406
|
-
"[trails-sdk] Quote intentId has changed, waiting for latest quote",
|
|
1407
|
-
{
|
|
1408
|
-
current: latestResolvedIntentIdRef.current,
|
|
1409
|
-
prepareSend: quoteIntentId,
|
|
1410
|
-
},
|
|
1411
|
-
)
|
|
1412
|
-
setError("Quote is updating. Please wait for the latest quote.")
|
|
1413
|
-
return
|
|
1414
|
-
}
|
|
787
|
+
// React Query handles quote staleness via its caching mechanism,
|
|
788
|
+
// so we no longer need fingerprint checks
|
|
1415
789
|
|
|
1416
790
|
setError(null)
|
|
1417
791
|
setIsSubmitting(true)
|
|
1418
792
|
|
|
1419
|
-
const
|
|
793
|
+
const quote = prepareSendResult.quote
|
|
1420
794
|
|
|
1421
|
-
logger.console.log(
|
|
1422
|
-
"[trails-sdk] Using prepared send result quote:",
|
|
1423
|
-
quote,
|
|
1424
|
-
)
|
|
795
|
+
logger.console.log("[trails-sdk] Using quote from useQuote:", quote)
|
|
1425
796
|
|
|
1426
797
|
function onOriginSend() {
|
|
1427
798
|
logger.console.log("[trails-sdk] onOriginSend called")
|
|
@@ -1435,10 +806,10 @@ export function useSendForm({
|
|
|
1435
806
|
|
|
1436
807
|
async function handleSend() {
|
|
1437
808
|
logger.console.log(
|
|
1438
|
-
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] handleSend called, about to call
|
|
809
|
+
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] handleSend called, about to call sendFn()",
|
|
1439
810
|
)
|
|
1440
811
|
logger.console.log(
|
|
1441
|
-
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] selectedFeeOption value at
|
|
812
|
+
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] selectedFeeOption value at swap() call time:",
|
|
1442
813
|
{
|
|
1443
814
|
selectedFeeOption,
|
|
1444
815
|
isNull: selectedFeeOption === null,
|
|
@@ -1447,24 +818,20 @@ export function useSendForm({
|
|
|
1447
818
|
stringified: JsonEncode(selectedFeeOption),
|
|
1448
819
|
},
|
|
1449
820
|
)
|
|
1450
|
-
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
})
|
|
1459
|
-
logger.console.log("[trails-sdk] send() completed, receipts:", {
|
|
1460
|
-
depositUserTxnReceipt,
|
|
1461
|
-
originIntentTransaction,
|
|
1462
|
-
destinationIntentTransaction,
|
|
821
|
+
|
|
822
|
+
// Execute the swap via useQuote's send function
|
|
823
|
+
// sendFn is guaranteed to be non-null here due to the check at the top of processSend
|
|
824
|
+
// Pass the current selectedFeeOption so gasless flow uses the correct fee token
|
|
825
|
+
// Pass onOriginSend as callback to be called when wallet signature is confirmed
|
|
826
|
+
const sendResult = await sendFn!({
|
|
827
|
+
selectedFeeOption: selectedFeeOption ?? null,
|
|
828
|
+
onOriginSend, // Called when wallet signature is confirmed, before tx completes
|
|
1463
829
|
})
|
|
830
|
+
logger.console.log("[trails-sdk] sendFn() completed:", sendResult)
|
|
1464
831
|
|
|
1465
832
|
// Move to receipt screen
|
|
1466
833
|
onComplete({
|
|
1467
|
-
transactionStates: quote.transactionStates,
|
|
834
|
+
transactionStates: quote.transactionStates ?? [],
|
|
1468
835
|
})
|
|
1469
836
|
}
|
|
1470
837
|
|
|
@@ -1484,7 +851,7 @@ export function useSendForm({
|
|
|
1484
851
|
|
|
1485
852
|
const toTokenSymbol = quote?.originToken?.symbol // Onramp will deposit origin token
|
|
1486
853
|
const toTokenAmount = normalizeNumber(
|
|
1487
|
-
quote.originAmountFormatted,
|
|
854
|
+
quote.originAmountFormatted ?? "0",
|
|
1488
855
|
).toString() // Onramp will deposit origin token amount
|
|
1489
856
|
const toChainId = quote?.originChain?.id // Onramp will deposit to origin chain
|
|
1490
857
|
const toRecipientAddress = quote.originDepositAddress // Onramp will deposit to origin address
|
|
@@ -1577,6 +944,7 @@ export function useSendForm({
|
|
|
1577
944
|
setIsWaitingForWalletConfirm(false)
|
|
1578
945
|
}, [
|
|
1579
946
|
prepareSendResult,
|
|
947
|
+
sendFn,
|
|
1580
948
|
amount,
|
|
1581
949
|
onSend,
|
|
1582
950
|
onConfirm,
|
|
@@ -1588,7 +956,6 @@ export function useSendForm({
|
|
|
1588
956
|
fundMethod,
|
|
1589
957
|
onNavigateToOnramp,
|
|
1590
958
|
selectedFeeOption, // Include so handleSend captures latest value
|
|
1591
|
-
buildQuoteInputsFingerprint,
|
|
1592
959
|
])
|
|
1593
960
|
|
|
1594
961
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
@@ -1709,14 +1076,6 @@ export function useSendForm({
|
|
|
1709
1076
|
fundMethod,
|
|
1710
1077
|
])
|
|
1711
1078
|
|
|
1712
|
-
useEffect(() => {
|
|
1713
|
-
if (quoteError) {
|
|
1714
|
-
setQuoteErrorPrettified(getPrettifiedErrorMessage(quoteError))
|
|
1715
|
-
} else {
|
|
1716
|
-
setQuoteErrorPrettified(null)
|
|
1717
|
-
}
|
|
1718
|
-
}, [quoteError])
|
|
1719
|
-
|
|
1720
1079
|
return {
|
|
1721
1080
|
amount,
|
|
1722
1081
|
amountRaw,
|
|
@@ -1762,8 +1121,8 @@ export function useSendForm({
|
|
|
1762
1121
|
prepareSendQuote: prepareSendResult?.quote ?? null,
|
|
1763
1122
|
quoteError,
|
|
1764
1123
|
quoteErrorPrettified,
|
|
1765
|
-
isRecipientContract,
|
|
1766
|
-
isSenderContractOnOrigin,
|
|
1767
|
-
isSenderContractOnDestination,
|
|
1124
|
+
isRecipientContract: quoteIsRecipientContract ?? false,
|
|
1125
|
+
isSenderContractOnOrigin: quoteIsSenderContractOnOrigin ?? false,
|
|
1126
|
+
isSenderContractOnDestination: quoteIsSenderContractOnDestination ?? false,
|
|
1768
1127
|
}
|
|
1769
1128
|
}
|