0xtrails 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aave.d.ts +2 -0
- package/dist/aave.d.ts.map +1 -1
- package/dist/abortController.d.ts +8 -0
- package/dist/abortController.d.ts.map +1 -0
- package/dist/{ccip-CXlshvBY.js → ccip-BMB3uDZt.js} +1 -1
- package/dist/config.d.ts +0 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +4 -4
- package/dist/constants.d.ts.map +1 -1
- package/dist/error.d.ts +4 -1
- package/dist/error.d.ts.map +1 -1
- package/dist/fees.d.ts +19 -0
- package/dist/fees.d.ts.map +1 -0
- package/dist/{index-_QuyGrjU.js → index-QXPUrZVv.js} +48719 -50852
- package/dist/index.d.ts +9 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +811 -784
- package/dist/intentReceiptMonitor.d.ts +24 -0
- package/dist/intentReceiptMonitor.d.ts.map +1 -0
- package/dist/intentReceiptPoller.d.ts +69 -0
- package/dist/intentReceiptPoller.d.ts.map +1 -0
- package/dist/intents.d.ts +15 -11
- package/dist/intents.d.ts.map +1 -1
- package/dist/morpho.d.ts +6 -5
- package/dist/morpho.d.ts.map +1 -1
- package/dist/mutations.d.ts +16 -0
- package/dist/mutations.d.ts.map +1 -0
- package/dist/preconditions.d.ts +5 -4
- package/dist/preconditions.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +7 -258
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +9 -6
- package/dist/prices.d.ts.map +1 -1
- package/dist/sequenceWallet.d.ts +3 -16
- package/dist/sequenceWallet.d.ts.map +1 -1
- package/dist/tokenBalances.d.ts +17 -13
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/trails.d.ts +24 -40
- package/dist/trails.d.ts.map +1 -1
- package/dist/transactionIntent/constants.d.ts +7 -0
- package/dist/transactionIntent/constants.d.ts.map +1 -0
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +44 -0
- package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -0
- package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +30 -0
- package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -0
- package/dist/transactionIntent/deposits/index.d.ts +4 -0
- package/dist/transactionIntent/deposits/index.d.ts.map +1 -0
- package/dist/transactionIntent/deposits/standardDeposit.d.ts +30 -0
- package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -0
- package/dist/transactionIntent/execution/index.d.ts +2 -0
- package/dist/transactionIntent/execution/index.d.ts.map +1 -0
- package/dist/transactionIntent/execution/transactionState.d.ts +5 -0
- package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -0
- package/dist/transactionIntent/handlers/crossChain.d.ts +82 -0
- package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -0
- package/dist/transactionIntent/handlers/index.d.ts +4 -0
- package/dist/transactionIntent/handlers/index.d.ts.map +1 -0
- package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts +62 -0
- package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts.map +1 -0
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +72 -0
- package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -0
- package/dist/transactionIntent/index.d.ts +9 -0
- package/dist/transactionIntent/index.d.ts.map +1 -0
- package/dist/transactionIntent/quote/feeExtractors.d.ts +17 -0
- package/dist/transactionIntent/quote/feeExtractors.d.ts.map +1 -0
- package/dist/transactionIntent/quote/index.d.ts +4 -0
- package/dist/transactionIntent/quote/index.d.ts.map +1 -0
- package/dist/transactionIntent/quote/normalizeQuote.d.ts +34 -0
- package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -0
- package/dist/transactionIntent/quote/quoteHelpers.d.ts +5 -0
- package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -0
- package/dist/transactionIntent/types.d.ts +131 -0
- package/dist/transactionIntent/types.d.ts.map +1 -0
- package/dist/transactionIntent/utils/balanceChecker.d.ts +18 -0
- package/dist/transactionIntent/utils/balanceChecker.d.ts.map +1 -0
- package/dist/transactionIntent/utils/index.d.ts +4 -0
- package/dist/transactionIntent/utils/index.d.ts.map +1 -0
- package/dist/transactionIntent/utils/lifiHelpers.d.ts +10 -0
- package/dist/transactionIntent/utils/lifiHelpers.d.ts.map +1 -0
- package/dist/transactionIntent/utils/testnetHelpers.d.ts +3 -0
- package/dist/transactionIntent/utils/testnetHelpers.d.ts.map +1 -0
- package/dist/transactionIntent/validators.d.ts +6 -0
- package/dist/transactionIntent/validators.d.ts.map +1 -0
- package/dist/transactions.d.ts +6 -3
- package/dist/transactions.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
- package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -1
- package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +2 -3
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
- package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
- package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
- package/dist/widget/components/DynamicSizeInputField.d.ts +13 -0
- package/dist/widget/components/DynamicSizeInputField.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts +2 -3
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
- package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
- package/dist/widget/components/FeeBreakdown.d.ts +9 -0
- package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
- package/dist/widget/components/FeeOptions.d.ts +5 -13
- package/dist/widget/components/FeeOptions.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts +2 -3
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/FundSwap.d.ts +2 -3
- package/dist/widget/components/FundSwap.d.ts.map +1 -1
- package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -1
- package/dist/widget/components/Identicon.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/Modal.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts +2 -3
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PoolDeposit.d.ts +3 -3
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
- package/dist/widget/components/PoolWithdraw.d.ts +3 -20
- package/dist/widget/components/PoolWithdraw.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts +2 -0
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
- package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts +2 -3
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -1
- package/dist/widget/components/Tooltip.d.ts +9 -0
- package/dist/widget/components/Tooltip.d.ts.map +1 -0
- package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
- package/dist/widget/components/WaasFeeOptions.d.ts +1 -0
- package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -1
- package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
- package/dist/widget/components/WalletConnect.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +2 -2
- package/dist/widget/hooks/useCheckout.d.ts +17 -4
- package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts +82 -0
- package/dist/widget/hooks/useQuote.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts +1 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +5 -6
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/hooks/useWalletConnectionContext.d.ts +25 -0
- package/dist/widget/hooks/useWalletConnectionContext.d.ts.map +1 -0
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +17 -7
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +19 -21
- package/src/aave.ts +54 -1
- package/src/abortController.ts +35 -0
- package/src/config.ts +57 -58
- package/src/constants.ts +11 -9
- package/src/error.ts +21 -3
- package/src/fees.ts +210 -0
- package/src/index.ts +35 -13
- package/src/intentReceiptMonitor.ts +102 -0
- package/src/intentReceiptPoller.ts +299 -0
- package/src/intents.ts +205 -171
- package/src/morpho.ts +58 -9
- package/src/mutations.ts +129 -0
- package/src/preconditions.ts +16 -21
- package/src/prepareSend.ts +92 -4699
- package/src/prices.ts +26 -22
- package/src/relaySdk.ts +2 -2
- package/src/sequenceWallet.ts +6 -73
- package/src/tokenBalances.ts +175 -69
- package/src/trails.ts +230 -722
- package/src/transactionIntent/constants.ts +11 -0
- package/src/transactionIntent/deposits/depositOrchestrator.ts +210 -0
- package/src/transactionIntent/deposits/gaslessDeposit.ts +588 -0
- package/src/transactionIntent/deposits/index.ts +3 -0
- package/src/transactionIntent/deposits/standardDeposit.ts +379 -0
- package/src/transactionIntent/execution/index.ts +1 -0
- package/src/transactionIntent/execution/transactionState.ts +35 -0
- package/src/transactionIntent/handlers/crossChain.ts +1707 -0
- package/src/transactionIntent/handlers/index.ts +3 -0
- package/src/transactionIntent/handlers/sameChainDifferentToken.ts +323 -0
- package/src/transactionIntent/handlers/sameChainSameToken.ts +712 -0
- package/src/transactionIntent/index.ts +9 -0
- package/src/transactionIntent/quote/feeExtractors.ts +81 -0
- package/src/transactionIntent/quote/index.ts +3 -0
- package/src/transactionIntent/quote/normalizeQuote.ts +367 -0
- package/src/transactionIntent/quote/quoteHelpers.ts +53 -0
- package/src/transactionIntent/types.ts +157 -0
- package/src/transactionIntent/utils/balanceChecker.ts +96 -0
- package/src/transactionIntent/utils/index.ts +3 -0
- package/src/transactionIntent/utils/lifiHelpers.ts +68 -0
- package/src/transactionIntent/utils/testnetHelpers.ts +10 -0
- package/src/transactionIntent/validators.ts +57 -0
- package/src/transactions.ts +98 -71
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountIntentTransactionHistory.tsx +36 -36
- package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
- package/src/widget/components/AccountSettings.tsx +70 -41
- package/src/widget/components/ChainFilterDropdown.tsx +24 -3
- package/src/widget/components/ClassicSwap.tsx +44 -107
- package/src/widget/components/ConfigDisplay.tsx +0 -11
- package/src/widget/components/ConnectWallet.tsx +4 -1
- package/src/widget/components/ConnectedWallets.tsx +51 -25
- package/src/widget/components/DynamicInputStyles.tsx +76 -0
- package/src/widget/components/DynamicSizeInputField.tsx +109 -0
- package/src/widget/components/Earn.tsx +34 -45
- package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
- package/src/widget/components/FeeBreakdown.tsx +155 -0
- package/src/widget/components/FeeOption.tsx +2 -2
- package/src/widget/components/FeeOptions.tsx +151 -112
- package/src/widget/components/Fund.tsx +10 -29
- package/src/widget/components/FundMethods.tsx +4 -3
- package/src/widget/components/FundSwap.tsx +2 -3
- package/src/widget/components/FundingMethodSelectorButton.tsx +24 -14
- package/src/widget/components/Identicon.tsx +164 -95
- package/src/widget/components/MeshConnectExchanges.tsx +2 -15
- package/src/widget/components/Modal.tsx +0 -12
- package/src/widget/components/Pay.tsx +72 -75
- package/src/widget/components/PoolDeposit.tsx +221 -242
- package/src/widget/components/PoolWithdraw.tsx +347 -469
- package/src/widget/components/PriceImpactWarning.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +906 -484
- package/src/widget/components/Receipt.tsx +16 -2
- package/src/widget/components/RecipientSelectorButton.tsx +7 -5
- package/src/widget/components/Recipients.tsx +1 -1
- package/src/widget/components/ScreenHeader.tsx +60 -36
- package/src/widget/components/Swap.tsx +2 -3
- package/src/widget/components/ThemeProvider.tsx +2 -1
- package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
- package/src/widget/components/TokenImage.tsx +1 -1
- package/src/widget/components/TokenSelector.tsx +62 -53
- package/src/widget/components/TokenSelectorButton.tsx +38 -15
- package/src/widget/components/Tooltip.tsx +51 -0
- package/src/widget/components/TransferPendingVertical.tsx +12 -8
- package/src/widget/components/WaasFeeOptions.tsx +139 -4
- package/src/widget/components/WalletConfirmation.tsx +23 -13
- package/src/widget/components/WalletConnect.tsx +93 -29
- package/src/widget/hooks/useAmountUsd.ts +9 -9
- package/src/widget/hooks/useCheckout.ts +97 -9
- package/src/widget/hooks/useDefaultTokenSelection.tsx +27 -21
- package/src/widget/hooks/useQuote.ts +466 -0
- package/src/widget/hooks/useSelectedFeeToken.tsx +32 -37
- package/src/widget/hooks/useSendForm.ts +45 -51
- package/src/widget/hooks/useTokenList.ts +34 -26
- package/src/widget/hooks/useWalletConnectionContext.tsx +128 -0
- package/src/widget/widget.tsx +365 -390
- package/dist/apiClient.d.ts +0 -9
- package/dist/apiClient.d.ts.map +0 -1
- package/dist/intentEntrypoint.d.ts +0 -114
- package/dist/intentEntrypoint.d.ts.map +0 -1
- package/dist/metaTxnMonitor.d.ts +0 -15
- package/dist/metaTxnMonitor.d.ts.map +0 -1
- package/dist/metaTxns.d.ts +0 -11
- package/dist/metaTxns.d.ts.map +0 -1
- package/dist/relayer.d.ts +0 -43
- package/dist/relayer.d.ts.map +0 -1
- package/src/apiClient.ts +0 -35
- package/src/intentEntrypoint.ts +0 -203
- package/src/metaTxnMonitor.ts +0 -171
- package/src/metaTxns.ts +0 -45
- package/src/relayer.ts +0 -289
|
@@ -0,0 +1,1707 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
Account,
|
|
3
|
+
Chain,
|
|
4
|
+
PublicClient,
|
|
5
|
+
WalletClient,
|
|
6
|
+
TransactionReceipt,
|
|
7
|
+
} from "viem"
|
|
8
|
+
import { createPublicClient, createWalletClient, http } from "viem"
|
|
9
|
+
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"
|
|
10
|
+
import type { TrailsAPIClient } from "@0xsequence/trails-api"
|
|
11
|
+
import type { Attestation } from "../../cctp.js"
|
|
12
|
+
import type { MetaTxnReceipt, PrepareSendReturn, SendReturn } from "../types.js"
|
|
13
|
+
import type { TransactionState } from "../../transactions.js"
|
|
14
|
+
import type { CheckoutOnHandlers } from "../../widget/hooks/useCheckout.js"
|
|
15
|
+
import type {
|
|
16
|
+
Intent,
|
|
17
|
+
CommitIntentResponse,
|
|
18
|
+
DepositSignature,
|
|
19
|
+
ExecuteIntentResponse,
|
|
20
|
+
} from "@0xsequence/trails-api"
|
|
21
|
+
import { TradeType, type SelectedFeeToken } from "../types.js"
|
|
22
|
+
import { logger } from "../../logger.js"
|
|
23
|
+
import {
|
|
24
|
+
isTestnetDebugMode,
|
|
25
|
+
getTestnetOriginTokenAddress,
|
|
26
|
+
} from "../utils/testnetHelpers.js"
|
|
27
|
+
import { shouldUseCctp, validateCctpDestinationToken } from "../validators.js"
|
|
28
|
+
import { getQueryParam } from "../../queryParams.js"
|
|
29
|
+
import { getIsCustomCalldata } from "../../contractUtils.js"
|
|
30
|
+
import { getNormalizedQuoteObject } from "../quote/normalizeQuote.js"
|
|
31
|
+
import {
|
|
32
|
+
cctpTransfer,
|
|
33
|
+
cctpTransferWithCustomCall,
|
|
34
|
+
getCCTPRelayerCallData,
|
|
35
|
+
getMintUSDCData,
|
|
36
|
+
getMessageTransmitter,
|
|
37
|
+
} from "../../cctp.js"
|
|
38
|
+
import { getChainInfo, getTestnetChainInfo } from "../../chains.js"
|
|
39
|
+
import { getTransactionStateFromReceipt } from "../execution/transactionState.js"
|
|
40
|
+
import {
|
|
41
|
+
simpleCreateSequenceWallet,
|
|
42
|
+
sequenceSendTransaction,
|
|
43
|
+
} from "../../sequenceWallet.js"
|
|
44
|
+
import { getIntentArgs } from "../quote/quoteHelpers.js"
|
|
45
|
+
import {
|
|
46
|
+
getIntent,
|
|
47
|
+
commitIntent,
|
|
48
|
+
buildCrossChainDepositParams,
|
|
49
|
+
} from "../../intents.js"
|
|
50
|
+
import { findFirstPreconditionForChainId } from "../../preconditions.js"
|
|
51
|
+
import {
|
|
52
|
+
decodeTrailsTokenSweeperEvents,
|
|
53
|
+
decodeGuestModuleEvents,
|
|
54
|
+
} from "../../decoders.js"
|
|
55
|
+
import { updatePersistentToast } from "../../toast.js"
|
|
56
|
+
import { queueCCTPTransfer } from "../../cctpqueue.js"
|
|
57
|
+
import { formatRawAmount } from "../../tokenBalances.js"
|
|
58
|
+
import { calcAmountUsdPrice } from "../../prices.js"
|
|
59
|
+
import { checkAccountBalance } from "../utils/balanceChecker.js"
|
|
60
|
+
import { estimateGasLimit } from "../../estimate.js"
|
|
61
|
+
import {
|
|
62
|
+
getFeesFromIntent,
|
|
63
|
+
getPriceImpactFromIntent,
|
|
64
|
+
getPriceImpactUsdFromIntent,
|
|
65
|
+
} from "../quote/feeExtractors.js"
|
|
66
|
+
import { getNeedsLifiNativeFee } from "../utils/lifiHelpers.js"
|
|
67
|
+
import { attemptUserDepositTx } from "../deposits/depositOrchestrator.js"
|
|
68
|
+
import {
|
|
69
|
+
getAccountTransactionHistory,
|
|
70
|
+
getTxTimeDiff,
|
|
71
|
+
} from "../../transactions.js"
|
|
72
|
+
import { trackPaymentCompleted, trackPaymentError } from "../../analytics.js"
|
|
73
|
+
import { POLLING_INTERVALS } from "../constants.js"
|
|
74
|
+
import { pollIntentReceipt } from "../../intentReceiptPoller.js"
|
|
75
|
+
import type { IntentReceipt } from "@0xsequence/trails-api"
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* @description
|
|
79
|
+
* This handler manages cross-chain token transfers (e.g., transferring USDC to USDC on a different chain).
|
|
80
|
+
* It supports both standard transfers and custom calldata execution.
|
|
81
|
+
*
|
|
82
|
+
* Key characteristics:
|
|
83
|
+
* - Uses the Trails API with commitIntent and waitIntentReceipt for monitoring
|
|
84
|
+
* - Two transaction legs (deposit and user transfer)
|
|
85
|
+
* - Cross-chain bridging or token swapping required
|
|
86
|
+
* - More complex flow compared to same-chain scenarios
|
|
87
|
+
*
|
|
88
|
+
* Transaction Flow:
|
|
89
|
+
* 1. Create intent via createIntent (Intent object created)
|
|
90
|
+
* 2. Commit intent via commitIntent (Intent record created in backend)
|
|
91
|
+
* 3. User submits deposit transaction on origin chain
|
|
92
|
+
* 4. Poll via waitIntentReceipt API to monitor deposit transaction completion
|
|
93
|
+
* 5. User submits transfer transaction on destination chain
|
|
94
|
+
* 6. Poll via waitIntentReceipt API to monitor transfer transaction completion
|
|
95
|
+
* 7. Update transaction state and invoke callbacks
|
|
96
|
+
*
|
|
97
|
+
* Error Handling:
|
|
98
|
+
* - Balance validation before transaction submission
|
|
99
|
+
* - Gas estimation for accurate fee calculation
|
|
100
|
+
* - Proper error propagation with checkout error callbacks
|
|
101
|
+
* - Fallback logging for debugging
|
|
102
|
+
*
|
|
103
|
+
* State Management:
|
|
104
|
+
* - transactionStates[0]: Deposit transaction
|
|
105
|
+
* - transactionStates[1]: User transfer transaction
|
|
106
|
+
* - transactionStates[2]: Destination transfer transaction
|
|
107
|
+
* - Updates propagated via onTransactionStateChange callback
|
|
108
|
+
* - Analytics tracking for payment completion or errors
|
|
109
|
+
*/
|
|
110
|
+
export async function handleCrossChain({
|
|
111
|
+
mainSignerAddress,
|
|
112
|
+
originChainId,
|
|
113
|
+
originTokenAddress,
|
|
114
|
+
originTokenBalance,
|
|
115
|
+
destinationChainId,
|
|
116
|
+
destinationTokenAddress,
|
|
117
|
+
swapAmount,
|
|
118
|
+
originTokenSymbol,
|
|
119
|
+
destinationTokenSymbol,
|
|
120
|
+
recipient,
|
|
121
|
+
destinationCalldata,
|
|
122
|
+
trailsClient,
|
|
123
|
+
sourceTokenPriceUsd,
|
|
124
|
+
destinationTokenPriceUsd,
|
|
125
|
+
sourceTokenDecimals,
|
|
126
|
+
destinationTokenDecimals,
|
|
127
|
+
paymasterUrl,
|
|
128
|
+
walletClient,
|
|
129
|
+
publicClient,
|
|
130
|
+
chain,
|
|
131
|
+
account,
|
|
132
|
+
fee,
|
|
133
|
+
dryMode,
|
|
134
|
+
onTransactionStateChange,
|
|
135
|
+
transactionStates,
|
|
136
|
+
slippageTolerance,
|
|
137
|
+
tradeType,
|
|
138
|
+
originNativeTokenPriceUsd,
|
|
139
|
+
quoteProvider,
|
|
140
|
+
fundMethod,
|
|
141
|
+
mode,
|
|
142
|
+
checkoutOnHandlers,
|
|
143
|
+
selectedFeeToken,
|
|
144
|
+
walletId,
|
|
145
|
+
abortSignal,
|
|
146
|
+
commitIntentFn,
|
|
147
|
+
executeIntentFn,
|
|
148
|
+
}: {
|
|
149
|
+
mainSignerAddress: string
|
|
150
|
+
originChainId: number
|
|
151
|
+
originTokenAddress: string
|
|
152
|
+
originTokenBalance: string
|
|
153
|
+
destinationChainId: number
|
|
154
|
+
destinationTokenAddress: string
|
|
155
|
+
swapAmount: string
|
|
156
|
+
originTokenSymbol: string
|
|
157
|
+
destinationTokenSymbol: string
|
|
158
|
+
recipient: string
|
|
159
|
+
destinationCalldata?: string
|
|
160
|
+
trailsClient: TrailsAPIClient
|
|
161
|
+
sourceTokenPriceUsd?: number | null
|
|
162
|
+
destinationTokenPriceUsd?: number | null
|
|
163
|
+
sourceTokenDecimals: number
|
|
164
|
+
destinationTokenDecimals: number
|
|
165
|
+
paymasterUrl?: string
|
|
166
|
+
walletClient: WalletClient
|
|
167
|
+
publicClient: PublicClient
|
|
168
|
+
chain: Chain
|
|
169
|
+
account: Account
|
|
170
|
+
fee: string
|
|
171
|
+
dryMode: boolean
|
|
172
|
+
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
173
|
+
transactionStates: TransactionState[]
|
|
174
|
+
slippageTolerance: string
|
|
175
|
+
tradeType: TradeType
|
|
176
|
+
originNativeTokenPriceUsd?: number | null
|
|
177
|
+
quoteProvider?: string | null
|
|
178
|
+
fundMethod?: string
|
|
179
|
+
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
180
|
+
checkoutOnHandlers?: CheckoutOnHandlers
|
|
181
|
+
selectedFeeToken?: SelectedFeeToken
|
|
182
|
+
walletId?: string
|
|
183
|
+
abortSignal?: AbortSignal
|
|
184
|
+
commitIntentFn?: (intent: Intent) => Promise<CommitIntentResponse>
|
|
185
|
+
executeIntentFn?: (params: {
|
|
186
|
+
intentId: string
|
|
187
|
+
depositTransactionHash?: string
|
|
188
|
+
depositSignature?: DepositSignature
|
|
189
|
+
}) => Promise<ExecuteIntentResponse>
|
|
190
|
+
}): Promise<PrepareSendReturn> {
|
|
191
|
+
const testnet = isTestnetDebugMode()
|
|
192
|
+
const useCctp = shouldUseCctp(
|
|
193
|
+
originTokenAddress,
|
|
194
|
+
destinationTokenAddress,
|
|
195
|
+
originChainId,
|
|
196
|
+
destinationChainId,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
const cctpFlag = getQueryParam("cctp") === "true"
|
|
200
|
+
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
201
|
+
|
|
202
|
+
// Validate CCTP destination token requirement for explicit CCTP usage
|
|
203
|
+
if (useCctp && cctpFlag) {
|
|
204
|
+
validateCctpDestinationToken(
|
|
205
|
+
destinationTokenAddress,
|
|
206
|
+
destinationChainId,
|
|
207
|
+
"cctp",
|
|
208
|
+
)
|
|
209
|
+
logger.console.log("[trails-sdk] using cctp")
|
|
210
|
+
|
|
211
|
+
const quote = await getNormalizedQuoteObject({
|
|
212
|
+
destinationAddress: recipient,
|
|
213
|
+
destinationCalldata,
|
|
214
|
+
originAmount: swapAmount,
|
|
215
|
+
originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
|
|
216
|
+
destinationAmount: swapAmount,
|
|
217
|
+
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
|
|
218
|
+
originTokenAddress: originTokenAddress,
|
|
219
|
+
destinationTokenAddress: destinationTokenAddress,
|
|
220
|
+
originChainId,
|
|
221
|
+
destinationChainId,
|
|
222
|
+
transactionStates,
|
|
223
|
+
originNativeTokenPriceUsd,
|
|
224
|
+
slippageTolerance,
|
|
225
|
+
quoteProvider: "cctp",
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
// Call onCheckoutQuote callback if provided
|
|
229
|
+
if (checkoutOnHandlers?.triggerCheckoutQuote) {
|
|
230
|
+
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
quote,
|
|
235
|
+
send: async ({
|
|
236
|
+
onOriginSend,
|
|
237
|
+
selectedFeeToken: runtimeSelectedFeeToken,
|
|
238
|
+
}: {
|
|
239
|
+
onOriginSend?: () => void
|
|
240
|
+
selectedFeeToken?: SelectedFeeToken
|
|
241
|
+
}): Promise<SendReturn> => {
|
|
242
|
+
// Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
|
|
243
|
+
const effectiveSelectedFeeToken =
|
|
244
|
+
runtimeSelectedFeeToken ?? selectedFeeToken
|
|
245
|
+
logger.console.log(
|
|
246
|
+
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called (LEGACY PATH):",
|
|
247
|
+
{
|
|
248
|
+
runtimeSelectedFeeToken,
|
|
249
|
+
prepareTimeSelectedFeeToken: selectedFeeToken,
|
|
250
|
+
effectiveSelectedFeeToken,
|
|
251
|
+
},
|
|
252
|
+
)
|
|
253
|
+
const originChain = testnet ? getTestnetChainInfo(chain)! : chain
|
|
254
|
+
const destinationChain = testnet
|
|
255
|
+
? getTestnetChainInfo(destinationChainId)!
|
|
256
|
+
: getChainInfo(destinationChainId)
|
|
257
|
+
|
|
258
|
+
if (!originChain || !destinationChain) {
|
|
259
|
+
logger.console.error("[trails-sdk] Invalid chain", {
|
|
260
|
+
originChain,
|
|
261
|
+
destinationChain,
|
|
262
|
+
originChainId,
|
|
263
|
+
destinationChainId,
|
|
264
|
+
chain,
|
|
265
|
+
testnet,
|
|
266
|
+
})
|
|
267
|
+
throw new Error("Invalid chain")
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
logger.console.log("[trails-sdk] originChain", originChain)
|
|
271
|
+
logger.console.log("[trails-sdk] destinationChain", destinationChain)
|
|
272
|
+
|
|
273
|
+
const originPublicClient = createPublicClient({
|
|
274
|
+
chain: originChain,
|
|
275
|
+
transport: http(),
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
let txHash: `0x${string}`
|
|
279
|
+
let waitForAttestation: () => Promise<Attestation>
|
|
280
|
+
|
|
281
|
+
if (hasCustomCalldata) {
|
|
282
|
+
const result = await cctpTransferWithCustomCall({
|
|
283
|
+
walletClient,
|
|
284
|
+
originChain,
|
|
285
|
+
destinationChain,
|
|
286
|
+
amount: BigInt(swapAmount),
|
|
287
|
+
})
|
|
288
|
+
|
|
289
|
+
txHash = result.txHash
|
|
290
|
+
waitForAttestation = result.waitForAttestation
|
|
291
|
+
} else {
|
|
292
|
+
const result = await cctpTransfer({
|
|
293
|
+
walletClient,
|
|
294
|
+
originChain,
|
|
295
|
+
destinationChain,
|
|
296
|
+
amount: BigInt(swapAmount),
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
txHash = result.txHash
|
|
300
|
+
waitForAttestation = result.waitForAttestation
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
if (onOriginSend) {
|
|
304
|
+
onOriginSend()
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
logger.console.log("[trails-sdk] waiting for tx", txHash)
|
|
308
|
+
|
|
309
|
+
const receipt = await originPublicClient.waitForTransactionReceipt({
|
|
310
|
+
hash: txHash,
|
|
311
|
+
})
|
|
312
|
+
|
|
313
|
+
logger.console.log("[trails-sdk] tx receipt", receipt)
|
|
314
|
+
|
|
315
|
+
transactionStates[0] = getTransactionStateFromReceipt(
|
|
316
|
+
receipt,
|
|
317
|
+
originChain.id,
|
|
318
|
+
transactionStates[0]?.label,
|
|
319
|
+
)
|
|
320
|
+
transactionStates[1] = getTransactionStateFromReceipt(
|
|
321
|
+
receipt,
|
|
322
|
+
originChain.id,
|
|
323
|
+
transactionStates[1]?.label,
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
onTransactionStateChange(transactionStates)
|
|
327
|
+
|
|
328
|
+
const attestation = await waitForAttestation()
|
|
329
|
+
|
|
330
|
+
if (!attestation) {
|
|
331
|
+
throw new Error("Failed to retrieve attestation")
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const tokenMessenger = getMessageTransmitter(destinationChain.id)!
|
|
335
|
+
|
|
336
|
+
logger.console.log("[trails-sdk] tokenMessenger", tokenMessenger)
|
|
337
|
+
const calls: {
|
|
338
|
+
to: `0x${string}`
|
|
339
|
+
data: `0x${string}`
|
|
340
|
+
value: bigint
|
|
341
|
+
}[] = []
|
|
342
|
+
|
|
343
|
+
if (hasCustomCalldata) {
|
|
344
|
+
calls.push(
|
|
345
|
+
await getCCTPRelayerCallData({
|
|
346
|
+
attestation,
|
|
347
|
+
targetContract: recipient,
|
|
348
|
+
calldata: destinationCalldata as `0x${string}`,
|
|
349
|
+
gasLimit: 300000n,
|
|
350
|
+
destinationChain,
|
|
351
|
+
}),
|
|
352
|
+
)
|
|
353
|
+
} else {
|
|
354
|
+
calls.push(
|
|
355
|
+
await getMintUSDCData({
|
|
356
|
+
tokenMessenger,
|
|
357
|
+
attestation,
|
|
358
|
+
}),
|
|
359
|
+
)
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
logger.console.log("[trails-sdk] calls", calls)
|
|
363
|
+
const delegatorPrivateKey = generatePrivateKey()
|
|
364
|
+
const delegatorAccount = privateKeyToAccount(delegatorPrivateKey)
|
|
365
|
+
const delegatorClient = createWalletClient({
|
|
366
|
+
account: delegatorAccount,
|
|
367
|
+
chain: destinationChain,
|
|
368
|
+
transport: http(),
|
|
369
|
+
})
|
|
370
|
+
const destinationPublicClient = createPublicClient({
|
|
371
|
+
chain: destinationChain,
|
|
372
|
+
transport: http(),
|
|
373
|
+
})
|
|
374
|
+
logger.console.log("[trails-sdk] delegatorClient", delegatorClient)
|
|
375
|
+
|
|
376
|
+
const sequenceWalletAddress = await simpleCreateSequenceWallet(
|
|
377
|
+
delegatorAccount as any,
|
|
378
|
+
)
|
|
379
|
+
logger.console.log(
|
|
380
|
+
"[trails-sdk] sequenceWalletAddress",
|
|
381
|
+
sequenceWalletAddress,
|
|
382
|
+
)
|
|
383
|
+
const sequenceTxHash = await sequenceSendTransaction(
|
|
384
|
+
sequenceWalletAddress,
|
|
385
|
+
delegatorClient,
|
|
386
|
+
destinationPublicClient,
|
|
387
|
+
calls,
|
|
388
|
+
destinationChain,
|
|
389
|
+
)
|
|
390
|
+
|
|
391
|
+
const destinationReceipt =
|
|
392
|
+
await destinationPublicClient.waitForTransactionReceipt({
|
|
393
|
+
hash: sequenceTxHash as `0x${string}`,
|
|
394
|
+
})
|
|
395
|
+
|
|
396
|
+
logger.console.log(
|
|
397
|
+
"[trails-sdk] destinationReceipt",
|
|
398
|
+
destinationReceipt,
|
|
399
|
+
)
|
|
400
|
+
|
|
401
|
+
if (transactionStates[2]) {
|
|
402
|
+
transactionStates[2] = getTransactionStateFromReceipt(
|
|
403
|
+
destinationReceipt,
|
|
404
|
+
destinationChain.id,
|
|
405
|
+
transactionStates[2]?.label,
|
|
406
|
+
)
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
onTransactionStateChange(transactionStates)
|
|
410
|
+
|
|
411
|
+
return {
|
|
412
|
+
depositUserTxnReceipt: receipt,
|
|
413
|
+
originMetaTxnReceipt: null,
|
|
414
|
+
destinationMetaTxnReceipt: null,
|
|
415
|
+
totalCompletionSeconds: 0,
|
|
416
|
+
}
|
|
417
|
+
},
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const destinationSalt = Date.now().toString()
|
|
422
|
+
|
|
423
|
+
const intentArgs = getIntentArgs(
|
|
424
|
+
mainSignerAddress,
|
|
425
|
+
originChainId,
|
|
426
|
+
originTokenAddress,
|
|
427
|
+
tradeType === TradeType.EXACT_OUTPUT ? originTokenBalance : swapAmount, // originTokenAmount
|
|
428
|
+
destinationChainId,
|
|
429
|
+
destinationTokenAddress,
|
|
430
|
+
tradeType === TradeType.EXACT_OUTPUT ? swapAmount : "0", // destinationTokenAmount
|
|
431
|
+
recipient,
|
|
432
|
+
destinationCalldata,
|
|
433
|
+
destinationSalt,
|
|
434
|
+
slippageTolerance,
|
|
435
|
+
tradeType,
|
|
436
|
+
quoteProvider,
|
|
437
|
+
)
|
|
438
|
+
|
|
439
|
+
logger.console.log("[trails-sdk] Creating intent with args:", intentArgs)
|
|
440
|
+
|
|
441
|
+
const { intent, gasFeeOptions } = await getIntent(trailsClient, intentArgs, {
|
|
442
|
+
originTokenSymbol,
|
|
443
|
+
destinationTokenSymbol,
|
|
444
|
+
feeTokenSymbol: originTokenSymbol,
|
|
445
|
+
})
|
|
446
|
+
logger.console.log("[trails-sdk] Got intent:", intent)
|
|
447
|
+
logger.console.log("[trails-sdk] Got gasFeeOptions:", gasFeeOptions)
|
|
448
|
+
|
|
449
|
+
if (!intent.preconditions?.length || !intent.calls?.length) {
|
|
450
|
+
throw new Error("Invalid intent")
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
const originIntentAddress = intent.originIntentAddress
|
|
454
|
+
logger.console.log(
|
|
455
|
+
"[trails-sdk] origin intent address:",
|
|
456
|
+
originIntentAddress.toString(),
|
|
457
|
+
)
|
|
458
|
+
|
|
459
|
+
const firstPrecondition = findFirstPreconditionForChainId(
|
|
460
|
+
intent.preconditions,
|
|
461
|
+
originChainId,
|
|
462
|
+
)
|
|
463
|
+
|
|
464
|
+
if (!firstPrecondition) {
|
|
465
|
+
throw new Error("No precondition found for origin chain")
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
const firstPreconditionMin = firstPrecondition?.minAmount?.toString()
|
|
469
|
+
const depositAmount = firstPreconditionMin
|
|
470
|
+
|
|
471
|
+
const quoteToAmount = intent.quote.toAmount
|
|
472
|
+
const quoteToAmountMin = intent.quote.toAmountMin
|
|
473
|
+
|
|
474
|
+
const originSendAmountFormatted = formatRawAmount(
|
|
475
|
+
depositAmount,
|
|
476
|
+
sourceTokenDecimals,
|
|
477
|
+
)
|
|
478
|
+
|
|
479
|
+
const depositAmountUsd = calcAmountUsdPrice({
|
|
480
|
+
amount: originSendAmountFormatted,
|
|
481
|
+
usdPrice: sourceTokenPriceUsd,
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
logger.console.log("[trails-sdk] depositAmountUsd", depositAmountUsd, {
|
|
485
|
+
amount: originSendAmountFormatted,
|
|
486
|
+
usdPrice: sourceTokenPriceUsd,
|
|
487
|
+
originTokenSymbol,
|
|
488
|
+
originSendAmountFormatted,
|
|
489
|
+
})
|
|
490
|
+
|
|
491
|
+
const effectiveDestinationTokenAmount = quoteToAmount
|
|
492
|
+
const effectiveDestinationTokenAmountFormatted = formatRawAmount(
|
|
493
|
+
effectiveDestinationTokenAmount,
|
|
494
|
+
destinationTokenDecimals,
|
|
495
|
+
)
|
|
496
|
+
|
|
497
|
+
const effectiveDestinationTokenAmountUsd = calcAmountUsdPrice({
|
|
498
|
+
amount: effectiveDestinationTokenAmountFormatted,
|
|
499
|
+
usdPrice: destinationTokenPriceUsd,
|
|
500
|
+
})
|
|
501
|
+
|
|
502
|
+
logger.console.log(
|
|
503
|
+
"[trails-sdk] effectiveDestinationTokenAmountUsd",
|
|
504
|
+
effectiveDestinationTokenAmountUsd,
|
|
505
|
+
{
|
|
506
|
+
amount: effectiveDestinationTokenAmountFormatted,
|
|
507
|
+
usdPrice: destinationTokenPriceUsd,
|
|
508
|
+
destinationTokenSymbol,
|
|
509
|
+
effectiveDestinationTokenAmount,
|
|
510
|
+
destinationTokenAddress,
|
|
511
|
+
destinationChainId,
|
|
512
|
+
},
|
|
513
|
+
)
|
|
514
|
+
|
|
515
|
+
let noSufficientBalance = false
|
|
516
|
+
|
|
517
|
+
if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
|
|
518
|
+
const { hasEnoughBalance } = await checkAccountBalance({
|
|
519
|
+
account,
|
|
520
|
+
tokenAddress: originTokenAddress,
|
|
521
|
+
depositAmount,
|
|
522
|
+
publicClient,
|
|
523
|
+
})
|
|
524
|
+
|
|
525
|
+
if (!hasEnoughBalance) {
|
|
526
|
+
noSufficientBalance = true
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Estimate gas limit for the quote (same logic as in attemptNonGaslessUserDeposit)
|
|
531
|
+
const originCallParamsForEstimate = buildCrossChainDepositParams({
|
|
532
|
+
originTokenAddress,
|
|
533
|
+
originIntentAddress,
|
|
534
|
+
depositAmount: firstPreconditionMin,
|
|
535
|
+
fee,
|
|
536
|
+
originChainId,
|
|
537
|
+
chain,
|
|
538
|
+
})
|
|
539
|
+
|
|
540
|
+
logger.console.log(
|
|
541
|
+
"[trails-sdk][gas-estimation] About to estimate gas limit for cross-chain quote with params:",
|
|
542
|
+
{
|
|
543
|
+
account: account.address,
|
|
544
|
+
to: originCallParamsForEstimate.to,
|
|
545
|
+
data: originCallParamsForEstimate.data,
|
|
546
|
+
value: originCallParamsForEstimate.value,
|
|
547
|
+
},
|
|
548
|
+
)
|
|
549
|
+
|
|
550
|
+
const estimatedGasLimitForQuote = await estimateGasLimit(
|
|
551
|
+
publicClient,
|
|
552
|
+
{
|
|
553
|
+
account: account.address,
|
|
554
|
+
to: originCallParamsForEstimate.to,
|
|
555
|
+
data: originCallParamsForEstimate.data,
|
|
556
|
+
value: BigInt(originCallParamsForEstimate.value),
|
|
557
|
+
},
|
|
558
|
+
"quote",
|
|
559
|
+
)
|
|
560
|
+
|
|
561
|
+
logger.console.log(
|
|
562
|
+
"[trails-sdk][gas-estimation] Estimated gas limit for cross-chain quote:",
|
|
563
|
+
estimatedGasLimitForQuote,
|
|
564
|
+
)
|
|
565
|
+
|
|
566
|
+
const quote = await getNormalizedQuoteObject({
|
|
567
|
+
originDepositAddress: originIntentAddress,
|
|
568
|
+
destinationDepositAddress: intent.destinationIntentAddress,
|
|
569
|
+
destinationAddress: recipient,
|
|
570
|
+
destinationCalldata,
|
|
571
|
+
originAmount: depositAmount,
|
|
572
|
+
destinationAmount: quoteToAmount.toString(),
|
|
573
|
+
originAmountMin: depositAmount,
|
|
574
|
+
destinationAmountMin: quoteToAmountMin.toString(),
|
|
575
|
+
originTokenAddress: originTokenAddress,
|
|
576
|
+
destinationTokenAddress: destinationTokenAddress,
|
|
577
|
+
originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
|
|
578
|
+
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
|
|
579
|
+
fees: getFeesFromIntent(intent, {
|
|
580
|
+
tradeType,
|
|
581
|
+
fromAmountUsd: depositAmountUsd,
|
|
582
|
+
toAmountUsd: effectiveDestinationTokenAmountUsd,
|
|
583
|
+
}),
|
|
584
|
+
originChainId,
|
|
585
|
+
destinationChainId,
|
|
586
|
+
slippageTolerance: slippageTolerance,
|
|
587
|
+
priceImpact: getPriceImpactFromIntent(intent),
|
|
588
|
+
priceImpactUsd: getPriceImpactUsdFromIntent(intent),
|
|
589
|
+
transactionStates,
|
|
590
|
+
originNativeTokenPriceUsd,
|
|
591
|
+
quoteProvider: intent.quote?.quoteProvider,
|
|
592
|
+
noSufficientBalance,
|
|
593
|
+
estimatedGasLimit: estimatedGasLimitForQuote,
|
|
594
|
+
intent,
|
|
595
|
+
})
|
|
596
|
+
|
|
597
|
+
// Call onCheckoutQuote callback if provided
|
|
598
|
+
if (checkoutOnHandlers?.triggerCheckoutQuote) {
|
|
599
|
+
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
// Gasless fee options are now returned in QuoteIntentResponse.gasFeeOptions
|
|
603
|
+
// and properly propagated from getIntent()
|
|
604
|
+
const effectiveGasless =
|
|
605
|
+
selectedFeeToken !== null &&
|
|
606
|
+
selectedFeeToken !== undefined &&
|
|
607
|
+
walletId !== "sequence-waas"
|
|
608
|
+
if (effectiveGasless) {
|
|
609
|
+
logger.console.log(
|
|
610
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled. Using gasFeeOptions from QuoteIntent response",
|
|
611
|
+
{ originChainId, gasFeeOptions },
|
|
612
|
+
)
|
|
613
|
+
} else if (walletId === "sequence-waas") {
|
|
614
|
+
logger.console.log(
|
|
615
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Skipping gasless fee options for sequence-waas wallet",
|
|
616
|
+
)
|
|
617
|
+
} else {
|
|
618
|
+
logger.console.log(
|
|
619
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled",
|
|
620
|
+
)
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
return {
|
|
624
|
+
quote,
|
|
625
|
+
feeOptions: gasFeeOptions,
|
|
626
|
+
send: async ({
|
|
627
|
+
onOriginSend,
|
|
628
|
+
selectedFeeToken: runtimeSelectedFeeToken,
|
|
629
|
+
}: {
|
|
630
|
+
onOriginSend?: () => void
|
|
631
|
+
selectedFeeToken?: SelectedFeeToken
|
|
632
|
+
}): Promise<SendReturn> => {
|
|
633
|
+
try {
|
|
634
|
+
// Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
|
|
635
|
+
const effectiveSelectedFeeToken =
|
|
636
|
+
runtimeSelectedFeeToken ?? selectedFeeToken
|
|
637
|
+
logger.console.log(
|
|
638
|
+
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called with:",
|
|
639
|
+
{
|
|
640
|
+
runtimeSelectedFeeToken,
|
|
641
|
+
prepareTimeSelectedFeeToken: selectedFeeToken,
|
|
642
|
+
effectiveSelectedFeeToken,
|
|
643
|
+
},
|
|
644
|
+
)
|
|
645
|
+
const commitIntentFnToUse =
|
|
646
|
+
commitIntentFn ||
|
|
647
|
+
((intent: Intent) =>
|
|
648
|
+
commitIntent(trailsClient, intent, {
|
|
649
|
+
originTokenSymbol,
|
|
650
|
+
destinationTokenSymbol,
|
|
651
|
+
}))
|
|
652
|
+
await commitIntentFnToUse(intent)
|
|
653
|
+
|
|
654
|
+
if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
|
|
655
|
+
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
656
|
+
account,
|
|
657
|
+
tokenAddress: originTokenAddress,
|
|
658
|
+
depositAmount,
|
|
659
|
+
publicClient,
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
if (!hasEnoughBalance) {
|
|
663
|
+
throw balanceError
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
logger.console.log("[trails-sdk] sending origin transaction")
|
|
668
|
+
const usingLIfi = false
|
|
669
|
+
let needsNativeFee = false
|
|
670
|
+
|
|
671
|
+
if (usingLIfi) {
|
|
672
|
+
needsNativeFee = getNeedsLifiNativeFee({
|
|
673
|
+
originTokenAddress,
|
|
674
|
+
destinationTokenAmount: swapAmount,
|
|
675
|
+
destinationTokenDecimals,
|
|
676
|
+
sourceTokenDecimals,
|
|
677
|
+
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
678
|
+
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
679
|
+
depositAmount,
|
|
680
|
+
})
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
|
|
684
|
+
logger.console.log(
|
|
685
|
+
"[trails-sdk] sourceTokenPriceUsd",
|
|
686
|
+
sourceTokenPriceUsd,
|
|
687
|
+
)
|
|
688
|
+
logger.console.log(
|
|
689
|
+
"[trails-sdk] destinationTokenPriceUsd",
|
|
690
|
+
destinationTokenPriceUsd,
|
|
691
|
+
)
|
|
692
|
+
logger.console.log(
|
|
693
|
+
"[trails-sdk] sourceTokenDecimals",
|
|
694
|
+
sourceTokenDecimals,
|
|
695
|
+
)
|
|
696
|
+
logger.console.log(
|
|
697
|
+
"[trails-sdk] destinationTokenDecimals",
|
|
698
|
+
destinationTokenDecimals,
|
|
699
|
+
)
|
|
700
|
+
|
|
701
|
+
let depositUserTxnReceipt: TransactionReceipt | null = null
|
|
702
|
+
let originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
703
|
+
let destinationMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
704
|
+
|
|
705
|
+
const testnet = isTestnetDebugMode()
|
|
706
|
+
const effectiveOriginChain = testnet
|
|
707
|
+
? getTestnetChainInfo(chain)!
|
|
708
|
+
: chain
|
|
709
|
+
const effectiveOriginTokenAddress = testnet
|
|
710
|
+
? getTestnetOriginTokenAddress(effectiveOriginChain.id)
|
|
711
|
+
: originTokenAddress
|
|
712
|
+
|
|
713
|
+
logger.console.log("[trails-sdk] testnet", testnet)
|
|
714
|
+
|
|
715
|
+
const destinationPublicClient = createPublicClient({
|
|
716
|
+
chain: getChainInfo(destinationChainId)!,
|
|
717
|
+
transport: http(),
|
|
718
|
+
})
|
|
719
|
+
|
|
720
|
+
const depositPromise = async () => {
|
|
721
|
+
logger.console.log(
|
|
722
|
+
"[trails-sdk] depositPromise called - starting deposit transaction",
|
|
723
|
+
)
|
|
724
|
+
logger.console.log("[trails-sdk] fundMethod value:", fundMethod)
|
|
725
|
+
|
|
726
|
+
// Skip wallet deposit if fund method is qr-code
|
|
727
|
+
if (fundMethod === "qr-code" || fundMethod === "exchange") {
|
|
728
|
+
logger.console.log(
|
|
729
|
+
"[trails-sdk] Skipping wallet deposit for QR code mode - fundMethod is:",
|
|
730
|
+
fundMethod,
|
|
731
|
+
)
|
|
732
|
+
return
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
logger.console.log(
|
|
736
|
+
"[trails-sdk] Calling attemptUserDepositTx with params:",
|
|
737
|
+
{
|
|
738
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
739
|
+
paymasterUrl,
|
|
740
|
+
chain: effectiveOriginChain.id,
|
|
741
|
+
account: account.address,
|
|
742
|
+
firstPreconditionMin,
|
|
743
|
+
originIntentAddress,
|
|
744
|
+
fee,
|
|
745
|
+
dryMode,
|
|
746
|
+
feeOptions: gasFeeOptions,
|
|
747
|
+
selectedFeeToken: effectiveSelectedFeeToken,
|
|
748
|
+
selectedFeeTokenType: typeof effectiveSelectedFeeToken,
|
|
749
|
+
selectedFeeTokenValue: JSON.stringify(effectiveSelectedFeeToken),
|
|
750
|
+
},
|
|
751
|
+
)
|
|
752
|
+
|
|
753
|
+
depositUserTxnReceipt = await attemptUserDepositTx({
|
|
754
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
755
|
+
paymasterUrl,
|
|
756
|
+
chain: effectiveOriginChain,
|
|
757
|
+
account,
|
|
758
|
+
firstPreconditionMin,
|
|
759
|
+
originIntentAddress,
|
|
760
|
+
onOriginSend,
|
|
761
|
+
publicClient,
|
|
762
|
+
walletClient,
|
|
763
|
+
destinationTokenDecimals,
|
|
764
|
+
sourceTokenDecimals,
|
|
765
|
+
fee,
|
|
766
|
+
dryMode,
|
|
767
|
+
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
768
|
+
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
769
|
+
swapAmount,
|
|
770
|
+
onTransactionStateChange,
|
|
771
|
+
transactionStates,
|
|
772
|
+
fundMethod,
|
|
773
|
+
originTokenSymbol,
|
|
774
|
+
destinationTokenSymbol,
|
|
775
|
+
depositAmountUsd,
|
|
776
|
+
feeOptions: gasFeeOptions,
|
|
777
|
+
trailsClient,
|
|
778
|
+
selectedFeeToken: effectiveSelectedFeeToken,
|
|
779
|
+
walletId,
|
|
780
|
+
abortSignal,
|
|
781
|
+
intentId: intent.intentId,
|
|
782
|
+
})
|
|
783
|
+
|
|
784
|
+
// In gasless mode, depositUserTxnReceipt will be null because the transaction
|
|
785
|
+
// is submitted to the backend and monitored via checkForDepositTx polling
|
|
786
|
+
// In non-gasless mode, we should have a receipt
|
|
787
|
+
// if (!depositUserTxnReceipt && !effectiveGasless) {
|
|
788
|
+
// throw new Error("Failed to send origin transaction")
|
|
789
|
+
// }
|
|
790
|
+
|
|
791
|
+
// Only process receipt if we have one (non-gasless mode)
|
|
792
|
+
if (depositUserTxnReceipt) {
|
|
793
|
+
transactionStates[0] = getTransactionStateFromReceipt(
|
|
794
|
+
depositUserTxnReceipt,
|
|
795
|
+
originChainId,
|
|
796
|
+
transactionStates[0]?.label,
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
// Call executeIntent after successful origin transaction
|
|
800
|
+
// This triggers the backend to start executing the intent
|
|
801
|
+
// In gasless mode, the executeIntent is called in attemptGaslessDeposit
|
|
802
|
+
if (depositUserTxnReceipt.status === "success" && intent.intentId) {
|
|
803
|
+
logger.console.log(
|
|
804
|
+
"[trails-sdk] Calling executeIntent with deposit transaction hash",
|
|
805
|
+
{
|
|
806
|
+
intentId: intent.intentId,
|
|
807
|
+
txHash: depositUserTxnReceipt.transactionHash,
|
|
808
|
+
},
|
|
809
|
+
)
|
|
810
|
+
try {
|
|
811
|
+
const executeIntentFnToUse =
|
|
812
|
+
executeIntentFn ||
|
|
813
|
+
trailsClient.executeIntent.bind(trailsClient)
|
|
814
|
+
await executeIntentFnToUse({
|
|
815
|
+
intentId: intent.intentId,
|
|
816
|
+
depositTransactionHash: depositUserTxnReceipt.transactionHash,
|
|
817
|
+
})
|
|
818
|
+
logger.console.log(
|
|
819
|
+
"[trails-sdk] executeIntent completed successfully",
|
|
820
|
+
)
|
|
821
|
+
} catch (error) {
|
|
822
|
+
logger.console.error(
|
|
823
|
+
"[trails-sdk] Error calling executeIntent:",
|
|
824
|
+
error,
|
|
825
|
+
)
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
try {
|
|
830
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents =
|
|
831
|
+
decodeTrailsTokenSweeperEvents(depositUserTxnReceipt)
|
|
832
|
+
transactionStates[0].decodedGuestModuleEvents =
|
|
833
|
+
decodeGuestModuleEvents(depositUserTxnReceipt)
|
|
834
|
+
transactionStates[0].refunded =
|
|
835
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents.findIndex(
|
|
836
|
+
(event) =>
|
|
837
|
+
event.type === "Refund" || event.type === "RefundAndSweep",
|
|
838
|
+
) !== -1
|
|
839
|
+
logger.console.log(
|
|
840
|
+
"[trails-sdk] [GASLESS-FLOW] Gasless deposit events decoded",
|
|
841
|
+
{
|
|
842
|
+
chainId: originChainId,
|
|
843
|
+
callFailed: (
|
|
844
|
+
transactionStates[0].decodedGuestModuleEvents || []
|
|
845
|
+
).filter((e: any) => e?.type === "CallFailed").length,
|
|
846
|
+
sweeperEvents: (
|
|
847
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents || []
|
|
848
|
+
).length,
|
|
849
|
+
refunded: transactionStates[0].refunded,
|
|
850
|
+
},
|
|
851
|
+
)
|
|
852
|
+
|
|
853
|
+
// Check for transaction failure or refund
|
|
854
|
+
const hasCallFailed = (
|
|
855
|
+
transactionStates[0].decodedGuestModuleEvents || []
|
|
856
|
+
).some((e: any) => e?.type === "CallFailed")
|
|
857
|
+
|
|
858
|
+
if (transactionStates[0].refunded || hasCallFailed) {
|
|
859
|
+
const errorMessage = transactionStates[0].refunded
|
|
860
|
+
? "Transaction was refunded"
|
|
861
|
+
: "Transaction call failed"
|
|
862
|
+
|
|
863
|
+
logger.console.error(
|
|
864
|
+
"[trails-sdk] [GASLESS-FLOW] Deposit transaction failed",
|
|
865
|
+
{
|
|
866
|
+
refunded: transactionStates[0].refunded,
|
|
867
|
+
callFailed: hasCallFailed,
|
|
868
|
+
},
|
|
869
|
+
)
|
|
870
|
+
|
|
871
|
+
// Call onCheckoutError callback if provided
|
|
872
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
873
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
} catch (error) {
|
|
877
|
+
logger.console.error(
|
|
878
|
+
"[trails-sdk] Error decoding gasless deposit events",
|
|
879
|
+
error,
|
|
880
|
+
)
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
onTransactionStateChange(transactionStates)
|
|
884
|
+
|
|
885
|
+
setTimeout(() => {
|
|
886
|
+
const destinationChain = getChainInfo(destinationChainId)
|
|
887
|
+
updatePersistentToast(
|
|
888
|
+
"In Progress",
|
|
889
|
+
`Your transaction to ${destinationChain?.name || "chain"} is in progress`,
|
|
890
|
+
"info",
|
|
891
|
+
)
|
|
892
|
+
}, 1000)
|
|
893
|
+
} else {
|
|
894
|
+
// Gasless mode - transaction submitted to backend via executeIntent in attemptGaslessDeposit
|
|
895
|
+
logger.console.log(
|
|
896
|
+
"[trails-sdk] Gasless mode - intent submitted to backend via executeIntent, transaction will be monitored via polling",
|
|
897
|
+
)
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
// Note: Unified polling will be started outside depositPromise after it completes
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
const checkForDepositTx = async () => {
|
|
904
|
+
while (true) {
|
|
905
|
+
// Check if we should abort
|
|
906
|
+
if (abortSignal?.aborted) {
|
|
907
|
+
logger.console.log(
|
|
908
|
+
"[trails-sdk] Aborting deposit tx check due to abort signal",
|
|
909
|
+
)
|
|
910
|
+
return null
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
try {
|
|
914
|
+
const response = await getAccountTransactionHistory({
|
|
915
|
+
chainId: originChainId,
|
|
916
|
+
accountAddress: originIntentAddress,
|
|
917
|
+
abortSignal,
|
|
918
|
+
})
|
|
919
|
+
logger.console.log(
|
|
920
|
+
"[trails-sdk] getAccountTransactionHistory response",
|
|
921
|
+
response,
|
|
922
|
+
)
|
|
923
|
+
if (response.transactions.length > 0) {
|
|
924
|
+
const tx = response.transactions[0]
|
|
925
|
+
if (!tx?.txnHash) {
|
|
926
|
+
await new Promise((resolve) =>
|
|
927
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
928
|
+
)
|
|
929
|
+
continue
|
|
930
|
+
}
|
|
931
|
+
// const isReceive = tx.transfers.some(
|
|
932
|
+
// (transfer) => transfer.transferType === "RECEIVE",
|
|
933
|
+
// )
|
|
934
|
+
// if (!isReceive) {
|
|
935
|
+
// await new Promise((resolve) => setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY))
|
|
936
|
+
// continue
|
|
937
|
+
// }
|
|
938
|
+
const originDepositTxReceipt =
|
|
939
|
+
await publicClient.getTransactionReceipt({
|
|
940
|
+
hash: tx.txnHash as `0x${string}`,
|
|
941
|
+
})
|
|
942
|
+
|
|
943
|
+
depositUserTxnReceipt = originDepositTxReceipt
|
|
944
|
+
|
|
945
|
+
transactionStates[0] = getTransactionStateFromReceipt(
|
|
946
|
+
originDepositTxReceipt,
|
|
947
|
+
originChainId,
|
|
948
|
+
transactionStates[0]?.label,
|
|
949
|
+
)
|
|
950
|
+
onTransactionStateChange(transactionStates)
|
|
951
|
+
|
|
952
|
+
if (onOriginSend) {
|
|
953
|
+
onOriginSend()
|
|
954
|
+
}
|
|
955
|
+
break
|
|
956
|
+
}
|
|
957
|
+
} catch (error) {
|
|
958
|
+
logger.console.error("Error checking for deposit tx", error)
|
|
959
|
+
}
|
|
960
|
+
await new Promise((resolve) =>
|
|
961
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
962
|
+
)
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
|
|
966
|
+
const _checkForDestinationDepositTx: () => Promise<TransactionReceipt | null> =
|
|
967
|
+
async () => {
|
|
968
|
+
while (true) {
|
|
969
|
+
// Check if we should abort
|
|
970
|
+
if (abortSignal?.aborted) {
|
|
971
|
+
logger.console.log(
|
|
972
|
+
"[trails-sdk] Aborting destination deposit tx check due to abort signal",
|
|
973
|
+
)
|
|
974
|
+
return null
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
try {
|
|
978
|
+
const response = await getAccountTransactionHistory({
|
|
979
|
+
chainId: destinationChainId,
|
|
980
|
+
accountAddress:
|
|
981
|
+
intent.destinationIntentAddress as `0x${string}`,
|
|
982
|
+
abortSignal,
|
|
983
|
+
})
|
|
984
|
+
logger.console.log(
|
|
985
|
+
"[trails-sdk] getAccountTransactionHistory response",
|
|
986
|
+
response,
|
|
987
|
+
)
|
|
988
|
+
if (response.transactions.length > 0) {
|
|
989
|
+
const tx = response.transactions[0]
|
|
990
|
+
if (!tx?.txnHash) {
|
|
991
|
+
await new Promise((resolve) =>
|
|
992
|
+
setTimeout(
|
|
993
|
+
resolve,
|
|
994
|
+
POLLING_INTERVALS.TRANSACTION_HISTORY,
|
|
995
|
+
),
|
|
996
|
+
)
|
|
997
|
+
continue
|
|
998
|
+
}
|
|
999
|
+
// const isReceive = tx.transfers.some(
|
|
1000
|
+
// (transfer) => transfer.transferType === "RECEIVE",
|
|
1001
|
+
// )
|
|
1002
|
+
// if (!isReceive) {
|
|
1003
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1004
|
+
// continue
|
|
1005
|
+
// }
|
|
1006
|
+
const destinationDepositTxReceipt =
|
|
1007
|
+
await destinationPublicClient.getTransactionReceipt({
|
|
1008
|
+
hash: tx.txnHash as `0x${string}`,
|
|
1009
|
+
})
|
|
1010
|
+
|
|
1011
|
+
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1012
|
+
destinationDepositTxReceipt,
|
|
1013
|
+
destinationChainId,
|
|
1014
|
+
transactionStates[2]?.label,
|
|
1015
|
+
)
|
|
1016
|
+
onTransactionStateChange(transactionStates)
|
|
1017
|
+
|
|
1018
|
+
return destinationDepositTxReceipt
|
|
1019
|
+
}
|
|
1020
|
+
} catch (error) {
|
|
1021
|
+
logger.console.error("Error checking for deposit tx", error)
|
|
1022
|
+
}
|
|
1023
|
+
await new Promise((resolve) =>
|
|
1024
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
1025
|
+
)
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Variables to store the waitForReceipt functions
|
|
1030
|
+
// DEPRECATED: These are no longer used - we now use the unified poller
|
|
1031
|
+
// let originMetaTxnReceiptPromise:
|
|
1032
|
+
// | ((abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>)
|
|
1033
|
+
// | null = null
|
|
1034
|
+
// let destinationMetaTxnReceiptPromise:
|
|
1035
|
+
// | ((abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>)
|
|
1036
|
+
// | ((abortSignal?: AbortSignal) => Promise<TransactionReceipt | null>)
|
|
1037
|
+
// | null = null
|
|
1038
|
+
|
|
1039
|
+
// Unified polling via shared poller instead of separate loops
|
|
1040
|
+
let unifiedPollerAbortController: AbortController | null = null
|
|
1041
|
+
let unifiedPollerPromise: Promise<IntentReceipt | null> | null = null
|
|
1042
|
+
|
|
1043
|
+
// Helper function to start unified polling for both origin and destination transactions
|
|
1044
|
+
const startUnifiedPolling = () => {
|
|
1045
|
+
if (!unifiedPollerAbortController) {
|
|
1046
|
+
logger.console.log(
|
|
1047
|
+
"[trails-sdk] Starting unified polling for intent",
|
|
1048
|
+
{
|
|
1049
|
+
intentId: intent.intentId,
|
|
1050
|
+
},
|
|
1051
|
+
)
|
|
1052
|
+
|
|
1053
|
+
unifiedPollerAbortController = new AbortController()
|
|
1054
|
+
unifiedPollerPromise = pollIntentReceipt({
|
|
1055
|
+
intentId: intent.intentId,
|
|
1056
|
+
trailsClient,
|
|
1057
|
+
abortSignal: unifiedPollerAbortController.signal,
|
|
1058
|
+
callbacks: {
|
|
1059
|
+
onDepositTransactionFound: async (txHash) => {
|
|
1060
|
+
logger.console.log(
|
|
1061
|
+
"[trails-sdk] Unified poller discovered deposit transaction",
|
|
1062
|
+
{
|
|
1063
|
+
txHash,
|
|
1064
|
+
},
|
|
1065
|
+
)
|
|
1066
|
+
|
|
1067
|
+
// For gasless flows, the deposit transaction is the user's initial deposit
|
|
1068
|
+
// Update transactionStates[0] with the deposit transaction receipt
|
|
1069
|
+
if (transactionStates[0]) {
|
|
1070
|
+
try {
|
|
1071
|
+
const depositTxReceipt =
|
|
1072
|
+
await publicClient.getTransactionReceipt({
|
|
1073
|
+
hash: txHash as `0x${string}`,
|
|
1074
|
+
})
|
|
1075
|
+
logger.console.log(
|
|
1076
|
+
"[trails-sdk] Deposit transaction receipt fetched",
|
|
1077
|
+
{
|
|
1078
|
+
txHash,
|
|
1079
|
+
blockNumber: depositTxReceipt?.blockNumber,
|
|
1080
|
+
status: depositTxReceipt?.status,
|
|
1081
|
+
},
|
|
1082
|
+
)
|
|
1083
|
+
|
|
1084
|
+
transactionStates[0] = getTransactionStateFromReceipt(
|
|
1085
|
+
depositTxReceipt,
|
|
1086
|
+
originChainId,
|
|
1087
|
+
transactionStates[0]?.label,
|
|
1088
|
+
)
|
|
1089
|
+
onTransactionStateChange(transactionStates)
|
|
1090
|
+
|
|
1091
|
+
// Decode events for the deposit transaction
|
|
1092
|
+
try {
|
|
1093
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents =
|
|
1094
|
+
decodeTrailsTokenSweeperEvents(depositTxReceipt)
|
|
1095
|
+
transactionStates[0].decodedGuestModuleEvents =
|
|
1096
|
+
decodeGuestModuleEvents(depositTxReceipt)
|
|
1097
|
+
transactionStates[0].refunded =
|
|
1098
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1099
|
+
(event) =>
|
|
1100
|
+
event.type === "Refund" ||
|
|
1101
|
+
event.type === "RefundAndSweep",
|
|
1102
|
+
) !== -1
|
|
1103
|
+
logger.console.log(
|
|
1104
|
+
"[trails-sdk] Deposit transaction events decoded",
|
|
1105
|
+
{
|
|
1106
|
+
chainId: originChainId,
|
|
1107
|
+
callFailed: (
|
|
1108
|
+
transactionStates[0].decodedGuestModuleEvents ||
|
|
1109
|
+
[]
|
|
1110
|
+
).filter((e: any) => e?.type === "CallFailed")
|
|
1111
|
+
.length,
|
|
1112
|
+
sweeperEvents: (
|
|
1113
|
+
transactionStates[0]
|
|
1114
|
+
.decodedTrailsTokenSweeperEvents || []
|
|
1115
|
+
).length,
|
|
1116
|
+
refunded: transactionStates[0].refunded,
|
|
1117
|
+
},
|
|
1118
|
+
)
|
|
1119
|
+
|
|
1120
|
+
const hasCallFailed = (
|
|
1121
|
+
transactionStates[0].decodedGuestModuleEvents || []
|
|
1122
|
+
).some((e: any) => e?.type === "CallFailed")
|
|
1123
|
+
|
|
1124
|
+
if (transactionStates[0].refunded || hasCallFailed) {
|
|
1125
|
+
const errorMessage = transactionStates[0].refunded
|
|
1126
|
+
? "Deposit transaction was refunded"
|
|
1127
|
+
: "Deposit transaction call failed"
|
|
1128
|
+
|
|
1129
|
+
logger.console.error(
|
|
1130
|
+
"[trails-sdk] Deposit transaction failed",
|
|
1131
|
+
{
|
|
1132
|
+
refunded: transactionStates[0].refunded,
|
|
1133
|
+
callFailed: hasCallFailed,
|
|
1134
|
+
},
|
|
1135
|
+
)
|
|
1136
|
+
|
|
1137
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1138
|
+
checkoutOnHandlers.triggerCheckoutError(
|
|
1139
|
+
errorMessage,
|
|
1140
|
+
)
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
onTransactionStateChange(transactionStates)
|
|
1145
|
+
} catch (error) {
|
|
1146
|
+
logger.console.error(
|
|
1147
|
+
"Error decoding deposit tx events",
|
|
1148
|
+
error,
|
|
1149
|
+
)
|
|
1150
|
+
}
|
|
1151
|
+
} catch (error) {
|
|
1152
|
+
logger.console.error(
|
|
1153
|
+
"[trails-sdk] Error fetching deposit transaction receipt:",
|
|
1154
|
+
error,
|
|
1155
|
+
)
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
},
|
|
1159
|
+
onOriginTransactionFound: async (txHash) => {
|
|
1160
|
+
logger.console.log(
|
|
1161
|
+
"[trails-sdk] Unified poller discovered origin transaction",
|
|
1162
|
+
{
|
|
1163
|
+
txHash,
|
|
1164
|
+
},
|
|
1165
|
+
)
|
|
1166
|
+
|
|
1167
|
+
// Store the transaction hash
|
|
1168
|
+
originMetaTxnReceipt = { txnHash: txHash } as MetaTxnReceipt
|
|
1169
|
+
|
|
1170
|
+
const originTxnReceipt =
|
|
1171
|
+
await publicClient.getTransactionReceipt({
|
|
1172
|
+
hash: txHash as `0x${string}`,
|
|
1173
|
+
})
|
|
1174
|
+
logger.console.log(
|
|
1175
|
+
"[trails-sdk] Origin transaction receipt fetched",
|
|
1176
|
+
{
|
|
1177
|
+
txHash,
|
|
1178
|
+
blockNumber: originTxnReceipt?.blockNumber,
|
|
1179
|
+
status: originTxnReceipt?.status,
|
|
1180
|
+
},
|
|
1181
|
+
)
|
|
1182
|
+
|
|
1183
|
+
if (transactionStates[1]) {
|
|
1184
|
+
transactionStates[1] = getTransactionStateFromReceipt(
|
|
1185
|
+
originTxnReceipt,
|
|
1186
|
+
originChainId,
|
|
1187
|
+
transactionStates[1]?.label,
|
|
1188
|
+
)
|
|
1189
|
+
onTransactionStateChange(transactionStates)
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
// Decode events for the origin transaction
|
|
1193
|
+
try {
|
|
1194
|
+
transactionStates[1]!.decodedTrailsTokenSweeperEvents =
|
|
1195
|
+
decodeTrailsTokenSweeperEvents(originTxnReceipt)
|
|
1196
|
+
transactionStates[1]!.decodedGuestModuleEvents =
|
|
1197
|
+
decodeGuestModuleEvents(originTxnReceipt)
|
|
1198
|
+
transactionStates[1]!.refunded =
|
|
1199
|
+
transactionStates[1]!.decodedTrailsTokenSweeperEvents.findIndex(
|
|
1200
|
+
(event) =>
|
|
1201
|
+
event.type === "Refund" ||
|
|
1202
|
+
event.type === "RefundAndSweep",
|
|
1203
|
+
) !== -1
|
|
1204
|
+
logger.console.log("[trails-sdk] Origin meta-tx events", {
|
|
1205
|
+
chainId: originChainId,
|
|
1206
|
+
callFailed: (
|
|
1207
|
+
transactionStates[1]!.decodedGuestModuleEvents || []
|
|
1208
|
+
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1209
|
+
sweeperEvents: (
|
|
1210
|
+
transactionStates[1]!.decodedTrailsTokenSweeperEvents ||
|
|
1211
|
+
[]
|
|
1212
|
+
).length,
|
|
1213
|
+
refunded: transactionStates[1]!.refunded,
|
|
1214
|
+
})
|
|
1215
|
+
|
|
1216
|
+
const hasCallFailed = (
|
|
1217
|
+
transactionStates[1]!.decodedGuestModuleEvents || []
|
|
1218
|
+
).some((e: any) => e?.type === "CallFailed")
|
|
1219
|
+
|
|
1220
|
+
if (transactionStates[1]!.refunded || hasCallFailed) {
|
|
1221
|
+
const errorMessage = transactionStates[1]!.refunded
|
|
1222
|
+
? "Origin transaction was refunded"
|
|
1223
|
+
: "Origin transaction call failed"
|
|
1224
|
+
|
|
1225
|
+
logger.console.error(
|
|
1226
|
+
"[trails-sdk] Origin meta-tx failed",
|
|
1227
|
+
{
|
|
1228
|
+
refunded: transactionStates[1]!.refunded,
|
|
1229
|
+
callFailed: hasCallFailed,
|
|
1230
|
+
},
|
|
1231
|
+
)
|
|
1232
|
+
|
|
1233
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1234
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
onTransactionStateChange(transactionStates)
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
logger.console.error(
|
|
1241
|
+
"Error decoding origin tx events",
|
|
1242
|
+
error,
|
|
1243
|
+
)
|
|
1244
|
+
}
|
|
1245
|
+
},
|
|
1246
|
+
onDestinationTransactionFound: async (txHash) => {
|
|
1247
|
+
logger.console.log(
|
|
1248
|
+
"[trails-sdk] Unified poller discovered destination transaction",
|
|
1249
|
+
{
|
|
1250
|
+
txHash,
|
|
1251
|
+
},
|
|
1252
|
+
)
|
|
1253
|
+
|
|
1254
|
+
// Store the transaction hash
|
|
1255
|
+
destinationMetaTxnReceipt = {
|
|
1256
|
+
txnHash: txHash,
|
|
1257
|
+
} as MetaTxnReceipt
|
|
1258
|
+
|
|
1259
|
+
const destinationTxnReceipt =
|
|
1260
|
+
await destinationPublicClient.getTransactionReceipt({
|
|
1261
|
+
hash: txHash as `0x${string}`,
|
|
1262
|
+
})
|
|
1263
|
+
logger.console.log(
|
|
1264
|
+
"[trails-sdk] Destination transaction receipt fetched",
|
|
1265
|
+
{
|
|
1266
|
+
txHash,
|
|
1267
|
+
blockNumber: destinationTxnReceipt?.blockNumber,
|
|
1268
|
+
status: destinationTxnReceipt?.status,
|
|
1269
|
+
},
|
|
1270
|
+
)
|
|
1271
|
+
|
|
1272
|
+
if (transactionStates[2]) {
|
|
1273
|
+
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1274
|
+
destinationTxnReceipt,
|
|
1275
|
+
destinationChainId,
|
|
1276
|
+
transactionStates[2]?.label,
|
|
1277
|
+
)
|
|
1278
|
+
onTransactionStateChange(transactionStates)
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Decode events for the destination transaction
|
|
1282
|
+
try {
|
|
1283
|
+
transactionStates[2]!.decodedTrailsTokenSweeperEvents =
|
|
1284
|
+
decodeTrailsTokenSweeperEvents(destinationTxnReceipt)
|
|
1285
|
+
transactionStates[2]!.decodedGuestModuleEvents =
|
|
1286
|
+
decodeGuestModuleEvents(destinationTxnReceipt)
|
|
1287
|
+
transactionStates[2]!.refunded =
|
|
1288
|
+
transactionStates[2]!.decodedTrailsTokenSweeperEvents.findIndex(
|
|
1289
|
+
(event) =>
|
|
1290
|
+
event.type === "Refund" ||
|
|
1291
|
+
event.type === "RefundAndSweep",
|
|
1292
|
+
) !== -1 ||
|
|
1293
|
+
(transactionStates[2]!.decodedTrailsTokenSweeperEvents.findIndex(
|
|
1294
|
+
(event) => event.type === "Sweep",
|
|
1295
|
+
) !== -1 &&
|
|
1296
|
+
transactionStates[2]!.decodedGuestModuleEvents.findIndex(
|
|
1297
|
+
(event) => event.type === "CallFailed",
|
|
1298
|
+
) !== -1)
|
|
1299
|
+
logger.console.log(
|
|
1300
|
+
"[trails-sdk] Destination meta-tx events",
|
|
1301
|
+
{
|
|
1302
|
+
chainId: destinationChainId,
|
|
1303
|
+
callFailed: (
|
|
1304
|
+
transactionStates[2]!.decodedGuestModuleEvents || []
|
|
1305
|
+
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1306
|
+
sweeperEvents: (
|
|
1307
|
+
transactionStates[2]!
|
|
1308
|
+
.decodedTrailsTokenSweeperEvents || []
|
|
1309
|
+
).length,
|
|
1310
|
+
refunded: transactionStates[2]!.refunded,
|
|
1311
|
+
},
|
|
1312
|
+
)
|
|
1313
|
+
|
|
1314
|
+
const hasCallFailed = (
|
|
1315
|
+
transactionStates[2]!.decodedGuestModuleEvents || []
|
|
1316
|
+
).some((e: any) => e?.type === "CallFailed")
|
|
1317
|
+
|
|
1318
|
+
if (transactionStates[2]!.refunded || hasCallFailed) {
|
|
1319
|
+
const errorMessage = transactionStates[2]!.refunded
|
|
1320
|
+
? "Destination transaction was refunded"
|
|
1321
|
+
: "Destination transaction call failed"
|
|
1322
|
+
|
|
1323
|
+
logger.console.error(
|
|
1324
|
+
"[trails-sdk] Destination meta-tx failed",
|
|
1325
|
+
{
|
|
1326
|
+
refunded: transactionStates[2]!.refunded,
|
|
1327
|
+
callFailed: hasCallFailed,
|
|
1328
|
+
},
|
|
1329
|
+
)
|
|
1330
|
+
|
|
1331
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1332
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1333
|
+
}
|
|
1334
|
+
}
|
|
1335
|
+
|
|
1336
|
+
onTransactionStateChange(transactionStates)
|
|
1337
|
+
} catch (error) {
|
|
1338
|
+
logger.console.error(
|
|
1339
|
+
"Error decoding destination tx events",
|
|
1340
|
+
error,
|
|
1341
|
+
)
|
|
1342
|
+
}
|
|
1343
|
+
},
|
|
1344
|
+
},
|
|
1345
|
+
}).then(
|
|
1346
|
+
() => {
|
|
1347
|
+
logger.console.log("[trails-sdk] Unified poller completed")
|
|
1348
|
+
return null
|
|
1349
|
+
},
|
|
1350
|
+
(error) => {
|
|
1351
|
+
logger.console.error("[trails-sdk] Unified poller error", error)
|
|
1352
|
+
return null
|
|
1353
|
+
},
|
|
1354
|
+
)
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// First phase: Send meta transactions and queue CCTP
|
|
1359
|
+
const originSendMetaTxnPromise = async () => {
|
|
1360
|
+
logger.console.log("[trails-sdk] Starting originSendMetaTxnPromise", {
|
|
1361
|
+
hasMetaTxn: !!intent.metaTxns[0],
|
|
1362
|
+
hasPrecondition: !!intent.preconditions[0],
|
|
1363
|
+
metaTxnId: intent.metaTxns[0]?.id,
|
|
1364
|
+
chainId: intent.metaTxns[0]?.chainId,
|
|
1365
|
+
})
|
|
1366
|
+
|
|
1367
|
+
if (intent.metaTxns[0] && intent.preconditions[0]) {
|
|
1368
|
+
const metaTxnId = intent.metaTxns[0].id
|
|
1369
|
+
const feeQuote = undefined
|
|
1370
|
+
logger.console.log("[trails-sdk] Processing origin meta txn", {
|
|
1371
|
+
metaTxnId,
|
|
1372
|
+
feeQuote,
|
|
1373
|
+
hasFeeQuote: !!feeQuote,
|
|
1374
|
+
})
|
|
1375
|
+
|
|
1376
|
+
logger.console.log(
|
|
1377
|
+
"[trails-sdk] NOTE: Client-side meta transaction submission deprecated",
|
|
1378
|
+
{
|
|
1379
|
+
metaTxnId,
|
|
1380
|
+
chainId: intent.metaTxns[0].chainId,
|
|
1381
|
+
walletAddress: intent.metaTxns[0].walletAddress,
|
|
1382
|
+
contract: intent.metaTxns[0].contract,
|
|
1383
|
+
message:
|
|
1384
|
+
"Transaction submission now handled by backend via executeIntent. Monitoring via WaitIntentReceipt API.",
|
|
1385
|
+
},
|
|
1386
|
+
)
|
|
1387
|
+
|
|
1388
|
+
logger.console.log(
|
|
1389
|
+
"[trails-sdk] Origin meta transaction sent successfully",
|
|
1390
|
+
{
|
|
1391
|
+
metaTxnId,
|
|
1392
|
+
},
|
|
1393
|
+
)
|
|
1394
|
+
|
|
1395
|
+
// Note: Unified polling will be started after deposit transaction is submitted
|
|
1396
|
+
} else {
|
|
1397
|
+
logger.console.warn(
|
|
1398
|
+
"[trails-sdk] Skipping origin sendMetaTxn - missing metaTxn or precondition",
|
|
1399
|
+
{
|
|
1400
|
+
hasMetaTxn: !!intent.metaTxns[0],
|
|
1401
|
+
hasPrecondition: !!intent.preconditions[0],
|
|
1402
|
+
},
|
|
1403
|
+
)
|
|
1404
|
+
}
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
const destinationSendMetaTxnPromise = async () => {
|
|
1408
|
+
logger.console.log(
|
|
1409
|
+
"[trails-sdk] Starting destinationSendMetaTxnPromise",
|
|
1410
|
+
{
|
|
1411
|
+
quoteProvider: intent.quote.quoteProvider,
|
|
1412
|
+
hasQuoteProviderRequestId: !!intent.quote.quoteProviderRequestId,
|
|
1413
|
+
hasPrecondition1: !!intent.preconditions[1],
|
|
1414
|
+
hasMetaTxn1: !!intent.metaTxns[1],
|
|
1415
|
+
},
|
|
1416
|
+
)
|
|
1417
|
+
|
|
1418
|
+
// Destination meta transactions are handled by the unified poller
|
|
1419
|
+
// which will be started after the deposit transaction is submitted
|
|
1420
|
+
logger.console.log(
|
|
1421
|
+
"[trails-sdk] Destination meta transaction monitoring will be handled by unified poller",
|
|
1422
|
+
)
|
|
1423
|
+
}
|
|
1424
|
+
|
|
1425
|
+
let queueCctpPromise: (() => Promise<void>) | null = null
|
|
1426
|
+
|
|
1427
|
+
const isCctp = intent.quote.quoteProvider === "cctp"
|
|
1428
|
+
if (isCctp) {
|
|
1429
|
+
queueCctpPromise = async () => {
|
|
1430
|
+
while (true) {
|
|
1431
|
+
// Check if we should abort
|
|
1432
|
+
if (abortSignal?.aborted) {
|
|
1433
|
+
logger.console.log(
|
|
1434
|
+
"[trails-sdk] Aborting CCTP queue due to abort signal",
|
|
1435
|
+
)
|
|
1436
|
+
return
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
const originMetaTxnHash = (
|
|
1440
|
+
originMetaTxnReceipt as MetaTxnReceipt | null
|
|
1441
|
+
)?.txnHash
|
|
1442
|
+
if (originMetaTxnHash) {
|
|
1443
|
+
await queueCCTPTransfer({
|
|
1444
|
+
trailsClient,
|
|
1445
|
+
sourceTxHash: originMetaTxnHash,
|
|
1446
|
+
sourceChainId: originChainId,
|
|
1447
|
+
destinationChainId: destinationChainId,
|
|
1448
|
+
})
|
|
1449
|
+
break
|
|
1450
|
+
}
|
|
1451
|
+
await new Promise((resolve) =>
|
|
1452
|
+
setTimeout(resolve, POLLING_INTERVALS.CCTP_QUEUE),
|
|
1453
|
+
)
|
|
1454
|
+
}
|
|
1455
|
+
}
|
|
1456
|
+
} else {
|
|
1457
|
+
queueCctpPromise = () => Promise.resolve()
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
// Only start polling for external deposits if needed:
|
|
1461
|
+
// - QR code or exchange funding (external deposits)
|
|
1462
|
+
// - Gasless flow (relayer-executed deposits)
|
|
1463
|
+
const needsDepositPolling =
|
|
1464
|
+
fundMethod === "qr-code" ||
|
|
1465
|
+
fundMethod === "exchange" ||
|
|
1466
|
+
effectiveGasless === true
|
|
1467
|
+
|
|
1468
|
+
if (needsDepositPolling) {
|
|
1469
|
+
logger.console.log(
|
|
1470
|
+
"[trails-sdk] Starting deposit polling for fundMethod:",
|
|
1471
|
+
fundMethod,
|
|
1472
|
+
"effectiveGasless:",
|
|
1473
|
+
effectiveGasless,
|
|
1474
|
+
)
|
|
1475
|
+
checkForDepositTx().catch((error) => {
|
|
1476
|
+
logger.console.error("Error checking for deposit tx", error)
|
|
1477
|
+
})
|
|
1478
|
+
} else {
|
|
1479
|
+
logger.console.log(
|
|
1480
|
+
"[trails-sdk] Skipping deposit polling for wallet funding with gas fees",
|
|
1481
|
+
)
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// Phase 1: Send meta transactions and queue CCTP
|
|
1485
|
+
logger.console.log(
|
|
1486
|
+
"[trails-sdk] Starting Phase 1: Sending meta transactions and queuing CCTP",
|
|
1487
|
+
)
|
|
1488
|
+
|
|
1489
|
+
await Promise.all([
|
|
1490
|
+
originSendMetaTxnPromise(),
|
|
1491
|
+
destinationSendMetaTxnPromise(),
|
|
1492
|
+
])
|
|
1493
|
+
|
|
1494
|
+
logger.console.log("[trails-sdk] Phase 1 completed successfully")
|
|
1495
|
+
|
|
1496
|
+
// Phase 2: Wait for receipts and execute deposit
|
|
1497
|
+
logger.console.log(
|
|
1498
|
+
"[trails-sdk] Starting Phase 2: Waiting for receipts and executing deposit",
|
|
1499
|
+
)
|
|
1500
|
+
|
|
1501
|
+
const waitForOriginMetaTxnReceiptPromise = async () => {
|
|
1502
|
+
logger.console.log(
|
|
1503
|
+
"[trails-sdk] Waiting for origin meta transaction receipt via unified poller",
|
|
1504
|
+
)
|
|
1505
|
+
// The unified poller handles origin transaction discovery and decoding via callbacks
|
|
1506
|
+
// We just need to wait for it to complete
|
|
1507
|
+
if (unifiedPollerPromise) {
|
|
1508
|
+
try {
|
|
1509
|
+
await unifiedPollerPromise
|
|
1510
|
+
} catch (error) {
|
|
1511
|
+
logger.console.error(
|
|
1512
|
+
"[trails-sdk] Error waiting for unified poller:",
|
|
1513
|
+
error,
|
|
1514
|
+
)
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
const waitForDestinationMetaTxnReceiptPromise = async () => {
|
|
1520
|
+
logger.console.log(
|
|
1521
|
+
"[trails-sdk] Waiting for destination meta transaction receipt via unified poller",
|
|
1522
|
+
)
|
|
1523
|
+
// The unified poller handles destination transaction discovery and decoding via callbacks
|
|
1524
|
+
// We just need to wait for it to complete
|
|
1525
|
+
if (unifiedPollerPromise) {
|
|
1526
|
+
try {
|
|
1527
|
+
await unifiedPollerPromise
|
|
1528
|
+
} catch (error) {
|
|
1529
|
+
logger.console.error(
|
|
1530
|
+
"[trails-sdk] Error waiting for unified poller:",
|
|
1531
|
+
error,
|
|
1532
|
+
)
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
logger.console.log(
|
|
1538
|
+
"[trails-sdk] Executing Phase 2: First executing deposit",
|
|
1539
|
+
)
|
|
1540
|
+
logger.console.log(
|
|
1541
|
+
"[trails-sdk] About to call depositPromise - fundMethod is:",
|
|
1542
|
+
fundMethod,
|
|
1543
|
+
)
|
|
1544
|
+
|
|
1545
|
+
// Execute deposit first to get the transaction receipt and call executeIntent
|
|
1546
|
+
await depositPromise()
|
|
1547
|
+
|
|
1548
|
+
// Start unified polling after deposit transaction is submitted and executeIntent is called
|
|
1549
|
+
// This ensures the backend has received the executeIntent call before we start polling waitIntentReceipt
|
|
1550
|
+
logger.console.log(
|
|
1551
|
+
"[trails-sdk] Starting unified polling after executeIntent has been called",
|
|
1552
|
+
)
|
|
1553
|
+
startUnifiedPolling()
|
|
1554
|
+
|
|
1555
|
+
// Now wait for all the receipts and CCTP processing
|
|
1556
|
+
logger.console.log(
|
|
1557
|
+
"[trails-sdk] Waiting for origin/destination receipts and CCTP processing",
|
|
1558
|
+
)
|
|
1559
|
+
await Promise.all([
|
|
1560
|
+
waitForOriginMetaTxnReceiptPromise(),
|
|
1561
|
+
waitForDestinationMetaTxnReceiptPromise(),
|
|
1562
|
+
queueCctpPromise(),
|
|
1563
|
+
])
|
|
1564
|
+
logger.console.log("[trails-sdk] Phase 2 completed successfully")
|
|
1565
|
+
|
|
1566
|
+
// Track payment completion for different chain and different token
|
|
1567
|
+
// In gasless mode, we don't have depositUserTxnReceipt since it's handled by backend
|
|
1568
|
+
// For same-chain, we don't have a destination meta transaction, only origin
|
|
1569
|
+
// For cross-chain, we need both origin and destination meta transactions
|
|
1570
|
+
const isDestinationCrossChain = originChainId !== destinationChainId
|
|
1571
|
+
const isSameChain = originChainId === destinationChainId
|
|
1572
|
+
const hasRequiredReceipts =
|
|
1573
|
+
(depositUserTxnReceipt || effectiveGasless) &&
|
|
1574
|
+
(isDestinationCrossChain
|
|
1575
|
+
? destinationMetaTxnReceipt
|
|
1576
|
+
: isSameChain
|
|
1577
|
+
? originMetaTxnReceipt
|
|
1578
|
+
: destinationMetaTxnReceipt)
|
|
1579
|
+
|
|
1580
|
+
if (hasRequiredReceipts) {
|
|
1581
|
+
// Check if any transaction failed or was refunded
|
|
1582
|
+
const hasAnyCallFailed = transactionStates.some((tx) =>
|
|
1583
|
+
tx?.decodedGuestModuleEvents?.some(
|
|
1584
|
+
(e: any) => e?.type === "CallFailed",
|
|
1585
|
+
),
|
|
1586
|
+
)
|
|
1587
|
+
const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
|
|
1588
|
+
const txStatus =
|
|
1589
|
+
!hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
|
|
1590
|
+
|
|
1591
|
+
// Always track payment completion regardless of success/failure
|
|
1592
|
+
trackPaymentCompleted({
|
|
1593
|
+
userAddress: account.address,
|
|
1594
|
+
originIntentAddress,
|
|
1595
|
+
originTxHash: depositUserTxnReceipt
|
|
1596
|
+
? (depositUserTxnReceipt as TransactionReceipt).transactionHash
|
|
1597
|
+
: undefined,
|
|
1598
|
+
destinationTxHash: (
|
|
1599
|
+
destinationMetaTxnReceipt as unknown as MetaTxnReceipt
|
|
1600
|
+
)?.txnHash,
|
|
1601
|
+
originChainId,
|
|
1602
|
+
destinationChainId,
|
|
1603
|
+
mode,
|
|
1604
|
+
fundMethod,
|
|
1605
|
+
originTokenSymbol,
|
|
1606
|
+
originTokenAddress,
|
|
1607
|
+
destinationTokenAddress,
|
|
1608
|
+
destinationTokenSymbol,
|
|
1609
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
1610
|
+
destinationTokenAmountUsd:
|
|
1611
|
+
effectiveDestinationTokenAmountUsd?.toString(),
|
|
1612
|
+
})
|
|
1613
|
+
|
|
1614
|
+
// Call onCheckoutComplete callback with transaction status
|
|
1615
|
+
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
1616
|
+
checkoutOnHandlers.triggerCheckoutComplete(
|
|
1617
|
+
txStatus,
|
|
1618
|
+
account.address,
|
|
1619
|
+
)
|
|
1620
|
+
}
|
|
1621
|
+
} else {
|
|
1622
|
+
if (
|
|
1623
|
+
transactionStates[1] &&
|
|
1624
|
+
transactionStates[1]?.transactionHash === "" &&
|
|
1625
|
+
transactionStates[1]?.state === "pending"
|
|
1626
|
+
) {
|
|
1627
|
+
transactionStates[1].state = "aborted"
|
|
1628
|
+
onTransactionStateChange(transactionStates)
|
|
1629
|
+
}
|
|
1630
|
+
if (
|
|
1631
|
+
transactionStates[2] &&
|
|
1632
|
+
transactionStates[2]?.transactionHash === "" &&
|
|
1633
|
+
transactionStates[2]?.state === "pending"
|
|
1634
|
+
) {
|
|
1635
|
+
transactionStates[2].state = "aborted"
|
|
1636
|
+
onTransactionStateChange(transactionStates)
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// Track payment error if transactions didn't complete successfully
|
|
1640
|
+
trackPaymentError({
|
|
1641
|
+
error:
|
|
1642
|
+
"Payment transactions possibly did not complete successfully. Was not able to receive both origin and destination meta transaction receipts. May be an API error.",
|
|
1643
|
+
userAddress: account.address,
|
|
1644
|
+
originIntentAddress,
|
|
1645
|
+
mode,
|
|
1646
|
+
fundMethod,
|
|
1647
|
+
originChainId,
|
|
1648
|
+
destinationChainId,
|
|
1649
|
+
originTokenSymbol,
|
|
1650
|
+
originTokenAddress,
|
|
1651
|
+
destinationTokenAddress,
|
|
1652
|
+
destinationTokenSymbol,
|
|
1653
|
+
})
|
|
1654
|
+
|
|
1655
|
+
// Call onCheckoutError callback if provided
|
|
1656
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1657
|
+
checkoutOnHandlers.triggerCheckoutError(
|
|
1658
|
+
"Payment transactions did not complete successfully",
|
|
1659
|
+
)
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
return {
|
|
1664
|
+
depositUserTxnReceipt,
|
|
1665
|
+
originMetaTxnReceipt,
|
|
1666
|
+
destinationMetaTxnReceipt,
|
|
1667
|
+
totalCompletionSeconds: await getTxTimeDiff(
|
|
1668
|
+
transactionStates[0],
|
|
1669
|
+
transactionStates[2],
|
|
1670
|
+
),
|
|
1671
|
+
}
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
const errorMessage =
|
|
1674
|
+
error instanceof Error
|
|
1675
|
+
? error.message
|
|
1676
|
+
: "Unknown error occurred during transaction"
|
|
1677
|
+
logger.console.error(
|
|
1678
|
+
"[trails-sdk] Error in sendHandlerForDifferentChainDifferentToken:",
|
|
1679
|
+
error,
|
|
1680
|
+
)
|
|
1681
|
+
|
|
1682
|
+
// Track payment error
|
|
1683
|
+
trackPaymentError({
|
|
1684
|
+
error: errorMessage,
|
|
1685
|
+
userAddress: account.address,
|
|
1686
|
+
originIntentAddress,
|
|
1687
|
+
mode,
|
|
1688
|
+
fundMethod,
|
|
1689
|
+
originChainId,
|
|
1690
|
+
destinationChainId,
|
|
1691
|
+
originTokenSymbol,
|
|
1692
|
+
originTokenAddress,
|
|
1693
|
+
destinationTokenAddress,
|
|
1694
|
+
destinationTokenSymbol,
|
|
1695
|
+
})
|
|
1696
|
+
|
|
1697
|
+
// Call onCheckoutError callback if provided
|
|
1698
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1699
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1700
|
+
}
|
|
1701
|
+
|
|
1702
|
+
// Re-throw the error so caller can handle if needed
|
|
1703
|
+
throw error
|
|
1704
|
+
}
|
|
1705
|
+
},
|
|
1706
|
+
}
|
|
1707
|
+
}
|