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
package/src/prepareSend.ts
CHANGED
|
@@ -1,368 +1,57 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
} from "@0xsequence/trails-api"
|
|
6
|
-
import type { TrailsAPIClient } from "@0xsequence/trails-api"
|
|
7
|
-
import type { Relayer } from "@0xsequence/relayer"
|
|
8
|
-
import { useQuery } from "@tanstack/react-query"
|
|
9
|
-
import type {
|
|
10
|
-
Account,
|
|
11
|
-
Chain,
|
|
12
|
-
PublicClient,
|
|
13
|
-
TransactionReceipt,
|
|
14
|
-
WalletClient,
|
|
15
|
-
} from "viem"
|
|
16
|
-
import {
|
|
17
|
-
createPublicClient,
|
|
18
|
-
createWalletClient,
|
|
19
|
-
decodeFunctionData,
|
|
20
|
-
erc20Abi,
|
|
21
|
-
formatUnits,
|
|
22
|
-
http,
|
|
23
|
-
maxUint256,
|
|
24
|
-
parseUnits,
|
|
25
|
-
zeroAddress,
|
|
26
|
-
} from "viem"
|
|
27
|
-
import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"
|
|
28
|
-
import { mainnet } from "viem/chains"
|
|
29
|
-
import {
|
|
30
|
-
trackPaymentCompleted,
|
|
31
|
-
trackPaymentError,
|
|
32
|
-
trackPaymentStarted,
|
|
33
|
-
trackRelayerCallCompleted,
|
|
34
|
-
trackRelayerCallError,
|
|
35
|
-
trackRelayerCallStarted,
|
|
36
|
-
trackTransactionConfirmed,
|
|
37
|
-
} from "./analytics.js"
|
|
38
|
-
import { useAPIClient } from "./apiClient.js"
|
|
39
|
-
import type { Attestation } from "./cctp.js"
|
|
40
|
-
import {
|
|
41
|
-
approveERC20,
|
|
42
|
-
cctpTransfer,
|
|
43
|
-
cctpTransferWithCustomCall,
|
|
44
|
-
getCCTPRelayerCallData,
|
|
45
|
-
getIsUsdcAddress,
|
|
46
|
-
getMessageTransmitter,
|
|
47
|
-
getMintUSDCData,
|
|
48
|
-
getNeedsApproval,
|
|
49
|
-
getUSDCTokenAddress,
|
|
50
|
-
} from "./cctp.js"
|
|
51
|
-
import { queueCCTPTransfer } from "./cctpqueue.js"
|
|
52
|
-
import { getChainInfo, getTestnetChainInfo } from "./chains.js"
|
|
53
|
-
import { attemptSwitchChain } from "./chainSwitch.js"
|
|
54
|
-
import {
|
|
55
|
-
getSequenceEnv,
|
|
56
|
-
getSlippageTolerance,
|
|
57
|
-
type SequenceEnv,
|
|
58
|
-
} from "./config.js"
|
|
59
|
-
import { TRAILS_INTENT_ENTRYPOINT_ADDRESS } from "./constants.js"
|
|
60
|
-
import {
|
|
61
|
-
decodeGuestModuleEvents,
|
|
62
|
-
decodeTrailsTokenSweeperEvents,
|
|
63
|
-
} from "./decoders.js"
|
|
1
|
+
import { abortControllerRegistry } from "./abortController.js"
|
|
2
|
+
import { createPublicClient, http, zeroAddress } from "viem"
|
|
3
|
+
import { trackPaymentError, trackPaymentStarted } from "./analytics.js"
|
|
4
|
+
import { getSlippageTolerance } from "./config.js"
|
|
64
5
|
import { getERC20TransferData } from "./encoders.js"
|
|
65
|
-
import {
|
|
66
|
-
import {
|
|
67
|
-
estimateGasCost,
|
|
68
|
-
estimateGasCostUsd,
|
|
69
|
-
estimateGasLimit,
|
|
70
|
-
DEFAULT_MIN_GASLIMIT,
|
|
71
|
-
} from "./estimate.js"
|
|
72
|
-
import { getExplorerUrl } from "./explorer.js"
|
|
73
|
-
import {
|
|
74
|
-
getNeedsIntentEntrypointApproval,
|
|
75
|
-
getPermitSignature,
|
|
76
|
-
getUserNonce,
|
|
77
|
-
signIntent,
|
|
78
|
-
} from "./gasless.js"
|
|
79
|
-
import { getIntentEntrypointFeeOptions } from "./intentEntrypoint.js"
|
|
80
|
-
import { useIndexerGatewayClient } from "./indexerClient.js"
|
|
81
|
-
import {
|
|
82
|
-
buildSameChainTransactionParams,
|
|
83
|
-
buildCrossChainDepositParams,
|
|
84
|
-
commitIntentConfig,
|
|
85
|
-
getIntentCallsPayloads as getIntentCallsPayloadsFromIntents,
|
|
86
|
-
sendOriginTransaction,
|
|
87
|
-
} from "./intents.js"
|
|
88
|
-
import type { IntentRequestParams } from "./intents.js"
|
|
89
|
-
import type { MetaTxn } from "./metaTxnMonitor.js"
|
|
90
|
-
import { getMetaTxStatus } from "./metaTxnMonitor.js"
|
|
91
|
-
import { relayerSendMetaTx } from "./metaTxns.js"
|
|
92
|
-
import { findFirstPreconditionForChainId } from "./preconditions.js"
|
|
93
|
-
import { calcAmountUsdPrice, getTokenPrice } from "./prices.js"
|
|
94
|
-
import { getQueryParam } from "./queryParams.js"
|
|
95
|
-
import type { MetaTxnReceipt, RelayerEnv } from "./relayer.js"
|
|
96
|
-
import { useRelayers } from "./relayer.js"
|
|
97
|
-
import {
|
|
98
|
-
executeSimpleRelayTransaction,
|
|
99
|
-
getRelaySDKQuote,
|
|
100
|
-
getTxHashFromRelayResult,
|
|
101
|
-
waitForRelayDestinationTx,
|
|
102
|
-
type RelayQuote,
|
|
103
|
-
type RelayTradeType,
|
|
104
|
-
} from "./relaySdk.js"
|
|
105
|
-
import {
|
|
106
|
-
sequenceSendTransaction,
|
|
107
|
-
simpleCreateSequenceWallet,
|
|
108
|
-
} from "./sequenceWallet.js"
|
|
109
|
-
import {
|
|
110
|
-
formatAmount,
|
|
111
|
-
formatAmountDisplay,
|
|
112
|
-
formatRawAmount,
|
|
113
|
-
formatUsdAmountDisplay,
|
|
114
|
-
getTokenBalancesWithPrices,
|
|
115
|
-
} from "./tokenBalances.js"
|
|
116
|
-
import {
|
|
117
|
-
getTokenInfo,
|
|
118
|
-
useSupportedTokens,
|
|
119
|
-
type SupportedToken,
|
|
120
|
-
} from "./tokens.js"
|
|
6
|
+
import { getTokenPrice } from "./prices.js"
|
|
121
7
|
import {
|
|
122
8
|
TRAILS_ROUTER_PLACEHOLDER_AMOUNT,
|
|
123
9
|
wrapCalldataWithTrailsRouterIfNeeded,
|
|
124
10
|
} from "./trailsRouter.js"
|
|
125
|
-
import type {
|
|
126
|
-
TransactionState,
|
|
127
|
-
TransactionStateStatus,
|
|
128
|
-
} from "./transactions.js"
|
|
129
|
-
import { getAccountTransactionHistory, getTxTimeDiff } from "./transactions.js"
|
|
130
|
-
import { requestWithTimeout } from "./utils.js"
|
|
131
|
-
import type { CheckoutOnHandlers } from "./widget/hooks/useCheckout.js"
|
|
11
|
+
import type { TransactionState } from "./transactions.js"
|
|
132
12
|
import { logger } from "./logger.js"
|
|
133
13
|
import { getIsCustomCalldata } from "./contractUtils.js"
|
|
134
|
-
import
|
|
135
|
-
import { useTrailsClient } from "./trailsClient.js"
|
|
136
|
-
import { updatePersistentToast } from "./toast.js"
|
|
137
|
-
import {
|
|
138
|
-
getDelegatorSmartAccount,
|
|
139
|
-
getPaymasterGaslessTransaction,
|
|
140
|
-
sendPaymasterGaslessTransaction,
|
|
141
|
-
} from "./paymasterSend.js"
|
|
142
|
-
|
|
143
|
-
export enum TradeType {
|
|
144
|
-
EXACT_INPUT = "EXACT_INPUT",
|
|
145
|
-
EXACT_OUTPUT = "EXACT_OUTPUT",
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
export type PrepareSendOptions = {
|
|
149
|
-
account: Account
|
|
150
|
-
originTokenAddress: string
|
|
151
|
-
originChainId: number
|
|
152
|
-
originTokenBalance: string
|
|
153
|
-
destinationChainId: number
|
|
154
|
-
recipient: string
|
|
155
|
-
destinationTokenAddress: string
|
|
156
|
-
swapAmount: string
|
|
157
|
-
tradeType?: TradeType
|
|
158
|
-
originTokenSymbol: string
|
|
159
|
-
destinationTokenSymbol: string
|
|
160
|
-
sequenceProjectAccessKey?: string
|
|
161
|
-
fee: string
|
|
162
|
-
client?: WalletClient
|
|
163
|
-
dryMode: boolean
|
|
164
|
-
apiClient: SequenceAPIClient
|
|
165
|
-
trailsClient: TrailsAPIClient
|
|
166
|
-
originRelayer: Relayer.RpcRelayer
|
|
167
|
-
destinationRelayer: Relayer.RpcRelayer
|
|
168
|
-
destinationCalldata?: string
|
|
169
|
-
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
170
|
-
sourceTokenPriceUsd?: number | null
|
|
171
|
-
destinationTokenPriceUsd?: number | null
|
|
172
|
-
sourceTokenDecimals: number
|
|
173
|
-
destinationTokenDecimals: number
|
|
174
|
-
paymasterUrl?: string
|
|
175
|
-
gasless?: boolean
|
|
176
|
-
slippageTolerance?: string
|
|
177
|
-
originNativeTokenPriceUsd?: number | null
|
|
178
|
-
quoteProvider?: string | null
|
|
179
|
-
fundMethod?: string
|
|
180
|
-
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
181
|
-
checkoutOnHandlers?: CheckoutOnHandlers
|
|
182
|
-
refundAddress?: string
|
|
183
|
-
selectedFeeToken?: any
|
|
184
|
-
walletId?: string
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
export type PrepareSendFees = {
|
|
188
|
-
feeTokenAddress: string | null
|
|
189
|
-
totalFeeAmount: string | null
|
|
190
|
-
totalFeeAmountUsd: string | null
|
|
191
|
-
totalFeeAmountUsdDisplay: string | null
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
export type PrepareSendQuote = {
|
|
195
|
-
originDepositAddress: string
|
|
196
|
-
destinationDepositAddress: string
|
|
197
|
-
destinationAddress: string
|
|
198
|
-
destinationCalldata: string
|
|
199
|
-
originChain: Chain
|
|
200
|
-
destinationChain: Chain
|
|
201
|
-
originAmount: string
|
|
202
|
-
originAmountDisplay: string
|
|
203
|
-
originAmountMin: string
|
|
204
|
-
originAmountMinDisplay: string
|
|
205
|
-
originAmountMinUsdFormatted: string
|
|
206
|
-
originAmountMinUsdDisplay: string
|
|
207
|
-
destinationAmount: string
|
|
208
|
-
destinationAmountDisplay: string
|
|
209
|
-
destinationAmountMin: string
|
|
210
|
-
destinationAmountMinDisplay: string
|
|
211
|
-
destinationAmountMinUsdFormatted: string
|
|
212
|
-
destinationAmountMinUsdDisplay: string
|
|
213
|
-
originAmountFormatted: string
|
|
214
|
-
originAmountUsdFormatted: string
|
|
215
|
-
originAmountUsdDisplay: string
|
|
216
|
-
destinationAmountFormatted: string
|
|
217
|
-
destinationAmountUsdFormatted: string
|
|
218
|
-
destinationAmountUsdDisplay: string
|
|
219
|
-
originToken: SupportedToken
|
|
220
|
-
destinationToken: SupportedToken
|
|
221
|
-
fees: PrepareSendFees
|
|
222
|
-
slippageTolerance: string
|
|
223
|
-
priceImpact: string
|
|
224
|
-
priceImpactUsdDisplay: string
|
|
225
|
-
completionEstimateSeconds: number
|
|
226
|
-
transactionStates: TransactionState[]
|
|
227
|
-
gasCostUsd: number
|
|
228
|
-
gasCostUsdDisplay: string
|
|
229
|
-
gasCost: string
|
|
230
|
-
gasCostFormatted: string
|
|
231
|
-
originTokenRate: string
|
|
232
|
-
destinationTokenRate: string
|
|
233
|
-
quoteProvider: QuoteProviderInfo | null
|
|
234
|
-
noSufficientBalance: boolean
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
export type PrepareSendReturn = {
|
|
238
|
-
quote: PrepareSendQuote
|
|
239
|
-
feeOptions?: any
|
|
240
|
-
send: ({
|
|
241
|
-
onOriginSend,
|
|
242
|
-
selectedFeeToken,
|
|
243
|
-
}: {
|
|
244
|
-
onOriginSend?: () => void
|
|
245
|
-
selectedFeeToken?: any
|
|
246
|
-
}) => Promise<SendReturn>
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
export type SendReturn = {
|
|
250
|
-
originUserTxReceipt: TransactionReceipt | null
|
|
251
|
-
originMetaTxnReceipt: MetaTxnReceipt | null
|
|
252
|
-
destinationMetaTxnReceipt: MetaTxnReceipt | null
|
|
253
|
-
totalCompletionSeconds?: number
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
export function getIsToSameChain(
|
|
257
|
-
originChainId: number,
|
|
258
|
-
destinationChainId: number,
|
|
259
|
-
): boolean {
|
|
260
|
-
return originChainId?.toString() === destinationChainId?.toString()
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
export function getIsToSameToken(
|
|
264
|
-
originTokenAddress: string,
|
|
265
|
-
destinationTokenAddress: string,
|
|
266
|
-
): boolean {
|
|
267
|
-
return (
|
|
268
|
-
originTokenAddress?.toLowerCase() === destinationTokenAddress?.toLowerCase()
|
|
269
|
-
)
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export function getIsToSameChainAndToken(
|
|
273
|
-
originChainId: number,
|
|
274
|
-
originTokenAddress: string,
|
|
275
|
-
destinationChainId: number,
|
|
276
|
-
destinationTokenAddress: string,
|
|
277
|
-
): boolean {
|
|
278
|
-
return (
|
|
279
|
-
getIsToSameChain(originChainId, destinationChainId) &&
|
|
280
|
-
getIsToSameToken(originTokenAddress, destinationTokenAddress)
|
|
281
|
-
)
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
export function getUseCctp(
|
|
285
|
-
originTokenAddress: string,
|
|
286
|
-
destinationTokenAddress: string,
|
|
287
|
-
originChainId: number,
|
|
288
|
-
destinationChainId: number,
|
|
289
|
-
) {
|
|
290
|
-
return (
|
|
291
|
-
getIsUsdcAddress(originTokenAddress, originChainId) &&
|
|
292
|
-
getIsUsdcAddress(destinationTokenAddress, destinationChainId)
|
|
293
|
-
)
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
export function validateCctpDestinationToken(
|
|
297
|
-
destinationTokenAddress: string,
|
|
298
|
-
destinationChainId: number,
|
|
299
|
-
quoteProvider?: string | null,
|
|
300
|
-
): void {
|
|
301
|
-
// If CCTP provider is selected, destination token must be USDC
|
|
302
|
-
if (
|
|
303
|
-
quoteProvider === "cctp" &&
|
|
304
|
-
!getIsUsdcAddress(destinationTokenAddress, destinationChainId)
|
|
305
|
-
) {
|
|
306
|
-
throw new Error(
|
|
307
|
-
`CCTP provider requires destination token to be USDC. Current destination token: ${destinationTokenAddress} on chain ${destinationChainId}. Please ensure the destination token is USDC as listed in the Circle CCTP documentation.`,
|
|
308
|
-
)
|
|
309
|
-
}
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
function isTestnetDebugMode(): boolean {
|
|
313
|
-
return getQueryParam("testnet") === "true"
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
function getTestnetOriginTokenAddress(testnetChainId: number): string {
|
|
317
|
-
return getUSDCTokenAddress(testnetChainId)!
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
function getIntentArgs(
|
|
321
|
-
mainSignerAddress: string,
|
|
322
|
-
originChainId: number,
|
|
323
|
-
originTokenAddress: string,
|
|
324
|
-
originTokenAmount: string,
|
|
325
|
-
destinationChainId: number,
|
|
326
|
-
destinationTokenAddress: string,
|
|
327
|
-
destinationTokenAmount: string,
|
|
328
|
-
destinationTokenSymbol: string,
|
|
329
|
-
recipient: string,
|
|
330
|
-
destinationCalldata: string | undefined,
|
|
331
|
-
destinationSalt: string = Date.now().toString(),
|
|
332
|
-
slippageTolerance: string, // 0.03 = 3%
|
|
333
|
-
tradeType: TradeType,
|
|
334
|
-
provider?: string | null,
|
|
335
|
-
): GetIntentCallsPayloadParams {
|
|
336
|
-
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
337
|
-
|
|
338
|
-
if (!provider || provider === "auto") {
|
|
339
|
-
provider = undefined
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const intentArgs = {
|
|
343
|
-
userAddress: mainSignerAddress,
|
|
344
|
-
originChainId,
|
|
345
|
-
originTokenAddress,
|
|
346
|
-
originTokenAmount: originTokenAmount, // max amount for exact_output
|
|
347
|
-
destinationChainId,
|
|
348
|
-
destinationToAddress: recipient,
|
|
349
|
-
destinationTokenAddress: destinationTokenAddress,
|
|
350
|
-
destinationTokenAmount: destinationTokenAmount,
|
|
351
|
-
destinationTokenSymbol: destinationTokenSymbol,
|
|
352
|
-
destinationCallData: hasCustomCalldata ? destinationCalldata : "0x",
|
|
353
|
-
destinationCallValue: "0",
|
|
354
|
-
destinationSalt,
|
|
355
|
-
slippageTolerance: Number(slippageTolerance),
|
|
356
|
-
tradeType,
|
|
357
|
-
provider,
|
|
358
|
-
}
|
|
14
|
+
import { getChainInfo } from "./chains.js"
|
|
359
15
|
|
|
360
|
-
|
|
361
|
-
|
|
16
|
+
// Import from transactionIntent module
|
|
17
|
+
import {
|
|
18
|
+
TradeType,
|
|
19
|
+
type PrepareSendOptions,
|
|
20
|
+
type PrepareSendReturn,
|
|
21
|
+
handleSameChainSameToken,
|
|
22
|
+
handleCrossChain,
|
|
23
|
+
isSameChain,
|
|
24
|
+
isSameToken,
|
|
25
|
+
validateCctpDestinationToken,
|
|
26
|
+
} from "./transactionIntent/index.js"
|
|
27
|
+
|
|
28
|
+
// Re-export types for backward compatibility
|
|
29
|
+
export { TradeType } from "./transactionIntent/index.js"
|
|
30
|
+
export type {
|
|
31
|
+
PrepareSendOptions,
|
|
32
|
+
PrepareSendFees,
|
|
33
|
+
PrepareSendQuote,
|
|
34
|
+
PrepareSendReturn,
|
|
35
|
+
SendReturn,
|
|
36
|
+
SelectedFeeToken,
|
|
37
|
+
} from "./transactionIntent/index.js"
|
|
38
|
+
|
|
39
|
+
// Re-export validators for backward compatibility
|
|
40
|
+
export {
|
|
41
|
+
isSameChain,
|
|
42
|
+
isSameToken,
|
|
43
|
+
validateCctpDestinationToken,
|
|
44
|
+
} from "./transactionIntent/index.js"
|
|
362
45
|
|
|
363
46
|
export async function prepareSend(
|
|
364
47
|
options: PrepareSendOptions,
|
|
365
48
|
): Promise<PrepareSendReturn> {
|
|
49
|
+
// Abort all existing operations when a new quote is generated
|
|
50
|
+
logger.console.log(
|
|
51
|
+
"[trails-sdk] New quote generated - aborting all existing operations",
|
|
52
|
+
)
|
|
53
|
+
abortControllerRegistry.abortAll()
|
|
54
|
+
|
|
366
55
|
const {
|
|
367
56
|
account,
|
|
368
57
|
originTokenAddress,
|
|
@@ -378,16 +67,12 @@ export async function prepareSend(
|
|
|
378
67
|
fee,
|
|
379
68
|
client: walletClient,
|
|
380
69
|
dryMode = false,
|
|
381
|
-
apiClient,
|
|
382
70
|
trailsClient,
|
|
383
|
-
originRelayer,
|
|
384
|
-
destinationRelayer,
|
|
385
71
|
destinationCalldata,
|
|
386
72
|
onTransactionStateChange,
|
|
387
73
|
sourceTokenDecimals,
|
|
388
74
|
destinationTokenDecimals,
|
|
389
75
|
paymasterUrl,
|
|
390
|
-
gasless = false,
|
|
391
76
|
slippageTolerance = getSlippageTolerance(),
|
|
392
77
|
originNativeTokenPriceUsd,
|
|
393
78
|
quoteProvider,
|
|
@@ -396,6 +81,9 @@ export async function prepareSend(
|
|
|
396
81
|
checkoutOnHandlers,
|
|
397
82
|
selectedFeeToken,
|
|
398
83
|
walletId,
|
|
84
|
+
abortSignal,
|
|
85
|
+
commitIntentFn,
|
|
86
|
+
executeIntentFn,
|
|
399
87
|
} = options
|
|
400
88
|
let { sourceTokenPriceUsd, destinationTokenPriceUsd } = options
|
|
401
89
|
|
|
@@ -464,12 +152,12 @@ export async function prepareSend(
|
|
|
464
152
|
originTokenAddress,
|
|
465
153
|
originChainId,
|
|
466
154
|
})
|
|
467
|
-
const price = await getTokenPrice(
|
|
468
|
-
|
|
469
|
-
|
|
155
|
+
const price = await getTokenPrice(trailsClient, {
|
|
156
|
+
tokenSymbol: originTokenAddress,
|
|
157
|
+
tokenAddress: originTokenAddress,
|
|
470
158
|
chainId: originChainId,
|
|
471
159
|
})
|
|
472
|
-
sourceTokenPriceUsd = price?.
|
|
160
|
+
sourceTokenPriceUsd = price?.priceUsd ?? 0
|
|
473
161
|
logger.console.log(
|
|
474
162
|
"[trails-sdk] source token price:",
|
|
475
163
|
sourceTokenPriceUsd,
|
|
@@ -488,12 +176,12 @@ export async function prepareSend(
|
|
|
488
176
|
destinationTokenAddress,
|
|
489
177
|
destinationChainId,
|
|
490
178
|
})
|
|
491
|
-
const price = await getTokenPrice(
|
|
492
|
-
|
|
493
|
-
|
|
179
|
+
const price = await getTokenPrice(trailsClient, {
|
|
180
|
+
tokenSymbol: destinationTokenAddress,
|
|
181
|
+
tokenAddress: destinationTokenAddress,
|
|
494
182
|
chainId: destinationChainId,
|
|
495
183
|
})
|
|
496
|
-
destinationTokenPriceUsd = price?.
|
|
184
|
+
destinationTokenPriceUsd = price?.priceUsd ?? 0
|
|
497
185
|
logger.console.log(
|
|
498
186
|
"[trails-sdk] destination token price:",
|
|
499
187
|
destinationTokenPriceUsd,
|
|
@@ -556,11 +244,8 @@ export async function prepareSend(
|
|
|
556
244
|
})
|
|
557
245
|
throw new Error(`Chain ${originChainId} not found`)
|
|
558
246
|
}
|
|
559
|
-
const isToSameChain =
|
|
560
|
-
const isToSameToken =
|
|
561
|
-
originTokenAddress,
|
|
562
|
-
destinationTokenAddress,
|
|
563
|
-
)
|
|
247
|
+
const isToSameChain = isSameChain(originChainId, destinationChainId)
|
|
248
|
+
const isToSameToken = isSameToken(originTokenAddress, destinationTokenAddress)
|
|
564
249
|
|
|
565
250
|
logger.console.log("[trails-sdk] isToSameChain", isToSameChain)
|
|
566
251
|
logger.console.log("[trails-sdk] isToSameToken", isToSameToken)
|
|
@@ -646,15 +331,17 @@ export async function prepareSend(
|
|
|
646
331
|
// }
|
|
647
332
|
|
|
648
333
|
if (isToSameToken && isToSameChain) {
|
|
649
|
-
return await
|
|
334
|
+
return await handleSameChainSameToken({
|
|
335
|
+
mainSignerAddress,
|
|
650
336
|
originTokenAddress,
|
|
651
337
|
originTokenDecimals: sourceTokenDecimals,
|
|
338
|
+
originTokenSymbol,
|
|
339
|
+
destinationTokenSymbol,
|
|
652
340
|
swapAmount,
|
|
653
341
|
destinationCalldata: effectiveDestinationCalldata,
|
|
654
342
|
recipient: effectiveDestinationAddress,
|
|
655
343
|
originChainId,
|
|
656
344
|
walletClient,
|
|
657
|
-
publicClient,
|
|
658
345
|
onTransactionStateChange,
|
|
659
346
|
dryMode,
|
|
660
347
|
account,
|
|
@@ -663,19 +350,21 @@ export async function prepareSend(
|
|
|
663
350
|
sourceTokenPriceUsd,
|
|
664
351
|
destinationTokenPriceUsd,
|
|
665
352
|
originNativeTokenPriceUsd,
|
|
666
|
-
slippageTolerance,
|
|
353
|
+
slippageTolerance: slippageTolerance,
|
|
667
354
|
checkoutOnHandlers,
|
|
668
|
-
gasless,
|
|
669
355
|
paymasterUrl,
|
|
670
356
|
selectedFeeToken,
|
|
357
|
+
walletId,
|
|
671
358
|
trailsClient,
|
|
672
|
-
originRelayer,
|
|
673
359
|
mode,
|
|
674
360
|
fundMethod,
|
|
361
|
+
abortSignal,
|
|
362
|
+
commitIntentFn,
|
|
363
|
+
executeIntentFn,
|
|
675
364
|
})
|
|
676
365
|
}
|
|
677
366
|
|
|
678
|
-
return await
|
|
367
|
+
return await handleCrossChain({
|
|
679
368
|
mainSignerAddress,
|
|
680
369
|
originChainId,
|
|
681
370
|
originTokenAddress,
|
|
@@ -687,16 +376,12 @@ export async function prepareSend(
|
|
|
687
376
|
destinationTokenSymbol,
|
|
688
377
|
recipient: effectiveDestinationAddress,
|
|
689
378
|
destinationCalldata: effectiveDestinationCalldata,
|
|
690
|
-
apiClient,
|
|
691
379
|
trailsClient,
|
|
692
380
|
sourceTokenPriceUsd,
|
|
693
381
|
destinationTokenPriceUsd,
|
|
694
382
|
sourceTokenDecimals,
|
|
695
383
|
destinationTokenDecimals,
|
|
696
|
-
gasless,
|
|
697
384
|
paymasterUrl,
|
|
698
|
-
originRelayer,
|
|
699
|
-
destinationRelayer,
|
|
700
385
|
walletClient,
|
|
701
386
|
publicClient,
|
|
702
387
|
chain,
|
|
@@ -714,4325 +399,33 @@ export async function prepareSend(
|
|
|
714
399
|
checkoutOnHandlers,
|
|
715
400
|
selectedFeeToken,
|
|
716
401
|
walletId,
|
|
402
|
+
abortSignal,
|
|
403
|
+
commitIntentFn,
|
|
404
|
+
executeIntentFn,
|
|
717
405
|
})
|
|
718
406
|
}
|
|
719
407
|
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
account,
|
|
745
|
-
fee,
|
|
746
|
-
dryMode,
|
|
747
|
-
onTransactionStateChange,
|
|
748
|
-
transactionStates,
|
|
749
|
-
slippageTolerance,
|
|
750
|
-
tradeType,
|
|
751
|
-
originNativeTokenPriceUsd,
|
|
752
|
-
quoteProvider,
|
|
753
|
-
fundMethod,
|
|
754
|
-
mode,
|
|
755
|
-
checkoutOnHandlers,
|
|
756
|
-
selectedFeeToken,
|
|
757
|
-
walletId,
|
|
758
|
-
}: {
|
|
759
|
-
mainSignerAddress: string
|
|
760
|
-
originChainId: number
|
|
761
|
-
originTokenAddress: string
|
|
762
|
-
originTokenBalance: string
|
|
763
|
-
destinationChainId: number
|
|
764
|
-
destinationTokenAddress: string
|
|
765
|
-
swapAmount: string
|
|
766
|
-
originTokenSymbol: string
|
|
767
|
-
destinationTokenSymbol: string
|
|
768
|
-
recipient: string
|
|
769
|
-
destinationCalldata?: string
|
|
770
|
-
apiClient: SequenceAPIClient
|
|
771
|
-
trailsClient: TrailsAPIClient
|
|
772
|
-
sourceTokenPriceUsd?: number | null
|
|
773
|
-
destinationTokenPriceUsd?: number | null
|
|
774
|
-
sourceTokenDecimals: number
|
|
775
|
-
destinationTokenDecimals: number
|
|
776
|
-
gasless: boolean
|
|
777
|
-
paymasterUrl?: string
|
|
778
|
-
originRelayer: Relayer.RpcRelayer
|
|
779
|
-
destinationRelayer: Relayer.RpcRelayer
|
|
780
|
-
walletClient: WalletClient
|
|
781
|
-
publicClient: PublicClient
|
|
782
|
-
chain: Chain
|
|
783
|
-
account: Account
|
|
784
|
-
fee: string
|
|
785
|
-
dryMode: boolean
|
|
786
|
-
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
787
|
-
transactionStates: TransactionState[]
|
|
788
|
-
slippageTolerance: string
|
|
789
|
-
tradeType: TradeType
|
|
790
|
-
originNativeTokenPriceUsd?: number | null
|
|
791
|
-
quoteProvider?: string | null
|
|
792
|
-
fundMethod?: string
|
|
793
|
-
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
794
|
-
checkoutOnHandlers?: CheckoutOnHandlers
|
|
795
|
-
selectedFeeToken?: any
|
|
796
|
-
walletId?: string
|
|
797
|
-
}): Promise<PrepareSendReturn> {
|
|
798
|
-
const testnet = isTestnetDebugMode()
|
|
799
|
-
const useCctp = getUseCctp(
|
|
800
|
-
originTokenAddress,
|
|
801
|
-
destinationTokenAddress,
|
|
802
|
-
originChainId,
|
|
803
|
-
destinationChainId,
|
|
804
|
-
)
|
|
805
|
-
|
|
806
|
-
const cctpFlag = getQueryParam("cctp") === "true"
|
|
807
|
-
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
808
|
-
|
|
809
|
-
// Validate CCTP destination token requirement for explicit CCTP usage
|
|
810
|
-
if (useCctp && cctpFlag) {
|
|
811
|
-
validateCctpDestinationToken(
|
|
812
|
-
destinationTokenAddress,
|
|
813
|
-
destinationChainId,
|
|
814
|
-
"cctp",
|
|
815
|
-
)
|
|
816
|
-
logger.console.log("[trails-sdk] using cctp")
|
|
817
|
-
|
|
818
|
-
const quote = await getNormalizedQuoteObject({
|
|
819
|
-
destinationAddress: recipient,
|
|
820
|
-
destinationCalldata,
|
|
821
|
-
originAmount: swapAmount,
|
|
822
|
-
originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
|
|
823
|
-
destinationAmount: swapAmount,
|
|
824
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
|
|
825
|
-
originTokenAddress: originTokenAddress,
|
|
826
|
-
destinationTokenAddress: destinationTokenAddress,
|
|
827
|
-
originChainId,
|
|
828
|
-
destinationChainId,
|
|
829
|
-
transactionStates,
|
|
830
|
-
originNativeTokenPriceUsd,
|
|
831
|
-
slippageTolerance,
|
|
832
|
-
quoteProvider: "cctp",
|
|
833
|
-
})
|
|
834
|
-
|
|
835
|
-
// Call onCheckoutQuote callback if provided
|
|
836
|
-
if (checkoutOnHandlers?.triggerCheckoutQuote) {
|
|
837
|
-
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
return {
|
|
841
|
-
quote,
|
|
842
|
-
send: async ({
|
|
843
|
-
onOriginSend,
|
|
844
|
-
selectedFeeToken: runtimeSelectedFeeToken,
|
|
845
|
-
}: {
|
|
846
|
-
onOriginSend?: () => void
|
|
847
|
-
selectedFeeToken?: any
|
|
848
|
-
}): Promise<SendReturn> => {
|
|
849
|
-
// Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
|
|
850
|
-
const effectiveSelectedFeeToken =
|
|
851
|
-
runtimeSelectedFeeToken ?? selectedFeeToken
|
|
852
|
-
logger.console.log(
|
|
853
|
-
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called (LEGACY PATH):",
|
|
854
|
-
{
|
|
855
|
-
runtimeSelectedFeeToken,
|
|
856
|
-
prepareTimeSelectedFeeToken: selectedFeeToken,
|
|
857
|
-
effectiveSelectedFeeToken,
|
|
858
|
-
},
|
|
859
|
-
)
|
|
860
|
-
const originChain = testnet ? getTestnetChainInfo(chain)! : chain
|
|
861
|
-
const destinationChain = testnet
|
|
862
|
-
? getTestnetChainInfo(destinationChainId)!
|
|
863
|
-
: getChainInfo(destinationChainId)
|
|
864
|
-
|
|
865
|
-
if (!originChain || !destinationChain) {
|
|
866
|
-
logger.console.error("[trails-sdk] Invalid chain", {
|
|
867
|
-
originChain,
|
|
868
|
-
destinationChain,
|
|
869
|
-
originChainId,
|
|
870
|
-
destinationChainId,
|
|
871
|
-
chain,
|
|
872
|
-
testnet,
|
|
873
|
-
})
|
|
874
|
-
throw new Error("Invalid chain")
|
|
875
|
-
}
|
|
876
|
-
|
|
877
|
-
logger.console.log("[trails-sdk] originChain", originChain)
|
|
878
|
-
logger.console.log("[trails-sdk] destinationChain", destinationChain)
|
|
879
|
-
|
|
880
|
-
const originPublicClient = createPublicClient({
|
|
881
|
-
chain: originChain,
|
|
882
|
-
transport: http(),
|
|
883
|
-
})
|
|
884
|
-
|
|
885
|
-
let txHash: `0x${string}`
|
|
886
|
-
let waitForAttestation: () => Promise<Attestation>
|
|
887
|
-
|
|
888
|
-
if (hasCustomCalldata) {
|
|
889
|
-
const result = await cctpTransferWithCustomCall({
|
|
890
|
-
walletClient,
|
|
891
|
-
originChain,
|
|
892
|
-
destinationChain,
|
|
893
|
-
amount: BigInt(swapAmount),
|
|
894
|
-
})
|
|
895
|
-
|
|
896
|
-
txHash = result.txHash
|
|
897
|
-
waitForAttestation = result.waitForAttestation
|
|
898
|
-
} else {
|
|
899
|
-
const result = await cctpTransfer({
|
|
900
|
-
walletClient,
|
|
901
|
-
originChain,
|
|
902
|
-
destinationChain,
|
|
903
|
-
amount: BigInt(swapAmount),
|
|
904
|
-
})
|
|
905
|
-
|
|
906
|
-
txHash = result.txHash
|
|
907
|
-
waitForAttestation = result.waitForAttestation
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
if (onOriginSend) {
|
|
911
|
-
onOriginSend()
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
logger.console.log("[trails-sdk] waiting for tx", txHash)
|
|
915
|
-
|
|
916
|
-
const receipt = await originPublicClient.waitForTransactionReceipt({
|
|
917
|
-
hash: txHash,
|
|
918
|
-
})
|
|
919
|
-
|
|
920
|
-
logger.console.log("[trails-sdk] tx receipt", receipt)
|
|
921
|
-
|
|
922
|
-
transactionStates[0] = getTransactionStateFromReceipt(
|
|
923
|
-
receipt,
|
|
924
|
-
originChain.id,
|
|
925
|
-
transactionStates[0]?.label,
|
|
926
|
-
)
|
|
927
|
-
transactionStates[1] = getTransactionStateFromReceipt(
|
|
928
|
-
receipt,
|
|
929
|
-
originChain.id,
|
|
930
|
-
transactionStates[1]?.label,
|
|
931
|
-
)
|
|
932
|
-
|
|
933
|
-
onTransactionStateChange(transactionStates)
|
|
934
|
-
|
|
935
|
-
const attestation = await waitForAttestation()
|
|
936
|
-
|
|
937
|
-
if (!attestation) {
|
|
938
|
-
throw new Error("Failed to retrieve attestation")
|
|
939
|
-
}
|
|
940
|
-
|
|
941
|
-
const tokenMessenger = getMessageTransmitter(destinationChain.id)!
|
|
942
|
-
|
|
943
|
-
logger.console.log("[trails-sdk] tokenMessenger", tokenMessenger)
|
|
944
|
-
const calls: {
|
|
945
|
-
to: `0x${string}`
|
|
946
|
-
data: `0x${string}`
|
|
947
|
-
value: bigint
|
|
948
|
-
}[] = []
|
|
949
|
-
|
|
950
|
-
if (hasCustomCalldata) {
|
|
951
|
-
calls.push(
|
|
952
|
-
await getCCTPRelayerCallData({
|
|
953
|
-
attestation,
|
|
954
|
-
targetContract: recipient,
|
|
955
|
-
calldata: destinationCalldata as `0x${string}`,
|
|
956
|
-
gasLimit: 300000n,
|
|
957
|
-
destinationChain,
|
|
958
|
-
}),
|
|
959
|
-
)
|
|
960
|
-
} else {
|
|
961
|
-
calls.push(
|
|
962
|
-
await getMintUSDCData({
|
|
963
|
-
tokenMessenger,
|
|
964
|
-
attestation,
|
|
965
|
-
}),
|
|
966
|
-
)
|
|
967
|
-
}
|
|
968
|
-
|
|
969
|
-
logger.console.log("[trails-sdk] calls", calls)
|
|
970
|
-
const delegatorPrivateKey = generatePrivateKey()
|
|
971
|
-
const delegatorAccount = privateKeyToAccount(delegatorPrivateKey)
|
|
972
|
-
const delegatorClient = createWalletClient({
|
|
973
|
-
account: delegatorAccount,
|
|
974
|
-
chain: destinationChain,
|
|
975
|
-
transport: http(),
|
|
976
|
-
})
|
|
977
|
-
const destinationPublicClient = createPublicClient({
|
|
978
|
-
chain: destinationChain,
|
|
979
|
-
transport: http(),
|
|
980
|
-
})
|
|
981
|
-
logger.console.log("[trails-sdk] delegatorClient", delegatorClient)
|
|
982
|
-
|
|
983
|
-
const sequenceWalletAddress = await simpleCreateSequenceWallet(
|
|
984
|
-
delegatorAccount as any,
|
|
985
|
-
)
|
|
986
|
-
logger.console.log(
|
|
987
|
-
"[trails-sdk] sequenceWalletAddress",
|
|
988
|
-
sequenceWalletAddress,
|
|
989
|
-
)
|
|
990
|
-
const sequenceTxHash = await sequenceSendTransaction(
|
|
991
|
-
sequenceWalletAddress,
|
|
992
|
-
delegatorClient,
|
|
993
|
-
destinationPublicClient,
|
|
994
|
-
calls,
|
|
995
|
-
destinationChain,
|
|
996
|
-
)
|
|
997
|
-
|
|
998
|
-
const destinationReceipt =
|
|
999
|
-
await destinationPublicClient.waitForTransactionReceipt({
|
|
1000
|
-
hash: sequenceTxHash as `0x${string}`,
|
|
1001
|
-
})
|
|
1002
|
-
|
|
1003
|
-
logger.console.log(
|
|
1004
|
-
"[trails-sdk] destinationReceipt",
|
|
1005
|
-
destinationReceipt,
|
|
1006
|
-
)
|
|
1007
|
-
|
|
1008
|
-
if (transactionStates[2]) {
|
|
1009
|
-
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1010
|
-
destinationReceipt,
|
|
1011
|
-
destinationChain.id,
|
|
1012
|
-
transactionStates[2]?.label,
|
|
1013
|
-
)
|
|
1014
|
-
}
|
|
1015
|
-
|
|
1016
|
-
onTransactionStateChange(transactionStates)
|
|
1017
|
-
|
|
1018
|
-
return {
|
|
1019
|
-
originUserTxReceipt: receipt,
|
|
1020
|
-
originMetaTxnReceipt: null,
|
|
1021
|
-
destinationMetaTxnReceipt: null,
|
|
1022
|
-
totalCompletionSeconds: 0,
|
|
1023
|
-
}
|
|
1024
|
-
},
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
const destinationSalt = Date.now().toString()
|
|
1029
|
-
|
|
1030
|
-
const intentArgs = getIntentArgs(
|
|
1031
|
-
mainSignerAddress,
|
|
1032
|
-
originChainId,
|
|
1033
|
-
originTokenAddress,
|
|
1034
|
-
tradeType === TradeType.EXACT_OUTPUT ? originTokenBalance : swapAmount, // originTokenAmount
|
|
1035
|
-
destinationChainId,
|
|
1036
|
-
destinationTokenAddress,
|
|
1037
|
-
tradeType === TradeType.EXACT_OUTPUT ? swapAmount : "0", // destinationTokenAmount
|
|
1038
|
-
destinationTokenSymbol,
|
|
1039
|
-
recipient,
|
|
1040
|
-
destinationCalldata,
|
|
1041
|
-
destinationSalt,
|
|
1042
|
-
slippageTolerance,
|
|
1043
|
-
tradeType,
|
|
1044
|
-
quoteProvider,
|
|
1045
|
-
)
|
|
1046
|
-
|
|
1047
|
-
logger.console.log("[trails-sdk] Creating intent with args:", intentArgs)
|
|
1048
|
-
|
|
1049
|
-
const intent = await getIntentCallsPayloadsFromIntents(
|
|
1050
|
-
trailsClient,
|
|
1051
|
-
{
|
|
1052
|
-
params: intentArgs,
|
|
1053
|
-
},
|
|
1054
|
-
{
|
|
1055
|
-
originTokenSymbol,
|
|
1056
|
-
destinationTokenSymbol,
|
|
1057
|
-
feeTokenSymbol: originTokenSymbol,
|
|
1058
|
-
},
|
|
1059
|
-
)
|
|
1060
|
-
logger.console.log("[trails-sdk] Got intent:", intent)
|
|
1061
|
-
|
|
1062
|
-
if (
|
|
1063
|
-
!intent.payloads.preconditions?.length ||
|
|
1064
|
-
!intent.payloads.calls?.length
|
|
1065
|
-
) {
|
|
1066
|
-
throw new Error("Invalid intent")
|
|
1067
|
-
}
|
|
1068
|
-
|
|
1069
|
-
const originIntentAddress = intent.payloads.originIntentAddress
|
|
1070
|
-
logger.console.log(
|
|
1071
|
-
"[trails-sdk] origin intent address:",
|
|
1072
|
-
originIntentAddress.toString(),
|
|
1073
|
-
)
|
|
1074
|
-
|
|
1075
|
-
const firstPrecondition = findFirstPreconditionForChainId(
|
|
1076
|
-
intent.payloads.preconditions,
|
|
1077
|
-
originChainId,
|
|
1078
|
-
)
|
|
1079
|
-
|
|
1080
|
-
if (!firstPrecondition) {
|
|
1081
|
-
throw new Error("No precondition found for origin chain")
|
|
1082
|
-
}
|
|
1083
|
-
|
|
1084
|
-
const firstPreconditionMin = firstPrecondition?.data?.min?.toString()
|
|
1085
|
-
const depositAmount = firstPreconditionMin
|
|
1086
|
-
|
|
1087
|
-
const quoteToAmount = intent.payloads.quote.toAmount
|
|
1088
|
-
const quoteToAmountMin = intent.payloads.quote.toAmountMin
|
|
1089
|
-
|
|
1090
|
-
const originSendAmountFormatted = formatRawAmount(
|
|
1091
|
-
depositAmount,
|
|
1092
|
-
sourceTokenDecimals,
|
|
1093
|
-
)
|
|
1094
|
-
|
|
1095
|
-
const depositAmountUsd = calcAmountUsdPrice({
|
|
1096
|
-
amount: originSendAmountFormatted,
|
|
1097
|
-
usdPrice: sourceTokenPriceUsd,
|
|
1098
|
-
})
|
|
1099
|
-
|
|
1100
|
-
logger.console.log("[trails-sdk] depositAmountUsd", depositAmountUsd, {
|
|
1101
|
-
amount: originSendAmountFormatted,
|
|
1102
|
-
usdPrice: sourceTokenPriceUsd,
|
|
1103
|
-
originTokenSymbol,
|
|
1104
|
-
originSendAmountFormatted,
|
|
1105
|
-
})
|
|
1106
|
-
|
|
1107
|
-
const effectiveDestinationTokenAmount = quoteToAmount
|
|
1108
|
-
const effectiveDestinationTokenAmountFormatted = formatRawAmount(
|
|
1109
|
-
effectiveDestinationTokenAmount,
|
|
1110
|
-
destinationTokenDecimals,
|
|
1111
|
-
)
|
|
1112
|
-
|
|
1113
|
-
const effectiveDestinationTokenAmountUsd = calcAmountUsdPrice({
|
|
1114
|
-
amount: effectiveDestinationTokenAmountFormatted,
|
|
1115
|
-
usdPrice: destinationTokenPriceUsd,
|
|
1116
|
-
})
|
|
1117
|
-
|
|
1118
|
-
logger.console.log(
|
|
1119
|
-
"[trails-sdk] effectiveDestinationTokenAmountUsd",
|
|
1120
|
-
effectiveDestinationTokenAmountUsd,
|
|
1121
|
-
{
|
|
1122
|
-
amount: effectiveDestinationTokenAmountFormatted,
|
|
1123
|
-
usdPrice: destinationTokenPriceUsd,
|
|
1124
|
-
destinationTokenSymbol,
|
|
1125
|
-
effectiveDestinationTokenAmount,
|
|
1126
|
-
destinationTokenAddress,
|
|
1127
|
-
destinationChainId,
|
|
1128
|
-
},
|
|
1129
|
-
)
|
|
1130
|
-
|
|
1131
|
-
let noSufficientBalance = false
|
|
1132
|
-
|
|
1133
|
-
if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
|
|
1134
|
-
const { hasEnoughBalance } = await checkAccountBalance({
|
|
1135
|
-
account,
|
|
1136
|
-
tokenAddress: originTokenAddress,
|
|
1137
|
-
depositAmount,
|
|
1138
|
-
publicClient,
|
|
1139
|
-
})
|
|
1140
|
-
|
|
1141
|
-
if (!hasEnoughBalance) {
|
|
1142
|
-
noSufficientBalance = true
|
|
1143
|
-
}
|
|
1144
|
-
}
|
|
1145
|
-
|
|
1146
|
-
// Estimate gas limit for the quote (same logic as in attemptNonGaslessUserDeposit)
|
|
1147
|
-
const originCallParamsForEstimate = buildCrossChainDepositParams({
|
|
1148
|
-
originTokenAddress,
|
|
1149
|
-
originIntentAddress,
|
|
1150
|
-
depositAmount: firstPreconditionMin,
|
|
1151
|
-
fee,
|
|
1152
|
-
originChainId,
|
|
1153
|
-
chain,
|
|
1154
|
-
})
|
|
1155
|
-
|
|
1156
|
-
logger.console.log(
|
|
1157
|
-
"[trails-sdk][gas-estimation] About to estimate gas limit for cross-chain quote with params:",
|
|
1158
|
-
{
|
|
1159
|
-
account: account.address,
|
|
1160
|
-
to: originCallParamsForEstimate.to,
|
|
1161
|
-
data: originCallParamsForEstimate.data,
|
|
1162
|
-
value: originCallParamsForEstimate.value,
|
|
1163
|
-
},
|
|
1164
|
-
)
|
|
1165
|
-
|
|
1166
|
-
const estimatedGasLimitForQuote = await estimateGasLimit(
|
|
1167
|
-
publicClient,
|
|
1168
|
-
{
|
|
1169
|
-
account: account.address,
|
|
1170
|
-
to: originCallParamsForEstimate.to,
|
|
1171
|
-
data: originCallParamsForEstimate.data,
|
|
1172
|
-
value: BigInt(originCallParamsForEstimate.value),
|
|
1173
|
-
},
|
|
1174
|
-
"quote",
|
|
1175
|
-
)
|
|
1176
|
-
|
|
1177
|
-
logger.console.log(
|
|
1178
|
-
"[trails-sdk][gas-estimation] Estimated gas limit for cross-chain quote:",
|
|
1179
|
-
estimatedGasLimitForQuote,
|
|
1180
|
-
)
|
|
1181
|
-
|
|
1182
|
-
const quote = await getNormalizedQuoteObject({
|
|
1183
|
-
originDepositAddress: originIntentAddress,
|
|
1184
|
-
destinationDepositAddress: intent.payloads.destinationIntentAddress,
|
|
1185
|
-
destinationAddress: recipient,
|
|
1186
|
-
destinationCalldata,
|
|
1187
|
-
originAmount: depositAmount,
|
|
1188
|
-
destinationAmount: quoteToAmount,
|
|
1189
|
-
originAmountMin: depositAmount,
|
|
1190
|
-
destinationAmountMin: quoteToAmountMin,
|
|
1191
|
-
originTokenAddress: originTokenAddress,
|
|
1192
|
-
destinationTokenAddress: destinationTokenAddress,
|
|
1193
|
-
originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
|
|
1194
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
|
|
1195
|
-
fees: getFeesFromIntent(intent, {
|
|
1196
|
-
tradeType,
|
|
1197
|
-
fromAmountUsd: depositAmountUsd,
|
|
1198
|
-
toAmountUsd: effectiveDestinationTokenAmountUsd,
|
|
1199
|
-
}),
|
|
1200
|
-
originChainId,
|
|
1201
|
-
destinationChainId,
|
|
1202
|
-
slippageTolerance: slippageTolerance,
|
|
1203
|
-
priceImpact: getPriceImpactFromIntent(intent),
|
|
1204
|
-
priceImpactUsd: getPriceImpactUsdFromIntent(intent),
|
|
1205
|
-
transactionStates,
|
|
1206
|
-
originNativeTokenPriceUsd,
|
|
1207
|
-
quoteProvider: intent.payloads?.quote?.quoteProvider,
|
|
1208
|
-
noSufficientBalance,
|
|
1209
|
-
estimatedGasLimit: estimatedGasLimitForQuote,
|
|
1210
|
-
})
|
|
1211
|
-
|
|
1212
|
-
// Call onCheckoutQuote callback if provided
|
|
1213
|
-
if (checkoutOnHandlers?.triggerCheckoutQuote) {
|
|
1214
|
-
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
1215
|
-
}
|
|
1216
|
-
|
|
1217
|
-
// Get intent entrypoint fee options if supported and gasless is enabled
|
|
1218
|
-
// Note: We fetch fee options whenever gasless is true, regardless of fundMethod,
|
|
1219
|
-
// because gasless scenarios (including tests) need fee collector information
|
|
1220
|
-
// Skip gasless fee options for sequence-waas wallet
|
|
1221
|
-
let intentEntrypointFeeOptions: any = null
|
|
1222
|
-
if (gasless && walletId !== "sequence-waas") {
|
|
1223
|
-
try {
|
|
1224
|
-
logger.console.log(
|
|
1225
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled, checking intent entrypoint support for chain:",
|
|
1226
|
-
originChainId,
|
|
1227
|
-
)
|
|
1228
|
-
|
|
1229
|
-
intentEntrypointFeeOptions = await getIntentEntrypointFeeOptions({
|
|
1230
|
-
trailsClient,
|
|
1231
|
-
userAddress: mainSignerAddress as `0x${string}`,
|
|
1232
|
-
tokenAddress: originTokenAddress as `0x${string}`,
|
|
1233
|
-
amount: depositAmount,
|
|
1234
|
-
intentAddress: originIntentAddress as `0x${string}`,
|
|
1235
|
-
chainId: originChainId,
|
|
1236
|
-
})
|
|
1237
|
-
logger.console.log(
|
|
1238
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Intent entrypoint fee options:",
|
|
1239
|
-
intentEntrypointFeeOptions,
|
|
1240
|
-
)
|
|
1241
|
-
} catch (error) {
|
|
1242
|
-
logger.console.error(
|
|
1243
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Error getting intent entrypoint fee options:",
|
|
1244
|
-
error,
|
|
1245
|
-
)
|
|
1246
|
-
}
|
|
1247
|
-
} else if (walletId === "sequence-waas") {
|
|
1248
|
-
logger.console.log(
|
|
1249
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Skipping gasless fee options for sequence-waas wallet",
|
|
1250
|
-
)
|
|
1251
|
-
} else {
|
|
1252
|
-
logger.console.log(
|
|
1253
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled, skipping intent entrypoint fee options fetch",
|
|
1254
|
-
)
|
|
1255
|
-
}
|
|
1256
|
-
|
|
1257
|
-
return {
|
|
1258
|
-
quote,
|
|
1259
|
-
feeOptions: intentEntrypointFeeOptions,
|
|
1260
|
-
send: async ({
|
|
1261
|
-
onOriginSend,
|
|
1262
|
-
selectedFeeToken: runtimeSelectedFeeToken,
|
|
1263
|
-
}: {
|
|
1264
|
-
onOriginSend?: () => void
|
|
1265
|
-
selectedFeeToken?: any
|
|
1266
|
-
}): Promise<SendReturn> => {
|
|
1267
|
-
try {
|
|
1268
|
-
// Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
|
|
1269
|
-
const effectiveSelectedFeeToken =
|
|
1270
|
-
runtimeSelectedFeeToken ?? selectedFeeToken
|
|
1271
|
-
logger.console.log(
|
|
1272
|
-
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called with:",
|
|
1273
|
-
{
|
|
1274
|
-
runtimeSelectedFeeToken,
|
|
1275
|
-
prepareTimeSelectedFeeToken: selectedFeeToken,
|
|
1276
|
-
effectiveSelectedFeeToken,
|
|
1277
|
-
},
|
|
1278
|
-
)
|
|
1279
|
-
await commitIntentConfig(
|
|
1280
|
-
trailsClient,
|
|
1281
|
-
mainSignerAddress,
|
|
1282
|
-
intent.payloads.calls,
|
|
1283
|
-
intent.payloads.preconditions,
|
|
1284
|
-
{
|
|
1285
|
-
originTokenSymbol,
|
|
1286
|
-
destinationTokenSymbol,
|
|
1287
|
-
},
|
|
1288
|
-
intentArgs as IntentRequestParams,
|
|
1289
|
-
)
|
|
1290
|
-
|
|
1291
|
-
if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
|
|
1292
|
-
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
1293
|
-
account,
|
|
1294
|
-
tokenAddress: originTokenAddress,
|
|
1295
|
-
depositAmount,
|
|
1296
|
-
publicClient,
|
|
1297
|
-
})
|
|
1298
|
-
|
|
1299
|
-
if (!hasEnoughBalance) {
|
|
1300
|
-
throw balanceError
|
|
1301
|
-
}
|
|
1302
|
-
}
|
|
1303
|
-
|
|
1304
|
-
logger.console.log("[trails-sdk] sending origin transaction")
|
|
1305
|
-
const usingLIfi = false
|
|
1306
|
-
let needsNativeFee = false
|
|
1307
|
-
|
|
1308
|
-
if (usingLIfi) {
|
|
1309
|
-
needsNativeFee = getNeedsLifiNativeFee({
|
|
1310
|
-
originTokenAddress,
|
|
1311
|
-
destinationTokenAmount: swapAmount,
|
|
1312
|
-
destinationTokenDecimals,
|
|
1313
|
-
sourceTokenDecimals,
|
|
1314
|
-
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
1315
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
1316
|
-
depositAmount,
|
|
1317
|
-
})
|
|
1318
|
-
}
|
|
1319
|
-
|
|
1320
|
-
logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
|
|
1321
|
-
logger.console.log(
|
|
1322
|
-
"[trails-sdk] sourceTokenPriceUsd",
|
|
1323
|
-
sourceTokenPriceUsd,
|
|
1324
|
-
)
|
|
1325
|
-
logger.console.log(
|
|
1326
|
-
"[trails-sdk] destinationTokenPriceUsd",
|
|
1327
|
-
destinationTokenPriceUsd,
|
|
1328
|
-
)
|
|
1329
|
-
logger.console.log(
|
|
1330
|
-
"[trails-sdk] sourceTokenDecimals",
|
|
1331
|
-
sourceTokenDecimals,
|
|
1332
|
-
)
|
|
1333
|
-
logger.console.log(
|
|
1334
|
-
"[trails-sdk] destinationTokenDecimals",
|
|
1335
|
-
destinationTokenDecimals,
|
|
1336
|
-
)
|
|
1337
|
-
|
|
1338
|
-
let originUserTxReceipt: TransactionReceipt | null = null
|
|
1339
|
-
let originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
1340
|
-
let destinationMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
1341
|
-
|
|
1342
|
-
const testnet = isTestnetDebugMode()
|
|
1343
|
-
const effectiveOriginChain = testnet
|
|
1344
|
-
? getTestnetChainInfo(chain)!
|
|
1345
|
-
: chain
|
|
1346
|
-
const effectiveOriginTokenAddress = testnet
|
|
1347
|
-
? getTestnetOriginTokenAddress(effectiveOriginChain.id)
|
|
1348
|
-
: originTokenAddress
|
|
1349
|
-
|
|
1350
|
-
logger.console.log("[trails-sdk] testnet", testnet)
|
|
1351
|
-
|
|
1352
|
-
const destinationPublicClient = createPublicClient({
|
|
1353
|
-
chain: getChainInfo(destinationChainId)!,
|
|
1354
|
-
transport: http(),
|
|
1355
|
-
})
|
|
1356
|
-
|
|
1357
|
-
const depositPromise = async () => {
|
|
1358
|
-
logger.console.log(
|
|
1359
|
-
"[trails-sdk] depositPromise called - starting deposit transaction",
|
|
1360
|
-
)
|
|
1361
|
-
logger.console.log("[trails-sdk] fundMethod value:", fundMethod)
|
|
1362
|
-
|
|
1363
|
-
// Skip wallet deposit if fund method is qr-code
|
|
1364
|
-
if (fundMethod === "qr-code" || fundMethod === "exchange") {
|
|
1365
|
-
logger.console.log(
|
|
1366
|
-
"[trails-sdk] Skipping wallet deposit for QR code mode - fundMethod is:",
|
|
1367
|
-
fundMethod,
|
|
1368
|
-
)
|
|
1369
|
-
return
|
|
1370
|
-
}
|
|
1371
|
-
|
|
1372
|
-
logger.console.log(
|
|
1373
|
-
"[trails-sdk] Calling attemptUserDepositTx with params:",
|
|
1374
|
-
{
|
|
1375
|
-
originTokenAddress: effectiveOriginTokenAddress,
|
|
1376
|
-
gasless,
|
|
1377
|
-
paymasterUrl,
|
|
1378
|
-
chain: effectiveOriginChain.id,
|
|
1379
|
-
account: account.address,
|
|
1380
|
-
firstPreconditionMin,
|
|
1381
|
-
originIntentAddress,
|
|
1382
|
-
fee,
|
|
1383
|
-
dryMode,
|
|
1384
|
-
feeOptions: intentEntrypointFeeOptions,
|
|
1385
|
-
selectedFeeToken: effectiveSelectedFeeToken,
|
|
1386
|
-
selectedFeeTokenType: typeof effectiveSelectedFeeToken,
|
|
1387
|
-
selectedFeeTokenValue: JSON.stringify(effectiveSelectedFeeToken),
|
|
1388
|
-
},
|
|
1389
|
-
)
|
|
1390
|
-
|
|
1391
|
-
originUserTxReceipt = await attemptUserDepositTx({
|
|
1392
|
-
originTokenAddress: effectiveOriginTokenAddress,
|
|
1393
|
-
gasless,
|
|
1394
|
-
paymasterUrl,
|
|
1395
|
-
chain: effectiveOriginChain,
|
|
1396
|
-
account,
|
|
1397
|
-
originRelayer,
|
|
1398
|
-
firstPreconditionMin,
|
|
1399
|
-
originIntentAddress,
|
|
1400
|
-
onOriginSend,
|
|
1401
|
-
publicClient,
|
|
1402
|
-
walletClient,
|
|
1403
|
-
destinationTokenDecimals,
|
|
1404
|
-
sourceTokenDecimals,
|
|
1405
|
-
fee,
|
|
1406
|
-
dryMode,
|
|
1407
|
-
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
1408
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
1409
|
-
swapAmount,
|
|
1410
|
-
onTransactionStateChange,
|
|
1411
|
-
transactionStates,
|
|
1412
|
-
fundMethod,
|
|
1413
|
-
originTokenSymbol,
|
|
1414
|
-
destinationTokenSymbol,
|
|
1415
|
-
depositAmountUsd,
|
|
1416
|
-
feeOptions: intentEntrypointFeeOptions,
|
|
1417
|
-
trailsClient,
|
|
1418
|
-
selectedFeeToken: effectiveSelectedFeeToken,
|
|
1419
|
-
walletId,
|
|
1420
|
-
})
|
|
1421
|
-
|
|
1422
|
-
if (!originUserTxReceipt) {
|
|
1423
|
-
throw new Error("Failed to send origin transaction")
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1426
|
-
transactionStates[0] = getTransactionStateFromReceipt(
|
|
1427
|
-
originUserTxReceipt,
|
|
1428
|
-
originChainId,
|
|
1429
|
-
transactionStates[0]?.label,
|
|
1430
|
-
)
|
|
1431
|
-
|
|
1432
|
-
try {
|
|
1433
|
-
transactionStates[0].decodedTrailsTokenSweeperEvents =
|
|
1434
|
-
decodeTrailsTokenSweeperEvents(originUserTxReceipt)
|
|
1435
|
-
transactionStates[0].decodedGuestModuleEvents =
|
|
1436
|
-
decodeGuestModuleEvents(originUserTxReceipt)
|
|
1437
|
-
transactionStates[0].refunded =
|
|
1438
|
-
transactionStates[0].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1439
|
-
(event) =>
|
|
1440
|
-
event.type === "Refund" || event.type === "RefundAndSweep",
|
|
1441
|
-
) !== -1
|
|
1442
|
-
logger.console.log(
|
|
1443
|
-
"[trails-sdk] [GASLESS-FLOW] Gasless deposit events decoded",
|
|
1444
|
-
{
|
|
1445
|
-
chainId: originChainId,
|
|
1446
|
-
callFailed: (
|
|
1447
|
-
transactionStates[0].decodedGuestModuleEvents || []
|
|
1448
|
-
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1449
|
-
sweeperEvents: (
|
|
1450
|
-
transactionStates[0].decodedTrailsTokenSweeperEvents || []
|
|
1451
|
-
).length,
|
|
1452
|
-
refunded: transactionStates[0].refunded,
|
|
1453
|
-
},
|
|
1454
|
-
)
|
|
1455
|
-
|
|
1456
|
-
// Check for transaction failure or refund
|
|
1457
|
-
const hasCallFailed = (
|
|
1458
|
-
transactionStates[0].decodedGuestModuleEvents || []
|
|
1459
|
-
).some((e: any) => e?.type === "CallFailed")
|
|
1460
|
-
|
|
1461
|
-
if (transactionStates[0].refunded || hasCallFailed) {
|
|
1462
|
-
const errorMessage = transactionStates[0].refunded
|
|
1463
|
-
? "Transaction was refunded"
|
|
1464
|
-
: "Transaction call failed"
|
|
1465
|
-
|
|
1466
|
-
logger.console.error(
|
|
1467
|
-
"[trails-sdk] [GASLESS-FLOW] Deposit transaction failed",
|
|
1468
|
-
{
|
|
1469
|
-
refunded: transactionStates[0].refunded,
|
|
1470
|
-
callFailed: hasCallFailed,
|
|
1471
|
-
},
|
|
1472
|
-
)
|
|
1473
|
-
|
|
1474
|
-
// Call onCheckoutError callback if provided
|
|
1475
|
-
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1476
|
-
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1477
|
-
}
|
|
1478
|
-
}
|
|
1479
|
-
} catch (error) {
|
|
1480
|
-
logger.console.error(
|
|
1481
|
-
"[trails-sdk] Error decoding gasless deposit events",
|
|
1482
|
-
error,
|
|
1483
|
-
)
|
|
1484
|
-
}
|
|
1485
|
-
|
|
1486
|
-
onTransactionStateChange(transactionStates)
|
|
1487
|
-
|
|
1488
|
-
setTimeout(() => {
|
|
1489
|
-
const destinationChain = getChainInfo(destinationChainId)
|
|
1490
|
-
updatePersistentToast(
|
|
1491
|
-
"In Progress",
|
|
1492
|
-
`Your transaction to ${destinationChain?.name || "chain"} is in progress`,
|
|
1493
|
-
"info",
|
|
1494
|
-
)
|
|
1495
|
-
}, 1000)
|
|
1496
|
-
}
|
|
1497
|
-
|
|
1498
|
-
const checkForDepositTx = async () => {
|
|
1499
|
-
while (true) {
|
|
1500
|
-
try {
|
|
1501
|
-
const response = await getAccountTransactionHistory({
|
|
1502
|
-
chainId: originChainId,
|
|
1503
|
-
accountAddress: originIntentAddress,
|
|
1504
|
-
})
|
|
1505
|
-
logger.console.log(
|
|
1506
|
-
"[trails-sdk] getAccountTransactionHistory response",
|
|
1507
|
-
response,
|
|
1508
|
-
)
|
|
1509
|
-
if (response.transactions.length > 0) {
|
|
1510
|
-
const tx = response.transactions[0]
|
|
1511
|
-
if (!tx?.txnHash) {
|
|
1512
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1513
|
-
continue
|
|
1514
|
-
}
|
|
1515
|
-
// const isReceive = tx.transfers.some(
|
|
1516
|
-
// (transfer) => transfer.transferType === "RECEIVE",
|
|
1517
|
-
// )
|
|
1518
|
-
// if (!isReceive) {
|
|
1519
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1520
|
-
// continue
|
|
1521
|
-
// }
|
|
1522
|
-
const originDepositTxReceipt =
|
|
1523
|
-
await publicClient.getTransactionReceipt({
|
|
1524
|
-
hash: tx.txnHash as `0x${string}`,
|
|
1525
|
-
})
|
|
1526
|
-
|
|
1527
|
-
originUserTxReceipt = originDepositTxReceipt
|
|
1528
|
-
|
|
1529
|
-
transactionStates[0] = getTransactionStateFromReceipt(
|
|
1530
|
-
originDepositTxReceipt,
|
|
1531
|
-
originChainId,
|
|
1532
|
-
transactionStates[0]?.label,
|
|
1533
|
-
)
|
|
1534
|
-
onTransactionStateChange(transactionStates)
|
|
1535
|
-
|
|
1536
|
-
if (onOriginSend) {
|
|
1537
|
-
onOriginSend()
|
|
1538
|
-
}
|
|
1539
|
-
break
|
|
1540
|
-
}
|
|
1541
|
-
} catch (error) {
|
|
1542
|
-
logger.console.error("Error checking for deposit tx", error)
|
|
1543
|
-
}
|
|
1544
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1545
|
-
}
|
|
1546
|
-
}
|
|
1547
|
-
|
|
1548
|
-
const _checkForDestinationDepositTx: () => Promise<TransactionReceipt | null> =
|
|
1549
|
-
async () => {
|
|
1550
|
-
while (true) {
|
|
1551
|
-
try {
|
|
1552
|
-
const response = await getAccountTransactionHistory({
|
|
1553
|
-
chainId: destinationChainId,
|
|
1554
|
-
accountAddress: intent.payloads
|
|
1555
|
-
.destinationIntentAddress as `0x${string}`,
|
|
1556
|
-
})
|
|
1557
|
-
logger.console.log(
|
|
1558
|
-
"[trails-sdk] getAccountTransactionHistory response",
|
|
1559
|
-
response,
|
|
1560
|
-
)
|
|
1561
|
-
if (response.transactions.length > 0) {
|
|
1562
|
-
const tx = response.transactions[0]
|
|
1563
|
-
if (!tx?.txnHash) {
|
|
1564
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1565
|
-
continue
|
|
1566
|
-
}
|
|
1567
|
-
// const isReceive = tx.transfers.some(
|
|
1568
|
-
// (transfer) => transfer.transferType === "RECEIVE",
|
|
1569
|
-
// )
|
|
1570
|
-
// if (!isReceive) {
|
|
1571
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1572
|
-
// continue
|
|
1573
|
-
// }
|
|
1574
|
-
const destinationDepositTxReceipt =
|
|
1575
|
-
await destinationPublicClient.getTransactionReceipt({
|
|
1576
|
-
hash: tx.txnHash as `0x${string}`,
|
|
1577
|
-
})
|
|
1578
|
-
|
|
1579
|
-
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1580
|
-
destinationDepositTxReceipt,
|
|
1581
|
-
destinationChainId,
|
|
1582
|
-
transactionStates[2]?.label,
|
|
1583
|
-
)
|
|
1584
|
-
onTransactionStateChange(transactionStates)
|
|
1585
|
-
|
|
1586
|
-
return destinationDepositTxReceipt
|
|
1587
|
-
}
|
|
1588
|
-
} catch (error) {
|
|
1589
|
-
logger.console.error("Error checking for deposit tx", error)
|
|
1590
|
-
}
|
|
1591
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1592
|
-
}
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
// Variables to store the waitForReceipt functions
|
|
1596
|
-
let originMetaTxnReceiptPromise:
|
|
1597
|
-
| (() => Promise<MetaTxnReceipt | null>)
|
|
1598
|
-
| null = null
|
|
1599
|
-
let destinationMetaTxnReceiptPromise:
|
|
1600
|
-
| ((abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>)
|
|
1601
|
-
| ((abortSignal?: AbortSignal) => Promise<TransactionReceipt | null>)
|
|
1602
|
-
| null = null
|
|
1603
|
-
|
|
1604
|
-
// First phase: Send meta transactions and queue CCTP
|
|
1605
|
-
const originSendMetaTxnPromise = async () => {
|
|
1606
|
-
logger.console.log("[trails-sdk] Starting originSendMetaTxnPromise", {
|
|
1607
|
-
hasMetaTxn: !!intent.payloads.metaTxns[0],
|
|
1608
|
-
hasPrecondition: !!intent.payloads.preconditions[0],
|
|
1609
|
-
metaTxnId: intent.payloads.metaTxns[0]?.id,
|
|
1610
|
-
chainId: intent.payloads.metaTxns[0]?.chainId,
|
|
1611
|
-
})
|
|
1612
|
-
|
|
1613
|
-
if (intent.payloads.metaTxns[0] && intent.payloads.preconditions[0]) {
|
|
1614
|
-
// Extract fee quote from intent response using metatxnid as key
|
|
1615
|
-
const metaTxnId = intent.payloads.metaTxns[0].id
|
|
1616
|
-
const feeQuote = intent.payloads.feeQuotes?.[metaTxnId]
|
|
1617
|
-
logger.console.log(
|
|
1618
|
-
"[trails-sdk] Extracted fee quote for origin meta txn",
|
|
1619
|
-
{
|
|
1620
|
-
metaTxnId,
|
|
1621
|
-
feeQuote,
|
|
1622
|
-
hasFeeQuote: !!feeQuote,
|
|
1623
|
-
},
|
|
1624
|
-
)
|
|
1625
|
-
|
|
1626
|
-
logger.console.log(
|
|
1627
|
-
"[trails-sdk] Calling sendMetaTxAndWaitForReceipt for origin",
|
|
1628
|
-
{
|
|
1629
|
-
metaTxnId,
|
|
1630
|
-
chainId: intent.payloads.metaTxns[0].chainId,
|
|
1631
|
-
walletAddress: intent.payloads.metaTxns[0].walletAddress,
|
|
1632
|
-
contract: intent.payloads.metaTxns[0].contract,
|
|
1633
|
-
},
|
|
1634
|
-
)
|
|
1635
|
-
|
|
1636
|
-
const { waitForReceipt } = await sendMetaTxAndWaitForReceipt({
|
|
1637
|
-
metaTx: intent.payloads.metaTxns[0] as MetaTxn,
|
|
1638
|
-
relayer: originRelayer,
|
|
1639
|
-
precondition: intent.payloads
|
|
1640
|
-
.preconditions[0] as IntentPrecondition,
|
|
1641
|
-
feeQuote: feeQuote,
|
|
1642
|
-
})
|
|
1643
|
-
|
|
1644
|
-
logger.console.log(
|
|
1645
|
-
"[trails-sdk] Origin meta transaction sent successfully",
|
|
1646
|
-
{
|
|
1647
|
-
metaTxnId,
|
|
1648
|
-
},
|
|
1649
|
-
)
|
|
1650
|
-
|
|
1651
|
-
// Store the waitForReceipt function for later use
|
|
1652
|
-
originMetaTxnReceiptPromise = waitForReceipt
|
|
1653
|
-
} else {
|
|
1654
|
-
logger.console.warn(
|
|
1655
|
-
"[trails-sdk] Skipping origin sendMetaTxn - missing metaTxn or precondition",
|
|
1656
|
-
{
|
|
1657
|
-
hasMetaTxn: !!intent.payloads.metaTxns[0],
|
|
1658
|
-
hasPrecondition: !!intent.payloads.preconditions[0],
|
|
1659
|
-
},
|
|
1660
|
-
)
|
|
1661
|
-
}
|
|
1662
|
-
}
|
|
1663
|
-
|
|
1664
|
-
const destinationSendMetaTxnPromise = async () => {
|
|
1665
|
-
logger.console.log(
|
|
1666
|
-
"[trails-sdk] Starting destinationSendMetaTxnPromise",
|
|
1667
|
-
{
|
|
1668
|
-
quoteProvider: intent.payloads.quote.quoteProvider,
|
|
1669
|
-
hasQuoteProviderRequestId:
|
|
1670
|
-
!!intent.payloads.quote.quoteProviderRequestId,
|
|
1671
|
-
hasPrecondition1: !!intent.payloads.preconditions[1],
|
|
1672
|
-
hasMetaTxn1: !!intent.payloads.metaTxns[1],
|
|
1673
|
-
},
|
|
1674
|
-
)
|
|
1675
|
-
|
|
1676
|
-
if (
|
|
1677
|
-
intent.payloads.quote.quoteProvider === "relay" &&
|
|
1678
|
-
intent.payloads.quote.quoteProviderRequestId &&
|
|
1679
|
-
!intent.payloads.preconditions[1] &&
|
|
1680
|
-
!intent.payloads.metaTxns[1]
|
|
1681
|
-
) {
|
|
1682
|
-
logger.console.log(
|
|
1683
|
-
"[trails-sdk] Setting up relay destination promise",
|
|
1684
|
-
{
|
|
1685
|
-
quoteProviderRequestId:
|
|
1686
|
-
intent.payloads.quote.quoteProviderRequestId,
|
|
1687
|
-
},
|
|
1688
|
-
)
|
|
1689
|
-
// For relay, we'll wait for the receipt in the wait phase
|
|
1690
|
-
// Just store the requestId for later use
|
|
1691
|
-
destinationMetaTxnReceiptPromise = async (
|
|
1692
|
-
abortSignal?: AbortSignal,
|
|
1693
|
-
) => {
|
|
1694
|
-
logger.console.log(
|
|
1695
|
-
"[trails-sdk] waitForRelayDestinationTx starting",
|
|
1696
|
-
{
|
|
1697
|
-
quoteProviderRequestId:
|
|
1698
|
-
intent.payloads.quote.quoteProviderRequestId,
|
|
1699
|
-
aborted: abortSignal?.aborted,
|
|
1700
|
-
},
|
|
1701
|
-
)
|
|
1702
|
-
try {
|
|
1703
|
-
// Check if we should abort before starting
|
|
1704
|
-
if (abortSignal?.aborted) {
|
|
1705
|
-
logger.console.log(
|
|
1706
|
-
"[trails-sdk] Aborting relay destination tx due to abort signal",
|
|
1707
|
-
)
|
|
1708
|
-
return null
|
|
1709
|
-
}
|
|
1710
|
-
|
|
1711
|
-
const txHash = await waitForRelayDestinationTx(
|
|
1712
|
-
intent.payloads.quote.quoteProviderRequestId,
|
|
1713
|
-
)
|
|
1714
|
-
logger.console.log(
|
|
1715
|
-
"[trails-sdk] waitForRelayDestinationTx completed",
|
|
1716
|
-
{
|
|
1717
|
-
txHash,
|
|
1718
|
-
quoteProviderRequestId:
|
|
1719
|
-
intent.payloads.quote.quoteProviderRequestId,
|
|
1720
|
-
},
|
|
1721
|
-
)
|
|
1722
|
-
if (txHash) {
|
|
1723
|
-
logger.console.log(
|
|
1724
|
-
"[trails-sdk] Fetching transaction receipt for relay destination",
|
|
1725
|
-
{
|
|
1726
|
-
txHash,
|
|
1727
|
-
chainId: destinationChainId,
|
|
1728
|
-
},
|
|
1729
|
-
)
|
|
1730
|
-
|
|
1731
|
-
const destinationTxnReceipt =
|
|
1732
|
-
await destinationPublicClient.getTransactionReceipt({
|
|
1733
|
-
hash: txHash as `0x${string}`,
|
|
1734
|
-
})
|
|
1735
|
-
logger.console.log(
|
|
1736
|
-
"[trails-sdk] relay destinationTxnReceipt received",
|
|
1737
|
-
{
|
|
1738
|
-
txHash,
|
|
1739
|
-
blockNumber: destinationTxnReceipt?.blockNumber,
|
|
1740
|
-
status: destinationTxnReceipt?.status,
|
|
1741
|
-
gasUsed: destinationTxnReceipt?.gasUsed,
|
|
1742
|
-
},
|
|
1743
|
-
)
|
|
1744
|
-
if (transactionStates[2]) {
|
|
1745
|
-
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1746
|
-
destinationTxnReceipt,
|
|
1747
|
-
destinationChainId,
|
|
1748
|
-
transactionStates[2]?.label,
|
|
1749
|
-
)
|
|
1750
|
-
}
|
|
1751
|
-
onTransactionStateChange(transactionStates)
|
|
1752
|
-
return destinationTxnReceipt
|
|
1753
|
-
} else {
|
|
1754
|
-
logger.console.warn(
|
|
1755
|
-
"[trails-sdk] No txHash returned from waitForRelayDestinationTx",
|
|
1756
|
-
{
|
|
1757
|
-
quoteProviderRequestId:
|
|
1758
|
-
intent.payloads.quote.quoteProviderRequestId,
|
|
1759
|
-
},
|
|
1760
|
-
)
|
|
1761
|
-
}
|
|
1762
|
-
} catch (error: unknown) {
|
|
1763
|
-
logger.console.error(
|
|
1764
|
-
"[trails-sdk] Error waiting for relay destination tx",
|
|
1765
|
-
{
|
|
1766
|
-
error:
|
|
1767
|
-
error instanceof Error ? error.message : String(error),
|
|
1768
|
-
quoteProviderRequestId:
|
|
1769
|
-
intent.payloads.quote.quoteProviderRequestId,
|
|
1770
|
-
},
|
|
1771
|
-
)
|
|
1772
|
-
if (transactionStates?.[2]) {
|
|
1773
|
-
transactionStates[2].state = "failed"
|
|
1774
|
-
onTransactionStateChange(transactionStates)
|
|
1775
|
-
}
|
|
1776
|
-
throw error
|
|
1777
|
-
}
|
|
1778
|
-
return null
|
|
1779
|
-
}
|
|
1780
|
-
} else {
|
|
1781
|
-
logger.console.log(
|
|
1782
|
-
"[trails-sdk] Setting up destination meta transaction promise (non-relay)",
|
|
1783
|
-
{
|
|
1784
|
-
quoteProvider: intent.payloads.quote.quoteProvider,
|
|
1785
|
-
hasMetaTxn1: !!intent.payloads.metaTxns[1],
|
|
1786
|
-
hasPrecondition1: !!intent.payloads.preconditions[1],
|
|
1787
|
-
},
|
|
1788
|
-
)
|
|
1789
|
-
|
|
1790
|
-
if (
|
|
1791
|
-
intent.payloads.metaTxns[1] &&
|
|
1792
|
-
intent.payloads.preconditions[1]
|
|
1793
|
-
) {
|
|
1794
|
-
// Extract fee quote from intent response using metatxnid as key
|
|
1795
|
-
const metaTxnId = intent.payloads.metaTxns[1].id
|
|
1796
|
-
const feeQuote = intent.payloads.feeQuotes?.[metaTxnId]
|
|
1797
|
-
logger.console.log(
|
|
1798
|
-
"[trails-sdk] Extracted fee quote for destination meta txn",
|
|
1799
|
-
{
|
|
1800
|
-
metaTxnId,
|
|
1801
|
-
feeQuote,
|
|
1802
|
-
hasFeeQuote: !!feeQuote,
|
|
1803
|
-
},
|
|
1804
|
-
)
|
|
1805
|
-
|
|
1806
|
-
logger.console.log(
|
|
1807
|
-
"[trails-sdk] Calling sendMetaTxAndWaitForReceipt for destination",
|
|
1808
|
-
{
|
|
1809
|
-
metaTxnId,
|
|
1810
|
-
chainId: intent.payloads.metaTxns[1].chainId,
|
|
1811
|
-
walletAddress: intent.payloads.metaTxns[1].walletAddress,
|
|
1812
|
-
contract: intent.payloads.metaTxns[1].contract,
|
|
1813
|
-
},
|
|
1814
|
-
)
|
|
1815
|
-
|
|
1816
|
-
const { waitForReceipt } = await sendMetaTxAndWaitForReceipt({
|
|
1817
|
-
metaTx: intent.payloads.metaTxns[1] as MetaTxn,
|
|
1818
|
-
relayer: destinationRelayer,
|
|
1819
|
-
precondition: intent.payloads
|
|
1820
|
-
.preconditions[1] as IntentPrecondition,
|
|
1821
|
-
feeQuote: feeQuote,
|
|
1822
|
-
})
|
|
1823
|
-
|
|
1824
|
-
logger.console.log(
|
|
1825
|
-
"[trails-sdk] Destination meta transaction sent successfully",
|
|
1826
|
-
{
|
|
1827
|
-
metaTxnId,
|
|
1828
|
-
},
|
|
1829
|
-
)
|
|
1830
|
-
|
|
1831
|
-
// Store the waitForReceipt function for later use
|
|
1832
|
-
destinationMetaTxnReceiptPromise = waitForReceipt
|
|
1833
|
-
} else {
|
|
1834
|
-
logger.console.warn(
|
|
1835
|
-
"[trails-sdk] Skipping destination sendMetaTxn - missing metaTxn or precondition",
|
|
1836
|
-
{
|
|
1837
|
-
hasMetaTxn: !!intent.payloads.metaTxns[1],
|
|
1838
|
-
hasPrecondition: !!intent.payloads.preconditions[1],
|
|
1839
|
-
},
|
|
1840
|
-
)
|
|
1841
|
-
}
|
|
1842
|
-
// } else if (intent.payloads.destinationIntentAddress) {
|
|
1843
|
-
// destinationMetaTxnReceiptPromise = checkForDestinationDepositTx
|
|
1844
|
-
// }
|
|
1845
|
-
}
|
|
1846
|
-
}
|
|
1847
|
-
|
|
1848
|
-
let queueCctpPromise: (() => Promise<void>) | null = null
|
|
1849
|
-
|
|
1850
|
-
const isCctp = intent.payloads.quote.quoteProvider === "cctp"
|
|
1851
|
-
if (isCctp) {
|
|
1852
|
-
queueCctpPromise = async () => {
|
|
1853
|
-
while (true) {
|
|
1854
|
-
const originMetaTxnHash = originMetaTxnReceipt?.txnHash
|
|
1855
|
-
if (originMetaTxnHash) {
|
|
1856
|
-
await queueCCTPTransfer({
|
|
1857
|
-
trailsClient,
|
|
1858
|
-
sourceTxHash: originMetaTxnHash,
|
|
1859
|
-
sourceChainId: originChainId,
|
|
1860
|
-
destinationChainId: destinationChainId,
|
|
1861
|
-
})
|
|
1862
|
-
break
|
|
1863
|
-
}
|
|
1864
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1865
|
-
}
|
|
1866
|
-
}
|
|
1867
|
-
} else {
|
|
1868
|
-
queueCctpPromise = () => Promise.resolve()
|
|
1869
|
-
}
|
|
1870
|
-
|
|
1871
|
-
checkForDepositTx().catch((error) => {
|
|
1872
|
-
logger.console.error("Error checking for deposit tx", error)
|
|
1873
|
-
})
|
|
1874
|
-
|
|
1875
|
-
// Phase 1: Send meta transactions and queue CCTP
|
|
1876
|
-
logger.console.log(
|
|
1877
|
-
"[trails-sdk] Starting Phase 1: Sending meta transactions and queuing CCTP",
|
|
1878
|
-
)
|
|
1879
|
-
|
|
1880
|
-
await Promise.all([
|
|
1881
|
-
originSendMetaTxnPromise(),
|
|
1882
|
-
destinationSendMetaTxnPromise(),
|
|
1883
|
-
])
|
|
1884
|
-
|
|
1885
|
-
logger.console.log("[trails-sdk] Phase 1 completed successfully")
|
|
1886
|
-
|
|
1887
|
-
// Phase 2: Wait for receipts and execute deposit
|
|
1888
|
-
logger.console.log(
|
|
1889
|
-
"[trails-sdk] Starting Phase 2: Waiting for receipts and executing deposit",
|
|
1890
|
-
)
|
|
1891
|
-
|
|
1892
|
-
const waitForOriginMetaTxnReceiptPromise = async () => {
|
|
1893
|
-
logger.console.log(
|
|
1894
|
-
"[trails-sdk] Waiting for origin meta transaction receipt",
|
|
1895
|
-
)
|
|
1896
|
-
if (originMetaTxnReceiptPromise) {
|
|
1897
|
-
try {
|
|
1898
|
-
originMetaTxnReceipt = await originMetaTxnReceiptPromise()
|
|
1899
|
-
|
|
1900
|
-
if (originMetaTxnReceipt && transactionStates[1]) {
|
|
1901
|
-
transactionStates[1] = getTransactionStateFromReceipt(
|
|
1902
|
-
originMetaTxnReceipt,
|
|
1903
|
-
originChainId,
|
|
1904
|
-
transactionStates[1]?.label,
|
|
1905
|
-
)
|
|
1906
|
-
onTransactionStateChange(transactionStates)
|
|
1907
|
-
|
|
1908
|
-
try {
|
|
1909
|
-
const receipt = await publicClient.getTransactionReceipt({
|
|
1910
|
-
hash: originMetaTxnReceipt.txnHash as `0x${string}`,
|
|
1911
|
-
})
|
|
1912
|
-
transactionStates[1].decodedTrailsTokenSweeperEvents =
|
|
1913
|
-
decodeTrailsTokenSweeperEvents(receipt)
|
|
1914
|
-
transactionStates[1].decodedGuestModuleEvents =
|
|
1915
|
-
decodeGuestModuleEvents(receipt)
|
|
1916
|
-
transactionStates[1].refunded =
|
|
1917
|
-
transactionStates[1].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1918
|
-
(event) =>
|
|
1919
|
-
event.type === "Refund" ||
|
|
1920
|
-
event.type === "RefundAndSweep",
|
|
1921
|
-
) !== -1
|
|
1922
|
-
logger.console.log("[trails-sdk] Origin meta-tx events", {
|
|
1923
|
-
chainId: originChainId,
|
|
1924
|
-
callFailed: (
|
|
1925
|
-
transactionStates[1].decodedGuestModuleEvents || []
|
|
1926
|
-
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1927
|
-
sweeperEvents: (
|
|
1928
|
-
transactionStates[1].decodedTrailsTokenSweeperEvents || []
|
|
1929
|
-
).length,
|
|
1930
|
-
refunded: transactionStates[1].refunded,
|
|
1931
|
-
})
|
|
1932
|
-
|
|
1933
|
-
// Check for transaction failure or refund
|
|
1934
|
-
const hasCallFailed = (
|
|
1935
|
-
transactionStates[1].decodedGuestModuleEvents || []
|
|
1936
|
-
).some((e: any) => e?.type === "CallFailed")
|
|
1937
|
-
|
|
1938
|
-
if (transactionStates[1].refunded || hasCallFailed) {
|
|
1939
|
-
const errorMessage = transactionStates[1].refunded
|
|
1940
|
-
? "Origin transaction was refunded"
|
|
1941
|
-
: "Origin transaction call failed"
|
|
1942
|
-
|
|
1943
|
-
logger.console.error("[trails-sdk] Origin meta-tx failed", {
|
|
1944
|
-
refunded: transactionStates[1].refunded,
|
|
1945
|
-
callFailed: hasCallFailed,
|
|
1946
|
-
})
|
|
1947
|
-
|
|
1948
|
-
// Call onCheckoutError callback if provided
|
|
1949
|
-
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1950
|
-
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1951
|
-
}
|
|
1952
|
-
}
|
|
1953
|
-
|
|
1954
|
-
onTransactionStateChange(transactionStates)
|
|
1955
|
-
} catch (error) {
|
|
1956
|
-
logger.console.error("Error decoding origin tx events", error)
|
|
1957
|
-
}
|
|
1958
|
-
}
|
|
1959
|
-
} catch (error) {
|
|
1960
|
-
logger.console.error(
|
|
1961
|
-
"[trails-sdk] Error waiting for origin receipt:",
|
|
1962
|
-
error,
|
|
1963
|
-
)
|
|
1964
|
-
}
|
|
1965
|
-
} else {
|
|
1966
|
-
logger.console.log(
|
|
1967
|
-
"[trails-sdk] No origin meta transaction receipt promise to wait for",
|
|
1968
|
-
)
|
|
1969
|
-
}
|
|
1970
|
-
}
|
|
1971
|
-
|
|
1972
|
-
const waitForDestinationMetaTxnReceiptPromise = async () => {
|
|
1973
|
-
logger.console.log(
|
|
1974
|
-
"[trails-sdk] Waiting for destination meta transaction receipt",
|
|
1975
|
-
)
|
|
1976
|
-
if (destinationMetaTxnReceiptPromise) {
|
|
1977
|
-
try {
|
|
1978
|
-
// Create abort controller for cancelling destination polling
|
|
1979
|
-
const abortController = new AbortController()
|
|
1980
|
-
|
|
1981
|
-
// Race between destination receipt and failure polling
|
|
1982
|
-
const destinationReceiptPromise = destinationMetaTxnReceiptPromise
|
|
1983
|
-
? destinationMetaTxnReceiptPromise(abortController.signal)
|
|
1984
|
-
: Promise.resolve(null)
|
|
1985
|
-
|
|
1986
|
-
const failurePollingPromise = new Promise<null>((resolve) => {
|
|
1987
|
-
const pollForFailures = () => {
|
|
1988
|
-
const isPreviousTxCallFailed =
|
|
1989
|
-
transactionStates?.some((tx) => tx.state === "failed") ||
|
|
1990
|
-
transactionStates?.some((tx) =>
|
|
1991
|
-
tx?.decodedGuestModuleEvents?.some(
|
|
1992
|
-
(event) => event.type === "CallFailed",
|
|
1993
|
-
),
|
|
1994
|
-
)
|
|
1995
|
-
|
|
1996
|
-
if (isPreviousTxCallFailed) {
|
|
1997
|
-
logger.console.log(
|
|
1998
|
-
"[trails-sdk] Aborting destination meta transaction due to previous transaction failure",
|
|
1999
|
-
)
|
|
2000
|
-
abortController.abort()
|
|
2001
|
-
resolve(null)
|
|
2002
|
-
} else {
|
|
2003
|
-
// Continue polling every 1 second
|
|
2004
|
-
setTimeout(pollForFailures, 1000)
|
|
2005
|
-
}
|
|
2006
|
-
}
|
|
2007
|
-
pollForFailures()
|
|
2008
|
-
})
|
|
2009
|
-
|
|
2010
|
-
destinationMetaTxnReceipt = (await Promise.race([
|
|
2011
|
-
destinationReceiptPromise,
|
|
2012
|
-
failurePollingPromise,
|
|
2013
|
-
])) as MetaTxnReceipt
|
|
2014
|
-
|
|
2015
|
-
logger.console.log(
|
|
2016
|
-
"[trails-sdk] destinationMetaTxnReceipt",
|
|
2017
|
-
destinationMetaTxnReceipt,
|
|
2018
|
-
)
|
|
2019
|
-
|
|
2020
|
-
if (destinationMetaTxnReceipt && transactionStates[2]) {
|
|
2021
|
-
transactionStates[2] = getTransactionStateFromReceipt(
|
|
2022
|
-
destinationMetaTxnReceipt,
|
|
2023
|
-
destinationChainId,
|
|
2024
|
-
transactionStates[2]?.label,
|
|
2025
|
-
)
|
|
2026
|
-
onTransactionStateChange(transactionStates)
|
|
2027
|
-
|
|
2028
|
-
try {
|
|
2029
|
-
const receipt =
|
|
2030
|
-
await destinationPublicClient.getTransactionReceipt({
|
|
2031
|
-
hash: destinationMetaTxnReceipt.txnHash as `0x${string}`,
|
|
2032
|
-
})
|
|
2033
|
-
transactionStates[2].decodedTrailsTokenSweeperEvents =
|
|
2034
|
-
decodeTrailsTokenSweeperEvents(receipt)
|
|
2035
|
-
transactionStates[2].decodedGuestModuleEvents =
|
|
2036
|
-
decodeGuestModuleEvents(receipt)
|
|
2037
|
-
transactionStates[2].refunded =
|
|
2038
|
-
transactionStates[2].decodedTrailsTokenSweeperEvents.findIndex(
|
|
2039
|
-
(event) =>
|
|
2040
|
-
event.type === "Refund" ||
|
|
2041
|
-
event.type === "RefundAndSweep",
|
|
2042
|
-
) !== -1 ||
|
|
2043
|
-
(transactionStates[2].decodedTrailsTokenSweeperEvents.findIndex(
|
|
2044
|
-
(event) => event.type === "Sweep",
|
|
2045
|
-
) !== -1 &&
|
|
2046
|
-
transactionStates[2].decodedGuestModuleEvents.findIndex(
|
|
2047
|
-
(event) => event.type === "CallFailed",
|
|
2048
|
-
) !== -1)
|
|
2049
|
-
logger.console.log(
|
|
2050
|
-
"[trails-sdk] Destination meta-tx events",
|
|
2051
|
-
{
|
|
2052
|
-
chainId: destinationChainId,
|
|
2053
|
-
callFailed: (
|
|
2054
|
-
transactionStates[2].decodedGuestModuleEvents || []
|
|
2055
|
-
).filter((e: any) => e?.type === "CallFailed").length,
|
|
2056
|
-
sweeperEvents: (
|
|
2057
|
-
transactionStates[2].decodedTrailsTokenSweeperEvents ||
|
|
2058
|
-
[]
|
|
2059
|
-
).length,
|
|
2060
|
-
refunded: transactionStates[2].refunded,
|
|
2061
|
-
},
|
|
2062
|
-
)
|
|
2063
|
-
|
|
2064
|
-
// Check for transaction failure or refund
|
|
2065
|
-
const hasCallFailed = (
|
|
2066
|
-
transactionStates[2].decodedGuestModuleEvents || []
|
|
2067
|
-
).some((e: any) => e?.type === "CallFailed")
|
|
2068
|
-
|
|
2069
|
-
if (transactionStates[2].refunded || hasCallFailed) {
|
|
2070
|
-
const errorMessage = transactionStates[2].refunded
|
|
2071
|
-
? "Destination transaction was refunded"
|
|
2072
|
-
: "Destination transaction call failed"
|
|
2073
|
-
|
|
2074
|
-
logger.console.error(
|
|
2075
|
-
"[trails-sdk] Destination meta-tx failed",
|
|
2076
|
-
{
|
|
2077
|
-
refunded: transactionStates[2].refunded,
|
|
2078
|
-
callFailed: hasCallFailed,
|
|
2079
|
-
},
|
|
2080
|
-
)
|
|
2081
|
-
|
|
2082
|
-
// Call onCheckoutError callback if provided
|
|
2083
|
-
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2084
|
-
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
2085
|
-
}
|
|
2086
|
-
}
|
|
2087
|
-
|
|
2088
|
-
onTransactionStateChange(transactionStates)
|
|
2089
|
-
} catch (error) {
|
|
2090
|
-
logger.console.error(
|
|
2091
|
-
"Error decoding destination tx events",
|
|
2092
|
-
error,
|
|
2093
|
-
)
|
|
2094
|
-
}
|
|
2095
|
-
}
|
|
2096
|
-
} catch (error) {
|
|
2097
|
-
logger.console.error(
|
|
2098
|
-
"[trails-sdk] Error waiting for destination receipt:",
|
|
2099
|
-
error,
|
|
2100
|
-
)
|
|
2101
|
-
// For relay transactions, this might be expected if still waiting
|
|
2102
|
-
if (intent.payloads.quote.quoteProvider === "relay") {
|
|
2103
|
-
logger.console.log(
|
|
2104
|
-
"[trails-sdk] Relay transaction still waiting, this is normal",
|
|
2105
|
-
)
|
|
2106
|
-
}
|
|
2107
|
-
}
|
|
2108
|
-
} else {
|
|
2109
|
-
logger.console.log(
|
|
2110
|
-
"[trails-sdk] No destination meta transaction receipt promise to wait for",
|
|
2111
|
-
)
|
|
2112
|
-
}
|
|
2113
|
-
}
|
|
2114
|
-
|
|
2115
|
-
logger.console.log(
|
|
2116
|
-
"[trails-sdk] Executing Phase 2 Promise.all with deposit",
|
|
2117
|
-
)
|
|
2118
|
-
logger.console.log(
|
|
2119
|
-
"[trails-sdk] About to call depositPromise - fundMethod is:",
|
|
2120
|
-
fundMethod,
|
|
2121
|
-
)
|
|
2122
|
-
|
|
2123
|
-
await Promise.all([
|
|
2124
|
-
depositPromise(),
|
|
2125
|
-
waitForOriginMetaTxnReceiptPromise(),
|
|
2126
|
-
waitForDestinationMetaTxnReceiptPromise(),
|
|
2127
|
-
queueCctpPromise(),
|
|
2128
|
-
])
|
|
2129
|
-
logger.console.log("[trails-sdk] Phase 2 completed successfully")
|
|
2130
|
-
|
|
2131
|
-
// Track payment completion for different chain and different token
|
|
2132
|
-
if (originUserTxReceipt && destinationMetaTxnReceipt) {
|
|
2133
|
-
// Check if any transaction failed or was refunded
|
|
2134
|
-
const hasAnyCallFailed = transactionStates.some((tx) =>
|
|
2135
|
-
tx?.decodedGuestModuleEvents?.some(
|
|
2136
|
-
(e: any) => e?.type === "CallFailed",
|
|
2137
|
-
),
|
|
2138
|
-
)
|
|
2139
|
-
const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
|
|
2140
|
-
const txStatus =
|
|
2141
|
-
!hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
|
|
2142
|
-
|
|
2143
|
-
// Always track payment completion regardless of success/failure
|
|
2144
|
-
trackPaymentCompleted({
|
|
2145
|
-
userAddress: account.address,
|
|
2146
|
-
originIntentAddress,
|
|
2147
|
-
originTxHash: (originUserTxReceipt as TransactionReceipt)
|
|
2148
|
-
.transactionHash,
|
|
2149
|
-
destinationTxHash: (destinationMetaTxnReceipt as MetaTxnReceipt)
|
|
2150
|
-
?.txnHash,
|
|
2151
|
-
originChainId,
|
|
2152
|
-
destinationChainId,
|
|
2153
|
-
mode,
|
|
2154
|
-
fundMethod,
|
|
2155
|
-
originTokenSymbol,
|
|
2156
|
-
originTokenAddress,
|
|
2157
|
-
destinationTokenAddress,
|
|
2158
|
-
destinationTokenSymbol,
|
|
2159
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2160
|
-
destinationTokenAmountUsd:
|
|
2161
|
-
effectiveDestinationTokenAmountUsd?.toString(),
|
|
2162
|
-
})
|
|
2163
|
-
|
|
2164
|
-
// Call onCheckoutComplete callback with transaction status
|
|
2165
|
-
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
2166
|
-
checkoutOnHandlers.triggerCheckoutComplete(txStatus)
|
|
2167
|
-
}
|
|
2168
|
-
} else {
|
|
2169
|
-
if (
|
|
2170
|
-
transactionStates[1] &&
|
|
2171
|
-
transactionStates[1]?.transactionHash === "" &&
|
|
2172
|
-
transactionStates[1]?.state === "pending"
|
|
2173
|
-
) {
|
|
2174
|
-
transactionStates[1].state = "aborted"
|
|
2175
|
-
onTransactionStateChange(transactionStates)
|
|
2176
|
-
}
|
|
2177
|
-
if (
|
|
2178
|
-
transactionStates[2] &&
|
|
2179
|
-
transactionStates[2]?.transactionHash === "" &&
|
|
2180
|
-
transactionStates[2]?.state === "pending"
|
|
2181
|
-
) {
|
|
2182
|
-
transactionStates[2].state = "aborted"
|
|
2183
|
-
onTransactionStateChange(transactionStates)
|
|
2184
|
-
}
|
|
2185
|
-
|
|
2186
|
-
// Track payment error if transactions didn't complete successfully
|
|
2187
|
-
trackPaymentError({
|
|
2188
|
-
error:
|
|
2189
|
-
"Payment transactions possibly did not complete successfully. Was not able to receive both origin and destination meta transaction receipts. May be an API error.",
|
|
2190
|
-
userAddress: account.address,
|
|
2191
|
-
originIntentAddress,
|
|
2192
|
-
mode,
|
|
2193
|
-
fundMethod,
|
|
2194
|
-
originChainId,
|
|
2195
|
-
destinationChainId,
|
|
2196
|
-
originTokenSymbol,
|
|
2197
|
-
originTokenAddress,
|
|
2198
|
-
destinationTokenAddress,
|
|
2199
|
-
destinationTokenSymbol,
|
|
2200
|
-
})
|
|
2201
|
-
|
|
2202
|
-
// Call onCheckoutError callback if provided
|
|
2203
|
-
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2204
|
-
checkoutOnHandlers.triggerCheckoutError(
|
|
2205
|
-
"Payment transactions did not complete successfully",
|
|
2206
|
-
)
|
|
2207
|
-
}
|
|
2208
|
-
}
|
|
2209
|
-
|
|
2210
|
-
return {
|
|
2211
|
-
originUserTxReceipt,
|
|
2212
|
-
originMetaTxnReceipt,
|
|
2213
|
-
destinationMetaTxnReceipt,
|
|
2214
|
-
totalCompletionSeconds: await getTxTimeDiff(
|
|
2215
|
-
transactionStates[0],
|
|
2216
|
-
transactionStates[2],
|
|
2217
|
-
),
|
|
2218
|
-
}
|
|
2219
|
-
} catch (error) {
|
|
2220
|
-
const errorMessage =
|
|
2221
|
-
error instanceof Error
|
|
2222
|
-
? error.message
|
|
2223
|
-
: "Unknown error occurred during transaction"
|
|
2224
|
-
logger.console.error(
|
|
2225
|
-
"[trails-sdk] Error in sendHandlerForDifferentChainDifferentToken:",
|
|
2226
|
-
error,
|
|
2227
|
-
)
|
|
2228
|
-
|
|
2229
|
-
// Track payment error
|
|
2230
|
-
trackPaymentError({
|
|
2231
|
-
error: errorMessage,
|
|
2232
|
-
userAddress: account.address,
|
|
2233
|
-
originIntentAddress,
|
|
2234
|
-
mode,
|
|
2235
|
-
fundMethod,
|
|
2236
|
-
originChainId,
|
|
2237
|
-
destinationChainId,
|
|
2238
|
-
originTokenSymbol,
|
|
2239
|
-
originTokenAddress,
|
|
2240
|
-
destinationTokenAddress,
|
|
2241
|
-
destinationTokenSymbol,
|
|
2242
|
-
})
|
|
2243
|
-
|
|
2244
|
-
// Call onCheckoutError callback if provided
|
|
2245
|
-
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2246
|
-
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
2247
|
-
}
|
|
2248
|
-
|
|
2249
|
-
// Re-throw the error so caller can handle if needed
|
|
2250
|
-
throw error
|
|
2251
|
-
}
|
|
2252
|
-
},
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
|
|
2256
|
-
async function sendHandlerForSameChainSameToken({
|
|
2257
|
-
originTokenAddress,
|
|
2258
|
-
originTokenDecimals,
|
|
2259
|
-
swapAmount,
|
|
2260
|
-
destinationCalldata,
|
|
2261
|
-
recipient,
|
|
2262
|
-
walletClient,
|
|
2263
|
-
onTransactionStateChange,
|
|
2264
|
-
dryMode,
|
|
2265
|
-
account,
|
|
2266
|
-
chain,
|
|
2267
|
-
transactionStates,
|
|
2268
|
-
sourceTokenPriceUsd,
|
|
2269
|
-
destinationTokenPriceUsd,
|
|
2270
|
-
originNativeTokenPriceUsd,
|
|
2271
|
-
slippageTolerance,
|
|
2272
|
-
checkoutOnHandlers,
|
|
2273
|
-
mode,
|
|
2274
|
-
fundMethod,
|
|
2275
|
-
gasless,
|
|
2276
|
-
paymasterUrl,
|
|
2277
|
-
selectedFeeToken,
|
|
2278
|
-
trailsClient,
|
|
2279
|
-
originRelayer,
|
|
2280
|
-
}: {
|
|
2281
|
-
originTokenAddress: string
|
|
2282
|
-
originTokenDecimals: number
|
|
2283
|
-
swapAmount: string
|
|
2284
|
-
destinationCalldata?: string
|
|
2285
|
-
recipient: string
|
|
2286
|
-
originChainId: number
|
|
2287
|
-
walletClient: WalletClient
|
|
2288
|
-
publicClient: PublicClient
|
|
2289
|
-
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
2290
|
-
dryMode: boolean
|
|
2291
|
-
account: Account
|
|
2292
|
-
chain: Chain
|
|
2293
|
-
transactionStates: TransactionState[]
|
|
2294
|
-
sourceTokenPriceUsd?: number | null
|
|
2295
|
-
destinationTokenPriceUsd?: number | null
|
|
2296
|
-
originNativeTokenPriceUsd?: number | null
|
|
2297
|
-
slippageTolerance?: string
|
|
2298
|
-
checkoutOnHandlers?: CheckoutOnHandlers
|
|
2299
|
-
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
2300
|
-
fundMethod?: string
|
|
2301
|
-
gasless?: boolean
|
|
2302
|
-
paymasterUrl?: string
|
|
2303
|
-
selectedFeeToken?: any
|
|
2304
|
-
trailsClient: TrailsAPIClient
|
|
2305
|
-
originRelayer: Relayer.RpcRelayer
|
|
2306
|
-
}): Promise<PrepareSendReturn> {
|
|
2307
|
-
logger.console.log("[trails-sdk] isToSameToken && isToSameChain")
|
|
2308
|
-
const testnet = isTestnetDebugMode()
|
|
2309
|
-
const effectiveOriginChain = testnet ? getTestnetChainInfo(chain)! : chain
|
|
2310
|
-
const effectiveOriginChainId = effectiveOriginChain.id
|
|
2311
|
-
const effectiveOriginTokenAddress = testnet
|
|
2312
|
-
? getTestnetOriginTokenAddress(effectiveOriginChainId)
|
|
2313
|
-
: originTokenAddress
|
|
2314
|
-
const effectivePublicClient = createPublicClient({
|
|
2315
|
-
chain: effectiveOriginChain,
|
|
2316
|
-
transport: http(),
|
|
2317
|
-
})
|
|
2318
|
-
|
|
2319
|
-
let noSufficientBalance = false
|
|
2320
|
-
|
|
2321
|
-
const { hasEnoughBalance } = await checkAccountBalance({
|
|
2322
|
-
account,
|
|
2323
|
-
tokenAddress: originTokenAddress,
|
|
2324
|
-
depositAmount: swapAmount,
|
|
2325
|
-
publicClient: effectivePublicClient,
|
|
2326
|
-
})
|
|
2327
|
-
|
|
2328
|
-
if (!hasEnoughBalance) {
|
|
2329
|
-
noSufficientBalance = true
|
|
2330
|
-
}
|
|
2331
|
-
|
|
2332
|
-
// Build origin call params and estimate gas limit for the quote
|
|
2333
|
-
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
2334
|
-
const originCallParamsBase = buildSameChainTransactionParams({
|
|
2335
|
-
hasCustomCalldata,
|
|
2336
|
-
recipient,
|
|
2337
|
-
effectiveOriginTokenAddress,
|
|
2338
|
-
destinationCalldata,
|
|
2339
|
-
swapAmount,
|
|
2340
|
-
effectiveOriginChainId,
|
|
2341
|
-
effectiveOriginChain,
|
|
2342
|
-
})
|
|
2343
|
-
|
|
2344
|
-
logger.console.log(
|
|
2345
|
-
"[trails-sdk][gas-estimation] About to estimate gas limit for quote with params:",
|
|
2346
|
-
{
|
|
2347
|
-
account: account.address,
|
|
2348
|
-
to: originCallParamsBase.to,
|
|
2349
|
-
data: originCallParamsBase.data,
|
|
2350
|
-
value: originCallParamsBase.value,
|
|
2351
|
-
},
|
|
2352
|
-
)
|
|
2353
|
-
|
|
2354
|
-
const estimatedGasLimitForQuote = await estimateGasLimit(
|
|
2355
|
-
effectivePublicClient,
|
|
2356
|
-
{
|
|
2357
|
-
account: account.address,
|
|
2358
|
-
to: originCallParamsBase.to,
|
|
2359
|
-
data: originCallParamsBase.data,
|
|
2360
|
-
value: BigInt(originCallParamsBase.value),
|
|
2361
|
-
},
|
|
2362
|
-
"quote",
|
|
2363
|
-
)
|
|
2364
|
-
|
|
2365
|
-
logger.console.log(
|
|
2366
|
-
"[trails-sdk][gas-estimation] Estimated gas limit for quote:",
|
|
2367
|
-
estimatedGasLimitForQuote,
|
|
2368
|
-
)
|
|
2369
|
-
|
|
2370
|
-
const quote = await getNormalizedQuoteObject({
|
|
2371
|
-
originDepositAddress: recipient,
|
|
2372
|
-
destinationDepositAddress: recipient,
|
|
2373
|
-
destinationAddress: recipient,
|
|
2374
|
-
destinationCalldata,
|
|
2375
|
-
originAmount: swapAmount, // fromAmount is same as toAmount for same chain same token
|
|
2376
|
-
destinationAmount: swapAmount,
|
|
2377
|
-
originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
|
|
2378
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
|
|
2379
|
-
originTokenAddress: effectiveOriginTokenAddress,
|
|
2380
|
-
destinationTokenAddress: effectiveOriginTokenAddress,
|
|
2381
|
-
transactionStates,
|
|
2382
|
-
originChainId: effectiveOriginChainId,
|
|
2383
|
-
destinationChainId: effectiveOriginChainId,
|
|
2384
|
-
originNativeTokenPriceUsd,
|
|
2385
|
-
slippageTolerance,
|
|
2386
|
-
quoteProvider: "",
|
|
2387
|
-
noSufficientBalance,
|
|
2388
|
-
estimatedGasLimit: estimatedGasLimitForQuote,
|
|
2389
|
-
})
|
|
2390
|
-
|
|
2391
|
-
// Call onCheckoutQuote callback if provided
|
|
2392
|
-
if (checkoutOnHandlers?.triggerCheckoutQuote) {
|
|
2393
|
-
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
2394
|
-
}
|
|
2395
|
-
|
|
2396
|
-
return {
|
|
2397
|
-
quote,
|
|
2398
|
-
send: async ({
|
|
2399
|
-
onOriginSend,
|
|
2400
|
-
}: {
|
|
2401
|
-
onOriginSend?: () => void
|
|
2402
|
-
}): Promise<SendReturn> => {
|
|
2403
|
-
try {
|
|
2404
|
-
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
2405
|
-
account,
|
|
2406
|
-
tokenAddress: effectiveOriginTokenAddress,
|
|
2407
|
-
depositAmount: swapAmount,
|
|
2408
|
-
publicClient: effectivePublicClient,
|
|
2409
|
-
})
|
|
2410
|
-
|
|
2411
|
-
if (!hasEnoughBalance) {
|
|
2412
|
-
throw balanceError
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
const depositAmountFormatted = Number(
|
|
2416
|
-
formatUnits(BigInt(swapAmount), originTokenDecimals),
|
|
2417
|
-
)
|
|
2418
|
-
const depositAmountUsd = calcAmountUsdPrice({
|
|
2419
|
-
amount: depositAmountFormatted,
|
|
2420
|
-
usdPrice: sourceTokenPriceUsd,
|
|
2421
|
-
})
|
|
2422
|
-
|
|
2423
|
-
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
2424
|
-
|
|
2425
|
-
// Build origin call params (reusing the same logic as quote estimation)
|
|
2426
|
-
const originCallParamsBase = buildSameChainTransactionParams({
|
|
2427
|
-
hasCustomCalldata,
|
|
2428
|
-
recipient,
|
|
2429
|
-
effectiveOriginTokenAddress,
|
|
2430
|
-
destinationCalldata,
|
|
2431
|
-
swapAmount,
|
|
2432
|
-
effectiveOriginChainId,
|
|
2433
|
-
effectiveOriginChain,
|
|
2434
|
-
})
|
|
2435
|
-
|
|
2436
|
-
// Estimate gas limit for consistency with actual transaction
|
|
2437
|
-
const gasLimit = await estimateGasLimit(
|
|
2438
|
-
effectivePublicClient,
|
|
2439
|
-
{
|
|
2440
|
-
account: account.address,
|
|
2441
|
-
to: originCallParamsBase.to,
|
|
2442
|
-
data: originCallParamsBase.data,
|
|
2443
|
-
value: BigInt(originCallParamsBase.value),
|
|
2444
|
-
},
|
|
2445
|
-
"send",
|
|
2446
|
-
)
|
|
2447
|
-
|
|
2448
|
-
const originCallParams = {
|
|
2449
|
-
...originCallParamsBase,
|
|
2450
|
-
gasLimit,
|
|
2451
|
-
}
|
|
2452
|
-
|
|
2453
|
-
logger.console.log("[trails-sdk] origin call params", originCallParams)
|
|
2454
|
-
|
|
2455
|
-
let originUserTxReceipt: TransactionReceipt | null = null
|
|
2456
|
-
const originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
2457
|
-
const destinationMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
2458
|
-
|
|
2459
|
-
await attemptSwitchChain({
|
|
2460
|
-
walletClient,
|
|
2461
|
-
desiredChainId: effectiveOriginChainId,
|
|
2462
|
-
})
|
|
2463
|
-
if (!dryMode) {
|
|
2464
|
-
try {
|
|
2465
|
-
onTransactionStateChange([
|
|
2466
|
-
{
|
|
2467
|
-
transactionHash: "",
|
|
2468
|
-
explorerUrl: "",
|
|
2469
|
-
chainId: effectiveOriginChainId,
|
|
2470
|
-
state: "pending",
|
|
2471
|
-
label: "Execute",
|
|
2472
|
-
},
|
|
2473
|
-
])
|
|
2474
|
-
} catch (error) {
|
|
2475
|
-
logger.console.error(
|
|
2476
|
-
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2477
|
-
error,
|
|
2478
|
-
)
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
|
-
// For gasless transactions with custom calldata, use the gasless deposit flow
|
|
2482
|
-
if (gasless && hasCustomCalldata) {
|
|
2483
|
-
logger.console.log(
|
|
2484
|
-
"[trails-sdk] Using gasless intent entrypoint flow for same-chain transaction with custom calldata",
|
|
2485
|
-
)
|
|
2486
|
-
|
|
2487
|
-
// Fetch fee options for gasless deposit
|
|
2488
|
-
let feeOptions: any = null
|
|
2489
|
-
try {
|
|
2490
|
-
feeOptions = await getIntentEntrypointFeeOptions({
|
|
2491
|
-
trailsClient,
|
|
2492
|
-
userAddress: account.address as `0x${string}`,
|
|
2493
|
-
tokenAddress: effectiveOriginTokenAddress as `0x${string}`,
|
|
2494
|
-
amount: swapAmount,
|
|
2495
|
-
intentAddress: recipient as `0x${string}`,
|
|
2496
|
-
chainId: effectiveOriginChainId,
|
|
2497
|
-
})
|
|
2498
|
-
logger.console.log(
|
|
2499
|
-
"[trails-sdk] [GASLESS-FLOW] Fetched intent entrypoint fee options for same-chain transaction:",
|
|
2500
|
-
feeOptions,
|
|
2501
|
-
)
|
|
2502
|
-
} catch (error) {
|
|
2503
|
-
logger.console.error(
|
|
2504
|
-
"[trails-sdk] [GASLESS-FLOW] Error fetching fee options for same-chain gasless transaction:",
|
|
2505
|
-
error,
|
|
2506
|
-
)
|
|
2507
|
-
}
|
|
2508
|
-
|
|
2509
|
-
const receipt = await attemptGaslessDeposit({
|
|
2510
|
-
account,
|
|
2511
|
-
walletClient,
|
|
2512
|
-
chain: effectiveOriginChain,
|
|
2513
|
-
depositTokenAddress: effectiveOriginTokenAddress,
|
|
2514
|
-
depositTokenAmount: swapAmount,
|
|
2515
|
-
depositRecipient: recipient,
|
|
2516
|
-
onOriginSend: () => {}, // No-op callback
|
|
2517
|
-
feeOptions,
|
|
2518
|
-
paymasterUrl,
|
|
2519
|
-
selectedFeeToken,
|
|
2520
|
-
trailsClient,
|
|
2521
|
-
originRelayer,
|
|
2522
|
-
})
|
|
2523
|
-
|
|
2524
|
-
if (receipt) {
|
|
2525
|
-
originUserTxReceipt = receipt
|
|
2526
|
-
|
|
2527
|
-
// Track the confirmed transaction
|
|
2528
|
-
trackTransactionConfirmed({
|
|
2529
|
-
transactionHash: receipt.transactionHash,
|
|
2530
|
-
chainId: effectiveOriginChainId,
|
|
2531
|
-
userAddress: account.address,
|
|
2532
|
-
blockNumber: Number(receipt.blockNumber),
|
|
2533
|
-
originTokenAddress,
|
|
2534
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2535
|
-
})
|
|
2536
|
-
|
|
2537
|
-
// Toast will be shown after transaction state analysis
|
|
2538
|
-
|
|
2539
|
-
// Update transaction state
|
|
2540
|
-
try {
|
|
2541
|
-
onTransactionStateChange([
|
|
2542
|
-
getTransactionStateFromReceipt(
|
|
2543
|
-
receipt,
|
|
2544
|
-
effectiveOriginChainId,
|
|
2545
|
-
transactionStates[0]?.label,
|
|
2546
|
-
),
|
|
2547
|
-
])
|
|
2548
|
-
} catch (error) {
|
|
2549
|
-
logger.console.error(
|
|
2550
|
-
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2551
|
-
error,
|
|
2552
|
-
)
|
|
2553
|
-
}
|
|
2554
|
-
}
|
|
2555
|
-
} else {
|
|
2556
|
-
// Non-gasless flow: handle approval if needed
|
|
2557
|
-
if (hasCustomCalldata) {
|
|
2558
|
-
try {
|
|
2559
|
-
const needsApproval = await getNeedsApproval({
|
|
2560
|
-
publicClient: effectivePublicClient,
|
|
2561
|
-
token: effectiveOriginTokenAddress,
|
|
2562
|
-
account: account.address,
|
|
2563
|
-
spender: recipient,
|
|
2564
|
-
amount: maxUint256,
|
|
2565
|
-
})
|
|
2566
|
-
|
|
2567
|
-
if (needsApproval) {
|
|
2568
|
-
const txHash = await approveERC20({
|
|
2569
|
-
walletClient,
|
|
2570
|
-
tokenAddress: effectiveOriginTokenAddress,
|
|
2571
|
-
spender: recipient,
|
|
2572
|
-
amount: maxUint256,
|
|
2573
|
-
chain: effectiveOriginChain,
|
|
2574
|
-
})
|
|
2575
|
-
|
|
2576
|
-
logger.console.log("waiting for approve", txHash)
|
|
2577
|
-
await effectivePublicClient.waitForTransactionReceipt({
|
|
2578
|
-
hash: txHash,
|
|
2579
|
-
})
|
|
2580
|
-
logger.console.log("approve done")
|
|
2581
|
-
}
|
|
2582
|
-
} catch (error) {
|
|
2583
|
-
logger.console.error(
|
|
2584
|
-
"[trails-sdk] Error approving ERC20",
|
|
2585
|
-
error,
|
|
2586
|
-
)
|
|
2587
|
-
}
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
|
-
// Show persistent toast for checkout flow
|
|
2591
|
-
updatePersistentToast(
|
|
2592
|
-
"Payment Started",
|
|
2593
|
-
"Waiting for wallet confirmation...",
|
|
2594
|
-
"info",
|
|
2595
|
-
)
|
|
2596
|
-
|
|
2597
|
-
logger.console.log(
|
|
2598
|
-
"[trails-sdk] origin call params",
|
|
2599
|
-
originCallParams,
|
|
2600
|
-
)
|
|
2601
|
-
const txHash = await sendOriginTransaction(
|
|
2602
|
-
account,
|
|
2603
|
-
walletClient,
|
|
2604
|
-
originCallParams as any,
|
|
2605
|
-
{
|
|
2606
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2607
|
-
},
|
|
2608
|
-
) // TODO: Add proper type
|
|
2609
|
-
|
|
2610
|
-
logger.console.log("[trails-sdk] origin tx", txHash)
|
|
2611
|
-
|
|
2612
|
-
if (onOriginSend) {
|
|
2613
|
-
onOriginSend()
|
|
2614
|
-
}
|
|
2615
|
-
|
|
2616
|
-
// Wait for transaction receipt
|
|
2617
|
-
const receipt =
|
|
2618
|
-
await effectivePublicClient.waitForTransactionReceipt({
|
|
2619
|
-
hash: txHash,
|
|
2620
|
-
})
|
|
2621
|
-
logger.console.log("[trails-sdk] receipt", receipt)
|
|
2622
|
-
originUserTxReceipt = receipt
|
|
2623
|
-
|
|
2624
|
-
trackTransactionConfirmed({
|
|
2625
|
-
transactionHash: txHash,
|
|
2626
|
-
chainId: effectiveOriginChainId,
|
|
2627
|
-
userAddress: account.address,
|
|
2628
|
-
blockNumber: Number(receipt.blockNumber),
|
|
2629
|
-
originTokenAddress,
|
|
2630
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2631
|
-
})
|
|
2632
|
-
|
|
2633
|
-
// Toast will be shown after transaction state analysis
|
|
2634
|
-
|
|
2635
|
-
try {
|
|
2636
|
-
onTransactionStateChange([
|
|
2637
|
-
getTransactionStateFromReceipt(
|
|
2638
|
-
originUserTxReceipt,
|
|
2639
|
-
effectiveOriginChainId,
|
|
2640
|
-
transactionStates[0]?.label,
|
|
2641
|
-
),
|
|
2642
|
-
])
|
|
2643
|
-
} catch (error) {
|
|
2644
|
-
logger.console.error(
|
|
2645
|
-
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2646
|
-
error,
|
|
2647
|
-
)
|
|
2648
|
-
}
|
|
2649
|
-
}
|
|
2650
|
-
|
|
2651
|
-
// Show conditional toast based on transaction status
|
|
2652
|
-
const chainInfo = getChainInfo(effectiveOriginChainId)
|
|
2653
|
-
if (originUserTxReceipt) {
|
|
2654
|
-
if (originUserTxReceipt?.status === "success") {
|
|
2655
|
-
updatePersistentToast(
|
|
2656
|
-
"Transfer Confirmed",
|
|
2657
|
-
`Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
|
|
2658
|
-
"info",
|
|
2659
|
-
)
|
|
2660
|
-
} else {
|
|
2661
|
-
updatePersistentToast(
|
|
2662
|
-
"Transfer Failed",
|
|
2663
|
-
`Your transaction on ${chainInfo?.name || "chain"} failed`,
|
|
2664
|
-
"error",
|
|
2665
|
-
)
|
|
2666
|
-
}
|
|
2667
|
-
}
|
|
2668
|
-
|
|
2669
|
-
// Track payment completion for same-chain same-token transaction
|
|
2670
|
-
if (originUserTxReceipt && originUserTxReceipt.status === "success") {
|
|
2671
|
-
// Check if any transaction failed or was refunded
|
|
2672
|
-
const hasAnyCallFailed = transactionStates.some((tx) =>
|
|
2673
|
-
tx?.decodedGuestModuleEvents?.some(
|
|
2674
|
-
(e: any) => e?.type === "CallFailed",
|
|
2675
|
-
),
|
|
2676
|
-
)
|
|
2677
|
-
const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
|
|
2678
|
-
const txStatus =
|
|
2679
|
-
!hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
|
|
2680
|
-
|
|
2681
|
-
// Always track payment completion regardless of success/failure
|
|
2682
|
-
trackPaymentCompleted({
|
|
2683
|
-
userAddress: account.address,
|
|
2684
|
-
originTxHash: originUserTxReceipt.transactionHash,
|
|
2685
|
-
originChainId: effectiveOriginChainId, // Same chain
|
|
2686
|
-
mode,
|
|
2687
|
-
fundMethod,
|
|
2688
|
-
originTokenAddress,
|
|
2689
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2690
|
-
destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
|
|
2691
|
-
})
|
|
2692
|
-
|
|
2693
|
-
// Call onCheckoutComplete callback with transaction status
|
|
2694
|
-
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
2695
|
-
checkoutOnHandlers.triggerCheckoutComplete(txStatus)
|
|
2696
|
-
}
|
|
2697
|
-
} else if (originUserTxReceipt) {
|
|
2698
|
-
trackPaymentError({
|
|
2699
|
-
error: "Transaction failed",
|
|
2700
|
-
userAddress: account.address,
|
|
2701
|
-
mode,
|
|
2702
|
-
fundMethod,
|
|
2703
|
-
originTokenAddress,
|
|
2704
|
-
})
|
|
2705
|
-
|
|
2706
|
-
// Call onCheckoutError callback if provided
|
|
2707
|
-
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2708
|
-
checkoutOnHandlers.triggerCheckoutError("Transaction failed")
|
|
2709
|
-
}
|
|
2710
|
-
}
|
|
2711
|
-
}
|
|
2712
|
-
|
|
2713
|
-
return {
|
|
2714
|
-
originUserTxReceipt,
|
|
2715
|
-
originMetaTxnReceipt,
|
|
2716
|
-
destinationMetaTxnReceipt,
|
|
2717
|
-
totalCompletionSeconds: 0,
|
|
2718
|
-
}
|
|
2719
|
-
} catch (error) {
|
|
2720
|
-
const errorMessage =
|
|
2721
|
-
error instanceof Error
|
|
2722
|
-
? error.message
|
|
2723
|
-
: "Unknown error occurred during transaction"
|
|
2724
|
-
logger.console.error(
|
|
2725
|
-
"[trails-sdk] Error in sendHandlerForSameChainSameToken:",
|
|
2726
|
-
error,
|
|
2727
|
-
)
|
|
2728
|
-
|
|
2729
|
-
// Track payment error
|
|
2730
|
-
trackPaymentError({
|
|
2731
|
-
error: errorMessage,
|
|
2732
|
-
userAddress: account.address,
|
|
2733
|
-
mode,
|
|
2734
|
-
fundMethod,
|
|
2735
|
-
originTokenAddress,
|
|
2736
|
-
})
|
|
2737
|
-
|
|
2738
|
-
// Call onCheckoutError callback if provided
|
|
2739
|
-
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2740
|
-
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
2741
|
-
}
|
|
2742
|
-
|
|
2743
|
-
// Re-throw the error so caller can handle if needed
|
|
2744
|
-
throw error
|
|
2745
|
-
}
|
|
2746
|
-
},
|
|
2747
|
-
}
|
|
2748
|
-
}
|
|
2749
|
-
|
|
2750
|
-
// This handler uses relay sdk directly
|
|
2751
|
-
async function _sendHandlerForSameChainDifferentToken({
|
|
2752
|
-
originTokenAddress,
|
|
2753
|
-
originTokenDecimals,
|
|
2754
|
-
swapAmount,
|
|
2755
|
-
destinationTokenAddress,
|
|
2756
|
-
destinationTokenDecimals,
|
|
2757
|
-
destinationCalldata,
|
|
2758
|
-
recipient,
|
|
2759
|
-
originChainId,
|
|
2760
|
-
walletClient,
|
|
2761
|
-
publicClient,
|
|
2762
|
-
account,
|
|
2763
|
-
tradeType = TradeType.EXACT_OUTPUT,
|
|
2764
|
-
slippageTolerance,
|
|
2765
|
-
onTransactionStateChange,
|
|
2766
|
-
transactionStates,
|
|
2767
|
-
sourceTokenPriceUsd,
|
|
2768
|
-
destinationTokenPriceUsd,
|
|
2769
|
-
originNativeTokenPriceUsd,
|
|
2770
|
-
mode,
|
|
2771
|
-
fundMethod,
|
|
2772
|
-
originTokenSymbol,
|
|
2773
|
-
destinationTokenSymbol,
|
|
2774
|
-
}: {
|
|
2775
|
-
originTokenAddress: string
|
|
2776
|
-
originTokenDecimals: number
|
|
2777
|
-
swapAmount: string
|
|
2778
|
-
destinationTokenAddress: string
|
|
2779
|
-
destinationTokenDecimals: number
|
|
2780
|
-
destinationCalldata?: string
|
|
2781
|
-
recipient: string
|
|
2782
|
-
originChainId: number
|
|
2783
|
-
walletClient: WalletClient
|
|
2784
|
-
publicClient: PublicClient
|
|
2785
|
-
account: Account
|
|
2786
|
-
tradeType?: TradeType
|
|
2787
|
-
slippageTolerance?: string
|
|
2788
|
-
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
2789
|
-
transactionStates: TransactionState[]
|
|
2790
|
-
sourceTokenPriceUsd?: number | null
|
|
2791
|
-
destinationTokenPriceUsd?: number | null
|
|
2792
|
-
originNativeTokenPriceUsd?: number | null
|
|
2793
|
-
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
2794
|
-
fundMethod?: string
|
|
2795
|
-
originTokenSymbol: string
|
|
2796
|
-
destinationTokenSymbol: string
|
|
2797
|
-
}): Promise<PrepareSendReturn> {
|
|
2798
|
-
const destinationTxs: { to: string; value: string; data: string }[] = []
|
|
2799
|
-
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
2800
|
-
if (hasCustomCalldata && tradeType === TradeType.EXACT_OUTPUT) {
|
|
2801
|
-
destinationTxs.push({
|
|
2802
|
-
to: recipient,
|
|
2803
|
-
value: destinationTokenAddress === zeroAddress ? swapAmount : "0",
|
|
2804
|
-
data: destinationCalldata as `0x${string}`,
|
|
2805
|
-
})
|
|
2806
|
-
} else if (hasCustomCalldata && tradeType === TradeType.EXACT_INPUT) {
|
|
2807
|
-
destinationTxs.push({
|
|
2808
|
-
to: recipient,
|
|
2809
|
-
value: "0",
|
|
2810
|
-
data: destinationCalldata as `0x${string}`,
|
|
2811
|
-
})
|
|
2812
|
-
}
|
|
2813
|
-
|
|
2814
|
-
const quote = await getRelaySDKQuote({
|
|
2815
|
-
wallet: walletClient,
|
|
2816
|
-
chainId: originChainId,
|
|
2817
|
-
amount: swapAmount,
|
|
2818
|
-
currency: originTokenAddress,
|
|
2819
|
-
toCurrency: destinationTokenAddress,
|
|
2820
|
-
txs: destinationTxs,
|
|
2821
|
-
tradeType: tradeType as unknown as RelayTradeType,
|
|
2822
|
-
slippageTolerance: slippageTolerance,
|
|
2823
|
-
recipient: hasCustomCalldata ? recipient : undefined,
|
|
2824
|
-
})
|
|
2825
|
-
|
|
2826
|
-
logger.console.log("[trails-sdk] relaysdk quote", quote)
|
|
2827
|
-
let depositAmount = "0"
|
|
2828
|
-
let destinationTokenAmount = "0"
|
|
2829
|
-
|
|
2830
|
-
if (tradeType === TradeType.EXACT_INPUT) {
|
|
2831
|
-
depositAmount = swapAmount
|
|
2832
|
-
destinationTokenAmount = quote?.details?.currencyOut?.amount?.toString()
|
|
2833
|
-
} else {
|
|
2834
|
-
try {
|
|
2835
|
-
destinationTokenAmount = swapAmount
|
|
2836
|
-
depositAmount = quote.steps?.[0]?.items?.[0]?.data?.value
|
|
2837
|
-
if (originTokenAddress !== zeroAddress) {
|
|
2838
|
-
const decoded = decodeFunctionData({
|
|
2839
|
-
abi: erc20Abi,
|
|
2840
|
-
data: quote.steps?.[0]?.items?.[0]?.data?.data,
|
|
2841
|
-
})
|
|
2842
|
-
if (decoded.functionName === "approve") {
|
|
2843
|
-
depositAmount = decoded.args[1].toString()
|
|
2844
|
-
}
|
|
2845
|
-
if (decoded.functionName === "transfer") {
|
|
2846
|
-
depositAmount = decoded.args[1].toString()
|
|
2847
|
-
}
|
|
2848
|
-
}
|
|
2849
|
-
} catch (error) {
|
|
2850
|
-
logger.console.error("[trails-sdk] Error decoding function data:", error)
|
|
2851
|
-
}
|
|
2852
|
-
}
|
|
2853
|
-
|
|
2854
|
-
const depositOriginAddress =
|
|
2855
|
-
quote?.steps?.[quote?.steps?.length - 1]?.items?.[
|
|
2856
|
-
quote?.steps?.[quote?.steps?.length - 1]?.items?.length - 1
|
|
2857
|
-
]?.data?.to
|
|
2858
|
-
|
|
2859
|
-
if (
|
|
2860
|
-
quote?.details?.currencyIn?.amountFormatted &&
|
|
2861
|
-
quote?.details?.currencyIn?.amountUsd
|
|
2862
|
-
) {
|
|
2863
|
-
const quoteProviderSourceTokenPriceUsd =
|
|
2864
|
-
Number(quote.details.currencyIn.amountUsd) /
|
|
2865
|
-
Number(quote.details.currencyIn.amountFormatted)
|
|
2866
|
-
if (quoteProviderSourceTokenPriceUsd) {
|
|
2867
|
-
sourceTokenPriceUsd = quoteProviderSourceTokenPriceUsd
|
|
2868
|
-
}
|
|
2869
|
-
}
|
|
2870
|
-
|
|
2871
|
-
if (
|
|
2872
|
-
quote?.details?.currencyOut?.amountFormatted &&
|
|
2873
|
-
quote?.details?.currencyOut?.amountUsd
|
|
2874
|
-
) {
|
|
2875
|
-
const quoteProviderDestinationTokenPriceUsd =
|
|
2876
|
-
Number(quote.details.currencyOut.amountUsd) /
|
|
2877
|
-
Number(quote.details.currencyOut.amountFormatted)
|
|
2878
|
-
if (quoteProviderDestinationTokenPriceUsd) {
|
|
2879
|
-
destinationTokenPriceUsd = quoteProviderDestinationTokenPriceUsd
|
|
2880
|
-
}
|
|
2881
|
-
}
|
|
2882
|
-
|
|
2883
|
-
const normalizedQuote = await getNormalizedQuoteObject({
|
|
2884
|
-
originDepositAddress: depositOriginAddress,
|
|
2885
|
-
destinationDepositAddress: recipient,
|
|
2886
|
-
destinationAddress: recipient,
|
|
2887
|
-
destinationCalldata,
|
|
2888
|
-
originAmount: depositAmount,
|
|
2889
|
-
destinationAmount: destinationTokenAmount,
|
|
2890
|
-
originTokenAddress: originTokenAddress,
|
|
2891
|
-
destinationTokenAddress: destinationTokenAddress,
|
|
2892
|
-
originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
|
|
2893
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
|
|
2894
|
-
fees: getFeesFromRelaySdkQuote(quote),
|
|
2895
|
-
slippageTolerance: getSlippageToleranceFromRelaySdkQuote(quote),
|
|
2896
|
-
priceImpact: getPriceImpactFromRelaySdkQuote(quote),
|
|
2897
|
-
priceImpactUsd: getPriceImpactUsdFromRelaySdkQuote(quote),
|
|
2898
|
-
transactionStates,
|
|
2899
|
-
originChainId,
|
|
2900
|
-
destinationChainId: originChainId,
|
|
2901
|
-
originNativeTokenPriceUsd,
|
|
2902
|
-
quoteProvider: "relay",
|
|
2903
|
-
})
|
|
2904
|
-
|
|
2905
|
-
return {
|
|
2906
|
-
quote: normalizedQuote,
|
|
2907
|
-
send: async ({
|
|
2908
|
-
onOriginSend,
|
|
2909
|
-
}: {
|
|
2910
|
-
onOriginSend?: () => void
|
|
2911
|
-
}): Promise<SendReturn> => {
|
|
2912
|
-
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
2913
|
-
account,
|
|
2914
|
-
tokenAddress: originTokenAddress,
|
|
2915
|
-
depositAmount,
|
|
2916
|
-
publicClient,
|
|
2917
|
-
})
|
|
2918
|
-
|
|
2919
|
-
if (!hasEnoughBalance) {
|
|
2920
|
-
throw balanceError
|
|
2921
|
-
}
|
|
2922
|
-
|
|
2923
|
-
await attemptSwitchChain({
|
|
2924
|
-
walletClient,
|
|
2925
|
-
desiredChainId: originChainId,
|
|
2926
|
-
})
|
|
2927
|
-
|
|
2928
|
-
const result = await executeSimpleRelayTransaction(quote, walletClient)
|
|
2929
|
-
logger.console.log("[trails-sdk] relaysdk result", result)
|
|
2930
|
-
|
|
2931
|
-
const txHash = getTxHashFromRelayResult(result)
|
|
2932
|
-
|
|
2933
|
-
if (onOriginSend) {
|
|
2934
|
-
onOriginSend()
|
|
2935
|
-
}
|
|
2936
|
-
|
|
2937
|
-
const originUserTxReceipt = await publicClient.waitForTransactionReceipt({
|
|
2938
|
-
hash: txHash as `0x${string}`,
|
|
2939
|
-
})
|
|
2940
|
-
|
|
2941
|
-
transactionStates[0] = getTransactionStateFromReceipt(
|
|
2942
|
-
originUserTxReceipt,
|
|
2943
|
-
originChainId,
|
|
2944
|
-
transactionStates[0]?.label,
|
|
2945
|
-
)
|
|
2946
|
-
|
|
2947
|
-
try {
|
|
2948
|
-
onTransactionStateChange(transactionStates)
|
|
2949
|
-
} catch (error) {
|
|
2950
|
-
logger.console.error(
|
|
2951
|
-
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2952
|
-
error,
|
|
2953
|
-
)
|
|
2954
|
-
}
|
|
2955
|
-
|
|
2956
|
-
const depositAmountFormatted = Number(
|
|
2957
|
-
formatUnits(BigInt(depositAmount), originTokenDecimals),
|
|
2958
|
-
)
|
|
2959
|
-
|
|
2960
|
-
const depositAmountUsd = calcAmountUsdPrice({
|
|
2961
|
-
amount: depositAmountFormatted,
|
|
2962
|
-
usdPrice: sourceTokenPriceUsd,
|
|
2963
|
-
})
|
|
2964
|
-
|
|
2965
|
-
const destinationTokenAmountFormatted = Number(
|
|
2966
|
-
formatUnits(BigInt(destinationTokenAmount), destinationTokenDecimals),
|
|
2967
|
-
)
|
|
2968
|
-
|
|
2969
|
-
const destinationTokenAmountUsd = calcAmountUsdPrice({
|
|
2970
|
-
amount: Number(destinationTokenAmountFormatted),
|
|
2971
|
-
usdPrice: destinationTokenPriceUsd,
|
|
2972
|
-
})
|
|
2973
|
-
|
|
2974
|
-
// Track payment completion for same-chain different-token transaction
|
|
2975
|
-
if (originUserTxReceipt && originUserTxReceipt.status === "success") {
|
|
2976
|
-
trackPaymentCompleted({
|
|
2977
|
-
userAddress: account.address,
|
|
2978
|
-
originTxHash: originUserTxReceipt.transactionHash,
|
|
2979
|
-
originChainId,
|
|
2980
|
-
destinationChainId: originChainId, // Same chain
|
|
2981
|
-
mode,
|
|
2982
|
-
fundMethod,
|
|
2983
|
-
originTokenAddress,
|
|
2984
|
-
originTokenSymbol,
|
|
2985
|
-
destinationTokenAddress,
|
|
2986
|
-
destinationTokenSymbol,
|
|
2987
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2988
|
-
destinationTokenAmountUsd: destinationTokenAmountUsd?.toString(),
|
|
2989
|
-
})
|
|
2990
|
-
} else if (originUserTxReceipt) {
|
|
2991
|
-
trackPaymentError({
|
|
2992
|
-
error: "Relay transaction failed",
|
|
2993
|
-
userAddress: account.address,
|
|
2994
|
-
mode,
|
|
2995
|
-
fundMethod,
|
|
2996
|
-
originTokenAddress,
|
|
2997
|
-
originTokenSymbol,
|
|
2998
|
-
destinationTokenAddress,
|
|
2999
|
-
destinationTokenSymbol,
|
|
3000
|
-
})
|
|
3001
|
-
}
|
|
3002
|
-
|
|
3003
|
-
return {
|
|
3004
|
-
originUserTxReceipt: originUserTxReceipt,
|
|
3005
|
-
originMetaTxnReceipt: null,
|
|
3006
|
-
destinationMetaTxnReceipt: null,
|
|
3007
|
-
totalCompletionSeconds: 0,
|
|
3008
|
-
}
|
|
3009
|
-
},
|
|
3010
|
-
}
|
|
3011
|
-
}
|
|
3012
|
-
|
|
3013
|
-
async function attemptGaslessDeposit({
|
|
3014
|
-
paymasterUrl,
|
|
3015
|
-
depositTokenAddress,
|
|
3016
|
-
depositTokenAmount,
|
|
3017
|
-
depositRecipient,
|
|
3018
|
-
onOriginSend,
|
|
3019
|
-
walletClient,
|
|
3020
|
-
chain,
|
|
3021
|
-
account,
|
|
3022
|
-
trailsClient,
|
|
3023
|
-
originRelayer,
|
|
3024
|
-
feeOptions,
|
|
3025
|
-
selectedFeeToken,
|
|
3026
|
-
}: {
|
|
3027
|
-
paymasterUrl?: string
|
|
3028
|
-
depositTokenAddress: string
|
|
3029
|
-
depositTokenAmount: string
|
|
3030
|
-
depositRecipient: string
|
|
3031
|
-
onOriginSend?: () => void
|
|
3032
|
-
walletClient: WalletClient
|
|
3033
|
-
chain: Chain
|
|
3034
|
-
account: Account
|
|
3035
|
-
trailsClient: TrailsAPIClient
|
|
3036
|
-
originRelayer: Relayer.RpcRelayer
|
|
3037
|
-
feeOptions: any
|
|
3038
|
-
selectedFeeToken?: any
|
|
3039
|
-
}): Promise<TransactionReceipt | null> {
|
|
3040
|
-
let originUserTxReceipt: TransactionReceipt | null = null
|
|
3041
|
-
const originChainId = chain.id
|
|
3042
|
-
|
|
3043
|
-
logger.console.log(
|
|
3044
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] attemptGaslessDeposit called with:",
|
|
3045
|
-
{
|
|
3046
|
-
originChainId,
|
|
3047
|
-
depositTokenAddress,
|
|
3048
|
-
depositTokenAmount,
|
|
3049
|
-
depositRecipient,
|
|
3050
|
-
hasFeeOptions: !!feeOptions,
|
|
3051
|
-
feeOptionsLength: feeOptions?.feeOptions?.length,
|
|
3052
|
-
selectedFeeToken,
|
|
3053
|
-
hasSelectedFeeToken: !!selectedFeeToken,
|
|
3054
|
-
paymasterUrl,
|
|
3055
|
-
},
|
|
3056
|
-
)
|
|
3057
|
-
|
|
3058
|
-
const publicClient = createPublicClient({
|
|
3059
|
-
chain,
|
|
3060
|
-
transport: http(),
|
|
3061
|
-
})
|
|
3062
|
-
|
|
3063
|
-
logger.console.log("[trails-sdk] [GASLESS-FLOW] Intent entrypoint check:", {
|
|
3064
|
-
chainId: chain.id,
|
|
3065
|
-
chainName: chain.name,
|
|
3066
|
-
intentEntrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
|
|
3067
|
-
})
|
|
3068
|
-
|
|
3069
|
-
// NEW FLOW: Use Intent Entrypoint API with permit2 support
|
|
3070
|
-
logger.console.log(
|
|
3071
|
-
"[trails-sdk] Using Intent Entrypoint API flow with permit2 support for gasless deposit",
|
|
3072
|
-
)
|
|
3073
|
-
|
|
3074
|
-
// Switch to correct chain before requesting signatures
|
|
3075
|
-
logger.console.log(
|
|
3076
|
-
"[trails-sdk] [GASLESS-FLOW] Switching to chain before permit/intent signatures",
|
|
3077
|
-
{ originChainId },
|
|
3078
|
-
)
|
|
3079
|
-
await attemptSwitchChain({
|
|
3080
|
-
walletClient,
|
|
3081
|
-
desiredChainId: originChainId,
|
|
3082
|
-
})
|
|
3083
|
-
|
|
3084
|
-
try {
|
|
3085
|
-
if (paymasterUrl) {
|
|
3086
|
-
logger.console.log(
|
|
3087
|
-
"[trails-sdk] [GASLESS-FLOW] doing gasless with paymaster",
|
|
3088
|
-
)
|
|
3089
|
-
|
|
3090
|
-
// Switch to correct chain before requesting signatures
|
|
3091
|
-
logger.console.log(
|
|
3092
|
-
"[trails-sdk] [GASLESS-FLOW] Switching chain for paymaster flow",
|
|
3093
|
-
)
|
|
3094
|
-
await attemptSwitchChain({
|
|
3095
|
-
walletClient,
|
|
3096
|
-
desiredChainId: originChainId,
|
|
3097
|
-
})
|
|
3098
|
-
|
|
3099
|
-
const delegatorSmartAccount = await getDelegatorSmartAccount({
|
|
3100
|
-
publicClient,
|
|
3101
|
-
})
|
|
3102
|
-
|
|
3103
|
-
const calls: Array<{
|
|
3104
|
-
to: string
|
|
3105
|
-
data: string
|
|
3106
|
-
value: string
|
|
3107
|
-
}> = await getPaymasterGaslessTransaction({
|
|
3108
|
-
walletClient,
|
|
3109
|
-
chain,
|
|
3110
|
-
tokenAddress: depositTokenAddress as `0x${string}`,
|
|
3111
|
-
amount: BigInt(depositTokenAmount),
|
|
3112
|
-
recipient: depositRecipient as `0x${string}`,
|
|
3113
|
-
delegatorSmartAccount,
|
|
3114
|
-
})
|
|
3115
|
-
|
|
3116
|
-
logger.console.log("[trails-sdk] calls", calls)
|
|
3117
|
-
|
|
3118
|
-
const txHash = await sendPaymasterGaslessTransaction({
|
|
3119
|
-
walletClient,
|
|
3120
|
-
publicClient,
|
|
3121
|
-
chain,
|
|
3122
|
-
paymasterUrl,
|
|
3123
|
-
delegatorSmartAccount,
|
|
3124
|
-
calls,
|
|
3125
|
-
})
|
|
3126
|
-
|
|
3127
|
-
if (onOriginSend) {
|
|
3128
|
-
onOriginSend()
|
|
3129
|
-
}
|
|
3130
|
-
|
|
3131
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3132
|
-
hash: txHash as `0x${string}`,
|
|
3133
|
-
})
|
|
3134
|
-
logger.console.log("[trails-sdk] receipt", receipt)
|
|
3135
|
-
return receipt
|
|
3136
|
-
}
|
|
3137
|
-
|
|
3138
|
-
const deadline = Math.floor(Date.now() / 1000) + 3600 // 1 hour from now
|
|
3139
|
-
const hasFeeOptions = Boolean(
|
|
3140
|
-
feeOptions && feeOptions.feeOptions?.length > 0,
|
|
3141
|
-
)
|
|
3142
|
-
|
|
3143
|
-
// 1. Check if we need approval - check if we have enough allowance for this transaction (deposit + fee if same token)
|
|
3144
|
-
let requiredAmount = BigInt(depositTokenAmount)
|
|
3145
|
-
|
|
3146
|
-
// Match selectedFeeToken by tokenAddress to get the latest fee amount from feeOptions
|
|
3147
|
-
let selectedFeeOption = null
|
|
3148
|
-
if (selectedFeeToken && hasFeeOptions) {
|
|
3149
|
-
// Find matching fee option by tokenAddress to get latest amount
|
|
3150
|
-
selectedFeeOption = feeOptions.feeOptions.find(
|
|
3151
|
-
(opt: any) =>
|
|
3152
|
-
opt.tokenAddress?.toLowerCase() ===
|
|
3153
|
-
selectedFeeToken.tokenAddress?.toLowerCase(),
|
|
3154
|
-
)
|
|
3155
|
-
logger.console.log(
|
|
3156
|
-
"[trails-sdk] Matched selectedFeeToken to latest fee option:",
|
|
3157
|
-
{
|
|
3158
|
-
selectedFeeToken,
|
|
3159
|
-
matchedOption: selectedFeeOption,
|
|
3160
|
-
},
|
|
3161
|
-
)
|
|
3162
|
-
}
|
|
3163
|
-
|
|
3164
|
-
// Fallback to first fee option if no match or no selectedFeeToken
|
|
3165
|
-
if (!selectedFeeOption && hasFeeOptions) {
|
|
3166
|
-
selectedFeeOption = feeOptions.feeOptions[0]
|
|
3167
|
-
logger.console.log(
|
|
3168
|
-
"[trails-sdk] Using first fee option as fallback:",
|
|
3169
|
-
selectedFeeOption,
|
|
3170
|
-
)
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3173
|
-
// Only include fee in required amount if the fee token is the same as the deposit token
|
|
3174
|
-
if (selectedFeeOption?.amount && selectedFeeOption?.tokenAddress) {
|
|
3175
|
-
const feeTokenIsSameAsDepositToken =
|
|
3176
|
-
selectedFeeOption.tokenAddress.toLowerCase() ===
|
|
3177
|
-
depositTokenAddress.toLowerCase()
|
|
3178
|
-
|
|
3179
|
-
if (feeTokenIsSameAsDepositToken) {
|
|
3180
|
-
requiredAmount = requiredAmount + BigInt(selectedFeeOption.amount)
|
|
3181
|
-
logger.console.log(
|
|
3182
|
-
"[trails-sdk] Fee token matches deposit token, including fee in required approval amount:",
|
|
3183
|
-
{
|
|
3184
|
-
depositAmount: depositTokenAmount,
|
|
3185
|
-
feeAmount: selectedFeeOption.amount,
|
|
3186
|
-
feeTokenAddress: selectedFeeOption.tokenAddress,
|
|
3187
|
-
depositTokenAddress,
|
|
3188
|
-
totalRequired: requiredAmount.toString(),
|
|
3189
|
-
},
|
|
3190
|
-
)
|
|
3191
|
-
} else {
|
|
3192
|
-
logger.console.log(
|
|
3193
|
-
"[trails-sdk] Fee token differs from deposit token, separate approval will be needed:",
|
|
3194
|
-
{
|
|
3195
|
-
depositAmount: depositTokenAmount,
|
|
3196
|
-
depositTokenAddress,
|
|
3197
|
-
feeAmount: selectedFeeOption.amount,
|
|
3198
|
-
feeTokenAddress: selectedFeeOption.tokenAddress,
|
|
3199
|
-
depositTokenRequired: requiredAmount.toString(),
|
|
3200
|
-
},
|
|
3201
|
-
)
|
|
3202
|
-
}
|
|
3203
|
-
}
|
|
3204
|
-
|
|
3205
|
-
const needsApproval = await getNeedsIntentEntrypointApproval({
|
|
3206
|
-
client: publicClient,
|
|
3207
|
-
token: depositTokenAddress as `0x${string}`,
|
|
3208
|
-
account: account.address,
|
|
3209
|
-
entrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
|
|
3210
|
-
amount: requiredAmount, // Check if we have enough allowance for this specific transaction
|
|
3211
|
-
})
|
|
3212
|
-
|
|
3213
|
-
logger.console.log(
|
|
3214
|
-
"[trails-sdk] [GASLESS-FLOW] Checking permit requirements",
|
|
3215
|
-
{
|
|
3216
|
-
userAddress: account.address,
|
|
3217
|
-
tokenAddress: depositTokenAddress,
|
|
3218
|
-
depositAmount: depositTokenAmount,
|
|
3219
|
-
requiredAmount: requiredAmount.toString(),
|
|
3220
|
-
intentAddress: depositRecipient,
|
|
3221
|
-
chainID: originChainId,
|
|
3222
|
-
deadline,
|
|
3223
|
-
needsApproval,
|
|
3224
|
-
},
|
|
3225
|
-
)
|
|
3226
|
-
|
|
3227
|
-
// 2. Get permit signature if approval needed for deposit token
|
|
3228
|
-
// Note: Fee payment is handled by the backend/relayer, we only need permit for the deposit token
|
|
3229
|
-
let permitSignature: string | undefined
|
|
3230
|
-
let permitDeadline: number | undefined
|
|
3231
|
-
|
|
3232
|
-
if (needsApproval) {
|
|
3233
|
-
logger.console.log(
|
|
3234
|
-
"[trails-sdk] Getting permit signature for deposit token infinite approval",
|
|
3235
|
-
)
|
|
3236
|
-
|
|
3237
|
-
// Use infinite approval (maxUint256) so user doesn't need to approve again
|
|
3238
|
-
const permitAmount = maxUint256
|
|
3239
|
-
logger.console.log(
|
|
3240
|
-
"[trails-sdk] Using infinite approval for gasless deposits",
|
|
3241
|
-
{
|
|
3242
|
-
depositAmount: depositTokenAmount,
|
|
3243
|
-
depositTokenAddress,
|
|
3244
|
-
permitAmount: permitAmount.toString(),
|
|
3245
|
-
},
|
|
3246
|
-
)
|
|
3247
|
-
|
|
3248
|
-
const permitSig = await getPermitSignature({
|
|
3249
|
-
publicClient,
|
|
3250
|
-
walletClient,
|
|
3251
|
-
signer: account.address,
|
|
3252
|
-
spender: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
|
|
3253
|
-
tokenAddress: depositTokenAddress as `0x${string}`,
|
|
3254
|
-
amount: permitAmount, // Infinite approval
|
|
3255
|
-
chain,
|
|
3256
|
-
deadline: BigInt(deadline),
|
|
3257
|
-
})
|
|
3258
|
-
permitSignature = permitSig.signature
|
|
3259
|
-
permitDeadline = Number(permitSig.deadline)
|
|
3260
|
-
logger.console.log(
|
|
3261
|
-
"[trails-sdk] Deposit token permit signature obtained for infinite approval",
|
|
3262
|
-
)
|
|
3263
|
-
}
|
|
3264
|
-
|
|
3265
|
-
// 3. Get current nonce for the user
|
|
3266
|
-
logger.console.log("[trails-sdk] Getting user nonce")
|
|
3267
|
-
const nonce = await getUserNonce({
|
|
3268
|
-
publicClient,
|
|
3269
|
-
userAddress: account.address,
|
|
3270
|
-
intentEntrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
|
|
3271
|
-
})
|
|
3272
|
-
logger.console.log("[trails-sdk] User nonce:", nonce.toString())
|
|
3273
|
-
|
|
3274
|
-
// 4. Get intent signature
|
|
3275
|
-
logger.console.log("[trails-sdk] Requesting intent signature via EIP-712")
|
|
3276
|
-
// Get fee collector address from fee options response, or use selected fee option's collector
|
|
3277
|
-
const feeCollectorAddress = (selectedFeeOption?.feeCollector ||
|
|
3278
|
-
feeOptions?.feeCollector) as `0x${string}` | undefined
|
|
3279
|
-
|
|
3280
|
-
// Validate that we have a valid fee collector address
|
|
3281
|
-
if (!feeCollectorAddress || feeCollectorAddress === zeroAddress) {
|
|
3282
|
-
throw new Error(
|
|
3283
|
-
"[trails-sdk] Fee collector address not provided by API. Cannot proceed with gasless deposit. " +
|
|
3284
|
-
"Please ensure the API is returning feeCollector in the fee options response.",
|
|
3285
|
-
)
|
|
3286
|
-
}
|
|
3287
|
-
|
|
3288
|
-
logger.console.log(
|
|
3289
|
-
"[trails-sdk] Using fee collector address:",
|
|
3290
|
-
feeCollectorAddress,
|
|
3291
|
-
)
|
|
3292
|
-
|
|
3293
|
-
const { signature: intentSignature } = await signIntent({
|
|
3294
|
-
client: walletClient,
|
|
3295
|
-
intentParams: {
|
|
3296
|
-
user: account.address,
|
|
3297
|
-
token: depositTokenAddress as `0x${string}`,
|
|
3298
|
-
amount: BigInt(depositTokenAmount),
|
|
3299
|
-
intentAddress: depositRecipient as `0x${string}`,
|
|
3300
|
-
deadline: BigInt(deadline),
|
|
3301
|
-
chainId: originChainId,
|
|
3302
|
-
contractAddress: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
|
|
3303
|
-
nonce,
|
|
3304
|
-
feeAmount: BigInt(selectedFeeOption?.amount || "0"),
|
|
3305
|
-
feeCollector: feeCollectorAddress,
|
|
3306
|
-
},
|
|
3307
|
-
})
|
|
3308
|
-
logger.console.log("[trails-sdk] Intent signature received")
|
|
3309
|
-
|
|
3310
|
-
// 5. Call the deposit endpoint with permit support and optional fee
|
|
3311
|
-
logger.console.log(
|
|
3312
|
-
"[trails-sdk] Calling getIntentEntrypointDeposit with permit enabled",
|
|
3313
|
-
{ usePermit: needsApproval, hasFee: !!feeOptions },
|
|
3314
|
-
)
|
|
3315
|
-
|
|
3316
|
-
// selectedFeeOption was already determined at the start of the try block
|
|
3317
|
-
|
|
3318
|
-
logger.console.log(
|
|
3319
|
-
"[trails-sdk] Calling getIntentEntrypointDeposit with params:",
|
|
3320
|
-
{
|
|
3321
|
-
userAddress: account.address,
|
|
3322
|
-
tokenAddress: depositTokenAddress,
|
|
3323
|
-
amount: depositTokenAmount,
|
|
3324
|
-
intentAddress: depositRecipient,
|
|
3325
|
-
chainID: originChainId,
|
|
3326
|
-
deadline,
|
|
3327
|
-
usePermit: needsApproval,
|
|
3328
|
-
hasPermitSignature: !!permitSignature,
|
|
3329
|
-
feeAmount: selectedFeeOption?.amount,
|
|
3330
|
-
feeTokenSymbol: selectedFeeOption?.tokenSymbol,
|
|
3331
|
-
selectedFeeToken,
|
|
3332
|
-
usingSelectedFeeToken: !!selectedFeeToken,
|
|
3333
|
-
},
|
|
3334
|
-
)
|
|
3335
|
-
|
|
3336
|
-
const depositDataResponse = await trailsClient.getIntentEntrypointDeposit({
|
|
3337
|
-
params: {
|
|
3338
|
-
userAddress: account.address,
|
|
3339
|
-
tokenAddress: depositTokenAddress,
|
|
3340
|
-
amount: depositTokenAmount,
|
|
3341
|
-
intentAddress: depositRecipient,
|
|
3342
|
-
chainID: originChainId,
|
|
3343
|
-
deadline,
|
|
3344
|
-
intentSignature,
|
|
3345
|
-
usePermit: needsApproval, // Use permit if approval needed
|
|
3346
|
-
permitAmount: needsApproval ? maxUint256.toString() : undefined, // Pass infinite approval amount
|
|
3347
|
-
permitSignature,
|
|
3348
|
-
permitDeadline,
|
|
3349
|
-
feeAmount: selectedFeeOption?.amount || undefined,
|
|
3350
|
-
},
|
|
3351
|
-
})
|
|
3352
|
-
|
|
3353
|
-
const depositData = depositDataResponse.result
|
|
3354
|
-
logger.console.log("[trails-sdk] Deposit data received:", {
|
|
3355
|
-
depositWalletAddress: depositData.depositWalletAddress,
|
|
3356
|
-
entrypointAddress: depositData.entrypointAddress,
|
|
3357
|
-
metaTxnId: depositData.metaTxn?.id,
|
|
3358
|
-
usedPermit2: needsApproval,
|
|
3359
|
-
})
|
|
3360
|
-
|
|
3361
|
-
// 5. Send meta transaction via relayer
|
|
3362
|
-
logger.console.log("[trails-sdk] Sending meta transaction to relayer")
|
|
3363
|
-
const feeQuoteObj = depositData.feeQuote
|
|
3364
|
-
? ({
|
|
3365
|
-
_tag: "FeeQuote",
|
|
3366
|
-
_quote: { toJSON: () => depositData.feeQuote },
|
|
3367
|
-
} as any)
|
|
3368
|
-
: undefined
|
|
3369
|
-
|
|
3370
|
-
const opHash = await relayerSendMetaTx(
|
|
3371
|
-
originRelayer,
|
|
3372
|
-
depositData.metaTxn as any,
|
|
3373
|
-
[], // No preconditions for gasless deposit
|
|
3374
|
-
feeQuoteObj,
|
|
3375
|
-
)
|
|
3376
|
-
logger.console.log("[trails-sdk] Meta transaction sent, opHash:", opHash)
|
|
3377
|
-
|
|
3378
|
-
if (onOriginSend) {
|
|
3379
|
-
onOriginSend()
|
|
3380
|
-
}
|
|
3381
|
-
|
|
3382
|
-
// 6. Wait for transaction receipt
|
|
3383
|
-
logger.console.log("[trails-sdk] Waiting for transaction receipt")
|
|
3384
|
-
// eslint-disable-next-line no-constant-condition
|
|
3385
|
-
while (true) {
|
|
3386
|
-
const receipt: any = await getMetaTxStatus(
|
|
3387
|
-
originRelayer,
|
|
3388
|
-
depositData.metaTxn.id,
|
|
3389
|
-
Number(depositData.metaTxn.chainId),
|
|
3390
|
-
)
|
|
3391
|
-
logger.console.log("[trails-sdk] Meta transaction status:", receipt)
|
|
3392
|
-
|
|
3393
|
-
if (receipt?.transactionHash) {
|
|
3394
|
-
const metaTxnReceipt = receipt.data?.receipt
|
|
3395
|
-
if (!metaTxnReceipt) {
|
|
3396
|
-
throw new Error("No meta txn receipt found")
|
|
3397
|
-
}
|
|
3398
|
-
|
|
3399
|
-
// Get the full transaction receipt
|
|
3400
|
-
const txReceipt = await publicClient.getTransactionReceipt({
|
|
3401
|
-
hash: receipt.transactionHash as `0x${string}`,
|
|
3402
|
-
})
|
|
3403
|
-
logger.console.log("[trails-sdk] Transaction receipt:", txReceipt)
|
|
3404
|
-
originUserTxReceipt = txReceipt
|
|
3405
|
-
break
|
|
3406
|
-
}
|
|
3407
|
-
|
|
3408
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
3409
|
-
}
|
|
3410
|
-
} catch (error) {
|
|
3411
|
-
logger.console.error(
|
|
3412
|
-
"[trails-sdk] Error in Intent Entrypoint gasless deposit with permit2:",
|
|
3413
|
-
error,
|
|
3414
|
-
)
|
|
3415
|
-
throw error
|
|
3416
|
-
}
|
|
3417
|
-
|
|
3418
|
-
return originUserTxReceipt
|
|
3419
|
-
}
|
|
3420
|
-
|
|
3421
|
-
export async function attemptNonGaslessUserDeposit({
|
|
3422
|
-
originTokenAddress,
|
|
3423
|
-
firstPreconditionMin,
|
|
3424
|
-
onOriginSend,
|
|
3425
|
-
publicClient,
|
|
3426
|
-
walletClient,
|
|
3427
|
-
originChainId,
|
|
3428
|
-
chain,
|
|
3429
|
-
account,
|
|
3430
|
-
fee,
|
|
3431
|
-
dryMode,
|
|
3432
|
-
sourceTokenPriceUsd,
|
|
3433
|
-
destinationTokenPriceUsd,
|
|
3434
|
-
swapAmount,
|
|
3435
|
-
destinationTokenDecimals,
|
|
3436
|
-
sourceTokenDecimals,
|
|
3437
|
-
originIntentAddress,
|
|
3438
|
-
onTransactionStateChange,
|
|
3439
|
-
transactionStates,
|
|
3440
|
-
originTokenSymbol,
|
|
3441
|
-
destinationTokenSymbol,
|
|
3442
|
-
depositAmountUsd,
|
|
3443
|
-
}: {
|
|
3444
|
-
originTokenAddress: string
|
|
3445
|
-
firstPreconditionMin: string
|
|
3446
|
-
onOriginSend?: () => void
|
|
3447
|
-
publicClient: PublicClient
|
|
3448
|
-
walletClient: WalletClient
|
|
3449
|
-
originChainId: number
|
|
3450
|
-
chain: Chain
|
|
3451
|
-
account: Account
|
|
3452
|
-
fee: string
|
|
3453
|
-
dryMode: boolean
|
|
3454
|
-
sourceTokenPriceUsd?: number | null
|
|
3455
|
-
destinationTokenPriceUsd?: number | null
|
|
3456
|
-
swapAmount: string
|
|
3457
|
-
destinationTokenDecimals: number
|
|
3458
|
-
sourceTokenDecimals: number
|
|
3459
|
-
originIntentAddress: string
|
|
3460
|
-
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
3461
|
-
transactionStates: TransactionState[]
|
|
3462
|
-
originTokenSymbol: string
|
|
3463
|
-
destinationTokenSymbol: string
|
|
3464
|
-
depositAmountUsd: number
|
|
3465
|
-
}): Promise<TransactionReceipt | null> {
|
|
3466
|
-
let originUserTxReceipt: TransactionReceipt | null = null
|
|
3467
|
-
const usingLIfi = false
|
|
3468
|
-
let needsNativeFee = false
|
|
3469
|
-
|
|
3470
|
-
if (usingLIfi) {
|
|
3471
|
-
needsNativeFee = await getNeedsLifiNativeFee({
|
|
3472
|
-
originTokenAddress,
|
|
3473
|
-
destinationTokenAmount: swapAmount,
|
|
3474
|
-
destinationTokenDecimals,
|
|
3475
|
-
sourceTokenDecimals,
|
|
3476
|
-
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
3477
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
3478
|
-
depositAmount: firstPreconditionMin,
|
|
3479
|
-
})
|
|
3480
|
-
}
|
|
3481
|
-
let nativeFee = parseUnits("0.00005", 18).toString()
|
|
3482
|
-
if (originChainId === 137) {
|
|
3483
|
-
nativeFee = parseUnits("1.5", 18).toString()
|
|
3484
|
-
}
|
|
3485
|
-
|
|
3486
|
-
logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
|
|
3487
|
-
|
|
3488
|
-
// Build origin call params
|
|
3489
|
-
const originCallParamsBase = buildCrossChainDepositParams({
|
|
3490
|
-
originTokenAddress,
|
|
3491
|
-
originIntentAddress,
|
|
3492
|
-
depositAmount: firstPreconditionMin,
|
|
3493
|
-
fee,
|
|
3494
|
-
originChainId,
|
|
3495
|
-
chain,
|
|
3496
|
-
})
|
|
3497
|
-
|
|
3498
|
-
// Estimate gas limit for consistency with actual transaction
|
|
3499
|
-
const gasLimit = await estimateGasLimit(
|
|
3500
|
-
publicClient,
|
|
3501
|
-
{
|
|
3502
|
-
account: account.address,
|
|
3503
|
-
to: originCallParamsBase.to,
|
|
3504
|
-
data: originCallParamsBase.data,
|
|
3505
|
-
value: BigInt(originCallParamsBase.value),
|
|
3506
|
-
},
|
|
3507
|
-
"deposit",
|
|
3508
|
-
)
|
|
3509
|
-
|
|
3510
|
-
const originCallParams = {
|
|
3511
|
-
...originCallParamsBase,
|
|
3512
|
-
gasLimit,
|
|
3513
|
-
}
|
|
3514
|
-
|
|
3515
|
-
await attemptSwitchChain({
|
|
3516
|
-
walletClient,
|
|
3517
|
-
desiredChainId: originChainId,
|
|
3518
|
-
})
|
|
3519
|
-
|
|
3520
|
-
let useSendCalls = false
|
|
3521
|
-
const moreThan1Tx = needsNativeFee
|
|
3522
|
-
|
|
3523
|
-
if (moreThan1Tx) {
|
|
3524
|
-
try {
|
|
3525
|
-
// the reason for the timeout is some users experience this call to hang indefinitely on metamask on all chains.
|
|
3526
|
-
// not sure why this is happening, but it happens.
|
|
3527
|
-
const capabilities = await requestWithTimeout<Record<string, any>>(
|
|
3528
|
-
walletClient,
|
|
3529
|
-
[
|
|
3530
|
-
{
|
|
3531
|
-
method: "wallet_getCapabilities",
|
|
3532
|
-
params: [account.address],
|
|
3533
|
-
},
|
|
3534
|
-
],
|
|
3535
|
-
10000,
|
|
3536
|
-
)
|
|
3537
|
-
|
|
3538
|
-
logger.console.log("[trails-sdk] capabilities", capabilities)
|
|
3539
|
-
|
|
3540
|
-
// Check if the chain supports atomic transactions
|
|
3541
|
-
const chainHex = `0x${originChainId.toString(16)}` as const
|
|
3542
|
-
const chainCapabilities = capabilities[chainHex]
|
|
3543
|
-
useSendCalls = chainCapabilities?.atomic?.status === "supported"
|
|
3544
|
-
} catch (error) {
|
|
3545
|
-
logger.console.error("[trails-sdk] Error getting capabilities", error)
|
|
3546
|
-
}
|
|
3547
|
-
}
|
|
3548
|
-
|
|
3549
|
-
if (dryMode) {
|
|
3550
|
-
logger.console.log("[trails-sdk] dry mode, skipping send calls")
|
|
3551
|
-
}
|
|
3552
|
-
|
|
3553
|
-
if (useSendCalls) {
|
|
3554
|
-
logger.console.log("[trails-sdk] using sendCalls")
|
|
3555
|
-
} else {
|
|
3556
|
-
logger.console.log("[trails-sdk] using sendTransaction")
|
|
3557
|
-
}
|
|
3558
|
-
|
|
3559
|
-
if (useSendCalls) {
|
|
3560
|
-
if (!dryMode) {
|
|
3561
|
-
const calls: Array<{
|
|
3562
|
-
to: `0x${string}`
|
|
3563
|
-
data: `0x${string}`
|
|
3564
|
-
value?: `0x${string}`
|
|
3565
|
-
}> = []
|
|
3566
|
-
if (needsNativeFee) {
|
|
3567
|
-
calls.push({
|
|
3568
|
-
to: originIntentAddress as `0x${string}`,
|
|
3569
|
-
data: "0x00",
|
|
3570
|
-
value: `0x${BigInt(nativeFee).toString(16)}` as `0x${string}`,
|
|
3571
|
-
})
|
|
3572
|
-
}
|
|
3573
|
-
|
|
3574
|
-
// Add the origin call
|
|
3575
|
-
calls.push({
|
|
3576
|
-
to: originCallParams.to as `0x${string}`,
|
|
3577
|
-
data: originCallParams.data as `0x${string}`,
|
|
3578
|
-
value: originCallParams.value
|
|
3579
|
-
? `0x${BigInt(originCallParams.value).toString(16)}`
|
|
3580
|
-
: "0x0",
|
|
3581
|
-
})
|
|
3582
|
-
|
|
3583
|
-
// Update persistent toast before wallet interaction
|
|
3584
|
-
updatePersistentToast(
|
|
3585
|
-
"Waiting for Confirmation",
|
|
3586
|
-
"Please confirm the transaction in your wallet...",
|
|
3587
|
-
"info",
|
|
3588
|
-
)
|
|
3589
|
-
|
|
3590
|
-
// Send the batched call via EIP-7702
|
|
3591
|
-
const result = (await walletClient.request({
|
|
3592
|
-
method: "wallet_sendCalls",
|
|
3593
|
-
params: [
|
|
3594
|
-
{
|
|
3595
|
-
version: "2.0.0",
|
|
3596
|
-
chainId: `0x${originChainId.toString(16)}`,
|
|
3597
|
-
atomicRequired: true,
|
|
3598
|
-
calls,
|
|
3599
|
-
},
|
|
3600
|
-
],
|
|
3601
|
-
})) as { requestId: `0x${string}` }
|
|
3602
|
-
|
|
3603
|
-
logger.console.log("[trails-sdk] sendCalls result", result)
|
|
3604
|
-
const requestId = result.requestId || (result as any).id
|
|
3605
|
-
|
|
3606
|
-
// Poll to check if the tx has been submitted
|
|
3607
|
-
let txHash: `0x${string}` | undefined
|
|
3608
|
-
while (!txHash) {
|
|
3609
|
-
const status = (await walletClient.request({
|
|
3610
|
-
method: "wallet_getCallsStatus",
|
|
3611
|
-
params: [requestId],
|
|
3612
|
-
})) as {
|
|
3613
|
-
status: "pending" | "submitted" | "failed"
|
|
3614
|
-
transactionHash?: `0x${string}`
|
|
3615
|
-
error?: string
|
|
3616
|
-
}
|
|
3617
|
-
|
|
3618
|
-
logger.console.log("[trails-sdk] getCallsStatus result", status)
|
|
3619
|
-
const receipt = (status as any)?.receipts?.[0]
|
|
3620
|
-
|
|
3621
|
-
if ((status as any).status === 200 && receipt?.transactionHash) {
|
|
3622
|
-
txHash = receipt.transactionHash
|
|
3623
|
-
break
|
|
3624
|
-
} else if ((status as any).status === 500) {
|
|
3625
|
-
throw new Error(`Transaction failed: ${status.error}`)
|
|
3626
|
-
}
|
|
3627
|
-
|
|
3628
|
-
// wait a bit before polling again
|
|
3629
|
-
await new Promise((r) => setTimeout(r, 2000))
|
|
3630
|
-
}
|
|
3631
|
-
|
|
3632
|
-
if (onOriginSend) {
|
|
3633
|
-
onOriginSend()
|
|
3634
|
-
}
|
|
3635
|
-
|
|
3636
|
-
// Update persistent toast after transaction sent
|
|
3637
|
-
const chainInfo = getChainInfo(originChainId)
|
|
3638
|
-
updatePersistentToast(
|
|
3639
|
-
"Transaction Submitted",
|
|
3640
|
-
`Waiting for confirmation on ${chainInfo?.name || "chain"}...`,
|
|
3641
|
-
"info",
|
|
3642
|
-
)
|
|
3643
|
-
|
|
3644
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3645
|
-
hash: txHash as `0x${string}`,
|
|
3646
|
-
})
|
|
3647
|
-
logger.console.log("[trails-sdk] receipt", receipt)
|
|
3648
|
-
originUserTxReceipt = receipt
|
|
3649
|
-
}
|
|
3650
|
-
} else {
|
|
3651
|
-
if (!dryMode) {
|
|
3652
|
-
if (needsNativeFee) {
|
|
3653
|
-
const txHashNativeFee = await sendOriginTransaction(
|
|
3654
|
-
account,
|
|
3655
|
-
walletClient,
|
|
3656
|
-
{
|
|
3657
|
-
to: originIntentAddress,
|
|
3658
|
-
data: "0x00",
|
|
3659
|
-
value: nativeFee,
|
|
3660
|
-
chainId: originChainId,
|
|
3661
|
-
chain,
|
|
3662
|
-
} as any,
|
|
3663
|
-
{
|
|
3664
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
3665
|
-
},
|
|
3666
|
-
) // TODO: Add proper type
|
|
3667
|
-
logger.console.log("[trails-sdk] origin tx native fee", txHashNativeFee)
|
|
3668
|
-
// Wait for transaction receipt
|
|
3669
|
-
const feeReceipt = await publicClient.waitForTransactionReceipt({
|
|
3670
|
-
hash: txHashNativeFee,
|
|
3671
|
-
})
|
|
3672
|
-
logger.console.log("[trails-sdk] nativeFeeReceipt", feeReceipt)
|
|
3673
|
-
}
|
|
3674
|
-
|
|
3675
|
-
// Show persistent toast for checkout flow
|
|
3676
|
-
updatePersistentToast(
|
|
3677
|
-
"Payment Started",
|
|
3678
|
-
"Waiting for wallet confirmation...",
|
|
3679
|
-
"info",
|
|
3680
|
-
)
|
|
3681
|
-
|
|
3682
|
-
const txHash = await sendOriginTransaction(
|
|
3683
|
-
account,
|
|
3684
|
-
walletClient,
|
|
3685
|
-
originCallParams as any,
|
|
3686
|
-
{
|
|
3687
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
3688
|
-
},
|
|
3689
|
-
) // TODO: Add proper type
|
|
3690
|
-
logger.console.log("[trails-sdk] origin tx", txHash)
|
|
3691
|
-
|
|
3692
|
-
if (onOriginSend) {
|
|
3693
|
-
onOriginSend()
|
|
3694
|
-
}
|
|
3695
|
-
|
|
3696
|
-
if (transactionStates[0]) {
|
|
3697
|
-
transactionStates[0].state = "pending"
|
|
3698
|
-
|
|
3699
|
-
try {
|
|
3700
|
-
onTransactionStateChange(transactionStates)
|
|
3701
|
-
} catch (error) {
|
|
3702
|
-
logger.console.error(
|
|
3703
|
-
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
3704
|
-
error,
|
|
3705
|
-
)
|
|
3706
|
-
}
|
|
3707
|
-
}
|
|
3708
|
-
|
|
3709
|
-
// Wait for transaction receipt
|
|
3710
|
-
const receipt = await publicClient.waitForTransactionReceipt({
|
|
3711
|
-
hash: txHash,
|
|
3712
|
-
})
|
|
3713
|
-
|
|
3714
|
-
const chainInfo = getChainInfo(originChainId)
|
|
3715
|
-
updatePersistentToast(
|
|
3716
|
-
"Transfer Confirmed",
|
|
3717
|
-
`Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
|
|
3718
|
-
"info",
|
|
3719
|
-
)
|
|
3720
|
-
|
|
3721
|
-
trackTransactionConfirmed({
|
|
3722
|
-
transactionHash: txHash,
|
|
3723
|
-
chainId: originChainId,
|
|
3724
|
-
userAddress: account.address,
|
|
3725
|
-
blockNumber: Number(receipt.blockNumber),
|
|
3726
|
-
originIntentAddress,
|
|
3727
|
-
originTokenSymbol,
|
|
3728
|
-
destinationTokenSymbol,
|
|
3729
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
3730
|
-
})
|
|
3731
|
-
|
|
3732
|
-
logger.console.log("[trails-sdk] receipt", receipt)
|
|
3733
|
-
originUserTxReceipt = receipt
|
|
3734
|
-
}
|
|
3735
|
-
}
|
|
3736
|
-
|
|
3737
|
-
return originUserTxReceipt
|
|
3738
|
-
}
|
|
3739
|
-
|
|
3740
|
-
async function attemptUserDepositTx({
|
|
3741
|
-
originTokenAddress,
|
|
3742
|
-
gasless,
|
|
3743
|
-
paymasterUrl,
|
|
3744
|
-
chain,
|
|
3745
|
-
account,
|
|
3746
|
-
originRelayer,
|
|
3747
|
-
firstPreconditionMin,
|
|
3748
|
-
originIntentAddress,
|
|
3749
|
-
onOriginSend,
|
|
3750
|
-
publicClient,
|
|
3751
|
-
walletClient,
|
|
3752
|
-
destinationTokenDecimals,
|
|
3753
|
-
sourceTokenDecimals,
|
|
3754
|
-
fee,
|
|
3755
|
-
dryMode,
|
|
3756
|
-
sourceTokenPriceUsd,
|
|
3757
|
-
destinationTokenPriceUsd,
|
|
3758
|
-
swapAmount,
|
|
3759
|
-
onTransactionStateChange,
|
|
3760
|
-
transactionStates,
|
|
3761
|
-
fundMethod,
|
|
3762
|
-
originTokenSymbol,
|
|
3763
|
-
destinationTokenSymbol,
|
|
3764
|
-
depositAmountUsd,
|
|
3765
|
-
feeOptions,
|
|
3766
|
-
trailsClient,
|
|
3767
|
-
selectedFeeToken,
|
|
3768
|
-
walletId,
|
|
3769
|
-
}: {
|
|
3770
|
-
originTokenAddress: string
|
|
3771
|
-
gasless: boolean
|
|
3772
|
-
paymasterUrl?: string
|
|
3773
|
-
chain: Chain
|
|
3774
|
-
account: Account
|
|
3775
|
-
originRelayer: Relayer.RpcRelayer
|
|
3776
|
-
firstPreconditionMin: string
|
|
3777
|
-
originIntentAddress: string
|
|
3778
|
-
onOriginSend?: () => void
|
|
3779
|
-
publicClient: PublicClient
|
|
3780
|
-
walletClient: WalletClient
|
|
3781
|
-
destinationTokenDecimals: number
|
|
3782
|
-
sourceTokenDecimals: number
|
|
3783
|
-
swapAmount: string
|
|
3784
|
-
dryMode: boolean
|
|
3785
|
-
sourceTokenPriceUsd: number | null
|
|
3786
|
-
destinationTokenPriceUsd: number | null
|
|
3787
|
-
fee: string
|
|
3788
|
-
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
3789
|
-
transactionStates: TransactionState[]
|
|
3790
|
-
fundMethod?: string
|
|
3791
|
-
originTokenSymbol: string
|
|
3792
|
-
destinationTokenSymbol: string
|
|
3793
|
-
depositAmountUsd: number
|
|
3794
|
-
feeOptions?: any
|
|
3795
|
-
trailsClient: TrailsAPIClient
|
|
3796
|
-
selectedFeeToken?: any
|
|
3797
|
-
walletId?: string
|
|
3798
|
-
}): Promise<TransactionReceipt | null> {
|
|
3799
|
-
let originUserTxReceipt: TransactionReceipt | null = null
|
|
3800
|
-
const originChainId = chain.id
|
|
3801
|
-
|
|
3802
|
-
logger.console.log(
|
|
3803
|
-
"[trails-sdk] attemptUserDepositTx called with fundMethod:",
|
|
3804
|
-
fundMethod,
|
|
3805
|
-
)
|
|
3806
|
-
|
|
3807
|
-
// Skip wallet deposit if fund method is qr-code
|
|
3808
|
-
if (fundMethod === "qr-code" || fundMethod === "exchange") {
|
|
3809
|
-
logger.console.log(
|
|
3810
|
-
"[trails-sdk] Skipping wallet deposit for QR code mode - fundMethod is:",
|
|
3811
|
-
fundMethod,
|
|
3812
|
-
)
|
|
3813
|
-
return null
|
|
3814
|
-
}
|
|
3815
|
-
|
|
3816
|
-
const doGasless = getDoGasless(
|
|
3817
|
-
originTokenAddress,
|
|
3818
|
-
gasless,
|
|
3819
|
-
feeOptions,
|
|
3820
|
-
selectedFeeToken,
|
|
3821
|
-
walletId,
|
|
3822
|
-
)
|
|
3823
|
-
logger.console.log(
|
|
3824
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] doGasless check results:",
|
|
3825
|
-
{
|
|
3826
|
-
doGasless,
|
|
3827
|
-
paymasterUrl,
|
|
3828
|
-
hasFeeOptions: !!feeOptions,
|
|
3829
|
-
feeOptionsCount: feeOptions?.feeOptions?.length || 0,
|
|
3830
|
-
selectedFeeToken,
|
|
3831
|
-
selectedFeeTokenSet:
|
|
3832
|
-
selectedFeeToken !== null && selectedFeeToken !== undefined,
|
|
3833
|
-
originTokenAddress,
|
|
3834
|
-
gasless,
|
|
3835
|
-
isNotNative: originTokenAddress !== zeroAddress,
|
|
3836
|
-
},
|
|
3837
|
-
)
|
|
3838
|
-
if (doGasless || paymasterUrl) {
|
|
3839
|
-
logger.console.log(
|
|
3840
|
-
"[trails-sdk] [GASLESS-FLOW] Entering gasless deposit flow",
|
|
3841
|
-
{
|
|
3842
|
-
doGasless,
|
|
3843
|
-
hasPaymasterUrl: !!paymasterUrl,
|
|
3844
|
-
paymasterUrl,
|
|
3845
|
-
},
|
|
3846
|
-
)
|
|
3847
|
-
try {
|
|
3848
|
-
originUserTxReceipt = await attemptGaslessDeposit({
|
|
3849
|
-
paymasterUrl,
|
|
3850
|
-
depositTokenAddress: originTokenAddress,
|
|
3851
|
-
depositTokenAmount: firstPreconditionMin,
|
|
3852
|
-
depositRecipient: originIntentAddress,
|
|
3853
|
-
onOriginSend,
|
|
3854
|
-
walletClient,
|
|
3855
|
-
chain,
|
|
3856
|
-
account,
|
|
3857
|
-
trailsClient,
|
|
3858
|
-
originRelayer,
|
|
3859
|
-
feeOptions: feeOptions,
|
|
3860
|
-
selectedFeeToken: selectedFeeToken,
|
|
3861
|
-
})
|
|
3862
|
-
} catch (error) {
|
|
3863
|
-
logger.console.log("[trails-sdk] gassless attempt failed", error)
|
|
3864
|
-
// In strict gasless mode, re-throw error instead of falling back
|
|
3865
|
-
if (gasless) {
|
|
3866
|
-
throw error
|
|
3867
|
-
}
|
|
3868
|
-
}
|
|
3869
|
-
}
|
|
3870
|
-
|
|
3871
|
-
// If gasless attempt failed, try to send a regular transaction
|
|
3872
|
-
if (!originUserTxReceipt && !gasless) {
|
|
3873
|
-
originUserTxReceipt = await attemptNonGaslessUserDeposit({
|
|
3874
|
-
originTokenAddress,
|
|
3875
|
-
firstPreconditionMin,
|
|
3876
|
-
originIntentAddress,
|
|
3877
|
-
onOriginSend,
|
|
3878
|
-
publicClient,
|
|
3879
|
-
walletClient,
|
|
3880
|
-
originChainId,
|
|
3881
|
-
chain,
|
|
3882
|
-
account,
|
|
3883
|
-
fee,
|
|
3884
|
-
dryMode,
|
|
3885
|
-
sourceTokenPriceUsd,
|
|
3886
|
-
destinationTokenPriceUsd,
|
|
3887
|
-
swapAmount,
|
|
3888
|
-
destinationTokenDecimals,
|
|
3889
|
-
sourceTokenDecimals,
|
|
3890
|
-
onTransactionStateChange,
|
|
3891
|
-
transactionStates,
|
|
3892
|
-
originTokenSymbol,
|
|
3893
|
-
destinationTokenSymbol,
|
|
3894
|
-
depositAmountUsd,
|
|
3895
|
-
})
|
|
3896
|
-
}
|
|
3897
|
-
|
|
3898
|
-
return originUserTxReceipt
|
|
3899
|
-
}
|
|
3900
|
-
|
|
3901
|
-
export function getDoGasless(
|
|
3902
|
-
originTokenAddress: string,
|
|
3903
|
-
gasless: boolean,
|
|
3904
|
-
feeOptions?: any,
|
|
3905
|
-
selectedFeeToken?: any,
|
|
3906
|
-
walletId?: string,
|
|
3907
|
-
): boolean {
|
|
3908
|
-
// Don't use gasless flow for sequence-waas wallet
|
|
3909
|
-
if (walletId === "sequence-waas") {
|
|
3910
|
-
logger.console.log(
|
|
3911
|
-
"[trails-sdk] [GASLESS-FLOW] getDoGasless: Skipping gasless flow for sequence-waas wallet",
|
|
3912
|
-
)
|
|
3913
|
-
return false
|
|
3914
|
-
}
|
|
3915
|
-
|
|
3916
|
-
const hasFeeOptions = Boolean(feeOptions && feeOptions.feeOptions?.length > 0)
|
|
3917
|
-
|
|
3918
|
-
// Important: The UI passes selectedFeeToken in these states:
|
|
3919
|
-
// - null: User explicitly chose "Pay with native gas"
|
|
3920
|
-
// - {object}: User selected a fee token OR it was auto-selected
|
|
3921
|
-
// - undefined: Should not happen (initial state auto-selects if options exist)
|
|
3922
|
-
|
|
3923
|
-
// If selectedFeeToken is null, user explicitly chose native gas
|
|
3924
|
-
if (selectedFeeToken === null) {
|
|
3925
|
-
logger.console.log(
|
|
3926
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: User explicitly selected native gas (null)",
|
|
3927
|
-
)
|
|
3928
|
-
return false
|
|
3929
|
-
}
|
|
3930
|
-
|
|
3931
|
-
// If selectedFeeToken is undefined and no fee options, can't do gasless
|
|
3932
|
-
if (!selectedFeeToken && !hasFeeOptions) {
|
|
3933
|
-
logger.console.log(
|
|
3934
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: No fee token selected and no fee options available",
|
|
3935
|
-
)
|
|
3936
|
-
return false
|
|
3937
|
-
}
|
|
3938
|
-
|
|
3939
|
-
// If selectedFeeToken is undefined but fee options exist, use first ERC20 option
|
|
3940
|
-
let effectiveFeeToken = selectedFeeToken
|
|
3941
|
-
if (!effectiveFeeToken && hasFeeOptions) {
|
|
3942
|
-
const firstFeeOption = feeOptions.feeOptions[0]
|
|
3943
|
-
|
|
3944
|
-
// Check if first option is native gas
|
|
3945
|
-
const isFirstOptionNative =
|
|
3946
|
-
firstFeeOption?.tokenAddress === zeroAddress ||
|
|
3947
|
-
firstFeeOption?.tokenAddress?.toLowerCase() === zeroAddress ||
|
|
3948
|
-
firstFeeOption?.isNative === true
|
|
3949
|
-
|
|
3950
|
-
if (!isFirstOptionNative && firstFeeOption?.tokenAddress) {
|
|
3951
|
-
// First fee option is ERC20, use it
|
|
3952
|
-
effectiveFeeToken = firstFeeOption
|
|
3953
|
-
logger.console.log(
|
|
3954
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: No fee token selected, using first ERC20 fee option",
|
|
3955
|
-
{
|
|
3956
|
-
feeOption: effectiveFeeToken,
|
|
3957
|
-
},
|
|
3958
|
-
)
|
|
3959
|
-
} else {
|
|
3960
|
-
logger.console.log(
|
|
3961
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: First fee option is native gas, skipping gasless",
|
|
3962
|
-
)
|
|
3963
|
-
return false
|
|
3964
|
-
}
|
|
3965
|
-
}
|
|
3966
|
-
|
|
3967
|
-
// Check if the effective fee token is native gas
|
|
3968
|
-
const isNativeGasFee =
|
|
3969
|
-
!effectiveFeeToken ||
|
|
3970
|
-
effectiveFeeToken.tokenAddress === zeroAddress ||
|
|
3971
|
-
effectiveFeeToken.tokenAddress?.toLowerCase() === zeroAddress ||
|
|
3972
|
-
effectiveFeeToken.isNative === true
|
|
3973
|
-
|
|
3974
|
-
// Don't use gasless if origin token is native (sending ETH)
|
|
3975
|
-
if (originTokenAddress === zeroAddress) {
|
|
3976
|
-
logger.console.log(
|
|
3977
|
-
"[trails-sdk] [GASLESS-FLOW] getDoGasless: Origin token is native, skipping gasless",
|
|
3978
|
-
)
|
|
3979
|
-
return false
|
|
3980
|
-
}
|
|
3981
|
-
|
|
3982
|
-
// Don't use gasless if fee token is native
|
|
3983
|
-
if (isNativeGasFee) {
|
|
3984
|
-
logger.console.log(
|
|
3985
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: Fee token is native gas, skipping gasless",
|
|
3986
|
-
{
|
|
3987
|
-
effectiveFeeToken,
|
|
3988
|
-
},
|
|
3989
|
-
)
|
|
3990
|
-
return false
|
|
3991
|
-
}
|
|
3992
|
-
|
|
3993
|
-
// Don't use gasless if disabled
|
|
3994
|
-
if (!gasless) {
|
|
3995
|
-
logger.console.log(
|
|
3996
|
-
"[trails-sdk] [GASLESS-FLOW] getDoGasless: Gasless disabled",
|
|
3997
|
-
)
|
|
3998
|
-
return false
|
|
3999
|
-
}
|
|
4000
|
-
|
|
4001
|
-
// All conditions met, use gasless with ERC20 fee token
|
|
4002
|
-
logger.console.log(
|
|
4003
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless decision: Using gasless",
|
|
4004
|
-
{
|
|
4005
|
-
originTokenAddress,
|
|
4006
|
-
gasless,
|
|
4007
|
-
hasFeeOptions,
|
|
4008
|
-
selectedFeeToken,
|
|
4009
|
-
effectiveFeeToken,
|
|
4010
|
-
feeOptionsCount: feeOptions?.feeOptions?.length || 0,
|
|
4011
|
-
},
|
|
4012
|
-
)
|
|
4013
|
-
|
|
4014
|
-
return true
|
|
4015
|
-
}
|
|
4016
|
-
|
|
4017
|
-
function getTransactionStateFromReceipt(
|
|
4018
|
-
receipt: TransactionReceipt | MetaTxnReceipt,
|
|
4019
|
-
chainId: number,
|
|
4020
|
-
label: string = "Transaction",
|
|
4021
|
-
): TransactionState {
|
|
4022
|
-
let txHash: string = ""
|
|
4023
|
-
let state: TransactionStateStatus = "pending"
|
|
4024
|
-
let blockNumber: number = 0
|
|
4025
|
-
if ("transactionHash" in receipt) {
|
|
4026
|
-
txHash = receipt.transactionHash
|
|
4027
|
-
state = receipt.status === "success" ? "confirmed" : "failed"
|
|
4028
|
-
blockNumber = Number(receipt.blockNumber)
|
|
4029
|
-
} else if ("txnHash" in receipt) {
|
|
4030
|
-
txHash = receipt.txnHash
|
|
4031
|
-
state = receipt.status === "SUCCEEDED" ? "confirmed" : "failed"
|
|
4032
|
-
blockNumber = Number(receipt.blockNumber)
|
|
4033
|
-
}
|
|
4034
|
-
|
|
4035
|
-
return {
|
|
4036
|
-
transactionHash: txHash,
|
|
4037
|
-
explorerUrl: getExplorerUrl({ txHash, chainId }),
|
|
4038
|
-
chainId,
|
|
4039
|
-
blockNumber,
|
|
4040
|
-
state,
|
|
4041
|
-
label,
|
|
4042
|
-
}
|
|
4043
|
-
}
|
|
4044
|
-
|
|
4045
|
-
async function sendMetaTxAndWaitForReceipt({
|
|
4046
|
-
metaTx,
|
|
4047
|
-
relayer,
|
|
4048
|
-
precondition,
|
|
4049
|
-
feeQuote,
|
|
4050
|
-
}: {
|
|
4051
|
-
metaTx: MetaTxn
|
|
4052
|
-
relayer: Relayer.RpcRelayer
|
|
4053
|
-
precondition: IntentPrecondition | null
|
|
4054
|
-
feeQuote?: string
|
|
4055
|
-
}): Promise<{
|
|
4056
|
-
waitForReceipt: (abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>
|
|
4057
|
-
}> {
|
|
4058
|
-
try {
|
|
4059
|
-
logger.console.log("[trails-sdk] metaTx", metaTx)
|
|
4060
|
-
trackRelayerCallStarted({
|
|
4061
|
-
walletAddress: metaTx.walletAddress as `0x${string}`,
|
|
4062
|
-
contractAddress: metaTx.contract as `0x${string}`,
|
|
4063
|
-
chainId: Number(metaTx.chainId),
|
|
4064
|
-
})
|
|
4065
|
-
const feeQuoteObj = feeQuote
|
|
4066
|
-
? ({ _tag: "FeeQuote", _quote: { toJSON: () => feeQuote } } as any)
|
|
4067
|
-
: undefined
|
|
4068
|
-
logger.console.log("[trails-sdk] feeQuote", feeQuoteObj)
|
|
4069
|
-
const opHash = await relayerSendMetaTx(
|
|
4070
|
-
relayer,
|
|
4071
|
-
metaTx,
|
|
4072
|
-
precondition ? [precondition] : ([] as IntentPrecondition[]),
|
|
4073
|
-
feeQuoteObj,
|
|
4074
|
-
)
|
|
4075
|
-
logger.console.log("[trails-sdk] opHash", opHash)
|
|
4076
|
-
|
|
4077
|
-
trackRelayerCallCompleted({
|
|
4078
|
-
walletAddress: metaTx.walletAddress as `0x${string}`,
|
|
4079
|
-
contractAddress: metaTx.contract as `0x${string}`,
|
|
4080
|
-
chainId: Number(metaTx.chainId),
|
|
4081
|
-
})
|
|
4082
|
-
} catch (error) {
|
|
4083
|
-
trackRelayerCallError({
|
|
4084
|
-
walletAddress: metaTx.walletAddress as `0x${string}`,
|
|
4085
|
-
contractAddress: metaTx.contract as `0x${string}`,
|
|
4086
|
-
chainId: Number(metaTx.chainId),
|
|
4087
|
-
error: error instanceof Error ? error.message : "Unknown error",
|
|
4088
|
-
})
|
|
4089
|
-
throw error
|
|
4090
|
-
}
|
|
4091
|
-
|
|
4092
|
-
return {
|
|
4093
|
-
waitForReceipt: async (abortSignal?: AbortSignal) => {
|
|
4094
|
-
let originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
4095
|
-
|
|
4096
|
-
// eslint-disable-next-line no-constant-condition
|
|
4097
|
-
while (true) {
|
|
4098
|
-
// Check if we should abort
|
|
4099
|
-
if (abortSignal?.aborted) {
|
|
4100
|
-
logger.console.log(
|
|
4101
|
-
"[trails-sdk] Aborting meta transaction polling due to abort signal",
|
|
4102
|
-
)
|
|
4103
|
-
return null
|
|
4104
|
-
}
|
|
4105
|
-
|
|
4106
|
-
logger.console.log(
|
|
4107
|
-
"[trails-sdk] polling status",
|
|
4108
|
-
metaTx.id as `0x${string}`,
|
|
4109
|
-
metaTx.chainId.toString(),
|
|
4110
|
-
)
|
|
4111
|
-
const receipt: any = await getMetaTxStatus(
|
|
4112
|
-
relayer,
|
|
4113
|
-
metaTx.id,
|
|
4114
|
-
Number(metaTx.chainId),
|
|
4115
|
-
)
|
|
4116
|
-
logger.console.log("[trails-sdk] status", receipt)
|
|
4117
|
-
if (receipt?.transactionHash) {
|
|
4118
|
-
originMetaTxnReceipt = receipt.data?.receipt
|
|
4119
|
-
if (!originMetaTxnReceipt) {
|
|
4120
|
-
throw new Error("No meta txn receipt found")
|
|
4121
|
-
}
|
|
4122
|
-
break
|
|
4123
|
-
}
|
|
4124
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
4125
|
-
}
|
|
4126
|
-
|
|
4127
|
-
return originMetaTxnReceipt
|
|
4128
|
-
},
|
|
4129
|
-
}
|
|
4130
|
-
}
|
|
4131
|
-
|
|
4132
|
-
/**
|
|
4133
|
-
* Check if the account has enough balance for the deposit amount
|
|
4134
|
-
*/
|
|
4135
|
-
async function checkAccountBalance({
|
|
4136
|
-
account,
|
|
4137
|
-
tokenAddress,
|
|
4138
|
-
depositAmount,
|
|
4139
|
-
publicClient,
|
|
4140
|
-
}: {
|
|
4141
|
-
account: Account
|
|
4142
|
-
tokenAddress: string
|
|
4143
|
-
depositAmount: string
|
|
4144
|
-
publicClient: PublicClient
|
|
4145
|
-
}): Promise<{
|
|
4146
|
-
hasEnoughBalance: boolean
|
|
4147
|
-
balance: bigint
|
|
4148
|
-
requiredAmount: bigint
|
|
4149
|
-
balanceFormatted: string
|
|
4150
|
-
requiredAmountFormatted: string
|
|
4151
|
-
balanceError: Error | null
|
|
4152
|
-
}> {
|
|
4153
|
-
try {
|
|
4154
|
-
let balance: bigint
|
|
4155
|
-
|
|
4156
|
-
if (tokenAddress === zeroAddress) {
|
|
4157
|
-
// Native token balance
|
|
4158
|
-
balance = await publicClient.getBalance({ address: account.address })
|
|
4159
|
-
} else {
|
|
4160
|
-
// ERC20 token balance
|
|
4161
|
-
balance = await publicClient.readContract({
|
|
4162
|
-
address: tokenAddress as `0x${string}`,
|
|
4163
|
-
abi: erc20Abi,
|
|
4164
|
-
functionName: "balanceOf",
|
|
4165
|
-
args: [account.address],
|
|
4166
|
-
})
|
|
4167
|
-
}
|
|
4168
|
-
|
|
4169
|
-
const requiredAmount = BigInt(depositAmount)
|
|
4170
|
-
|
|
4171
|
-
logger.console.log("[trails-sdk] balance", balance)
|
|
4172
|
-
logger.console.log("[trails-sdk] requiredAmount", requiredAmount)
|
|
4173
|
-
const hasEnoughBalance = balance >= requiredAmount
|
|
4174
|
-
logger.console.log("[trails-sdk] hasEnoughBalance", hasEnoughBalance)
|
|
4175
|
-
|
|
4176
|
-
let balanceFormatted = ""
|
|
4177
|
-
let requiredAmountFormatted = ""
|
|
4178
|
-
if (tokenAddress === zeroAddress) {
|
|
4179
|
-
balanceFormatted = formatUnits(balance, 18)
|
|
4180
|
-
requiredAmountFormatted = formatUnits(requiredAmount, 18)
|
|
4181
|
-
} else {
|
|
4182
|
-
// ERC20 token balance
|
|
4183
|
-
const decimals = await publicClient.readContract({
|
|
4184
|
-
address: tokenAddress as `0x${string}`,
|
|
4185
|
-
abi: erc20Abi,
|
|
4186
|
-
functionName: "decimals",
|
|
4187
|
-
})
|
|
4188
|
-
if (!decimals) {
|
|
4189
|
-
throw new Error("Decimals not found")
|
|
4190
|
-
}
|
|
4191
|
-
balanceFormatted = formatUnits(balance, decimals)
|
|
4192
|
-
requiredAmountFormatted = formatUnits(requiredAmount, decimals)
|
|
4193
|
-
}
|
|
4194
|
-
|
|
4195
|
-
let balanceError = null
|
|
4196
|
-
if (!hasEnoughBalance) {
|
|
4197
|
-
balanceError = new InsufficientBalanceError(
|
|
4198
|
-
`Insufficient balance: Need ${formatAmount(requiredAmountFormatted)} ${tokenAddress} for ${account.address} on ${publicClient.chain?.name} but only have ${formatAmount(balanceFormatted)} ${tokenAddress}`,
|
|
4199
|
-
)
|
|
4200
|
-
}
|
|
4201
|
-
|
|
4202
|
-
return {
|
|
4203
|
-
hasEnoughBalance,
|
|
4204
|
-
balance,
|
|
4205
|
-
balanceFormatted,
|
|
4206
|
-
requiredAmount,
|
|
4207
|
-
requiredAmountFormatted,
|
|
4208
|
-
balanceError,
|
|
4209
|
-
}
|
|
4210
|
-
} catch (error) {
|
|
4211
|
-
logger.console.error("[trails-sdk] Error checking account balance:", error)
|
|
4212
|
-
return {
|
|
4213
|
-
hasEnoughBalance: false,
|
|
4214
|
-
balance: BigInt(0),
|
|
4215
|
-
balanceFormatted: "0",
|
|
4216
|
-
requiredAmount: BigInt(0),
|
|
4217
|
-
requiredAmountFormatted: "0",
|
|
4218
|
-
balanceError: error instanceof Error ? error : null,
|
|
4219
|
-
}
|
|
4220
|
-
}
|
|
4221
|
-
}
|
|
4222
|
-
|
|
4223
|
-
// ETH fee required by some bridges for low token amounts
|
|
4224
|
-
// TODO: update backend API to return the native fee requirement, if any
|
|
4225
|
-
function getNeedsLifiNativeFee({
|
|
4226
|
-
originTokenAddress,
|
|
4227
|
-
destinationTokenAmount,
|
|
4228
|
-
destinationTokenDecimals,
|
|
4229
|
-
sourceTokenDecimals,
|
|
4230
|
-
sourceTokenPriceUsd,
|
|
4231
|
-
destinationTokenPriceUsd,
|
|
4232
|
-
depositAmount,
|
|
4233
|
-
}: {
|
|
4234
|
-
originTokenAddress: string
|
|
4235
|
-
destinationTokenAmount: string
|
|
4236
|
-
destinationTokenDecimals: number
|
|
4237
|
-
sourceTokenDecimals: number
|
|
4238
|
-
sourceTokenPriceUsd: number | null
|
|
4239
|
-
destinationTokenPriceUsd: number | null
|
|
4240
|
-
depositAmount: string
|
|
4241
|
-
}): boolean {
|
|
4242
|
-
let needsNativeFee = false
|
|
4243
|
-
if (
|
|
4244
|
-
originTokenAddress !== zeroAddress &&
|
|
4245
|
-
sourceTokenPriceUsd &&
|
|
4246
|
-
destinationTokenPriceUsd &&
|
|
4247
|
-
depositAmount &&
|
|
4248
|
-
destinationTokenDecimals !== undefined &&
|
|
4249
|
-
sourceTokenDecimals !== undefined
|
|
4250
|
-
) {
|
|
4251
|
-
// Convert from wei to token units using formatUnits
|
|
4252
|
-
const destinationAmount = Number(
|
|
4253
|
-
formatUnits(BigInt(destinationTokenAmount), destinationTokenDecimals),
|
|
4254
|
-
)
|
|
4255
|
-
const depositAmountFormatted = Number(
|
|
4256
|
-
formatUnits(BigInt(depositAmount), sourceTokenDecimals),
|
|
4257
|
-
)
|
|
4258
|
-
logger.console.log("[trails-sdk] destinationAmount", destinationAmount)
|
|
4259
|
-
logger.console.log(
|
|
4260
|
-
"[trails-sdk] depositAmountFormatted",
|
|
4261
|
-
depositAmountFormatted,
|
|
4262
|
-
)
|
|
4263
|
-
const destinationAmountUsd = calcAmountUsdPrice({
|
|
4264
|
-
amount: destinationAmount,
|
|
4265
|
-
usdPrice: destinationTokenPriceUsd,
|
|
4266
|
-
})
|
|
4267
|
-
const depositAmountUsd = calcAmountUsdPrice({
|
|
4268
|
-
amount: depositAmountFormatted,
|
|
4269
|
-
usdPrice: sourceTokenPriceUsd,
|
|
4270
|
-
})
|
|
4271
|
-
const diff = depositAmountUsd - destinationAmountUsd
|
|
4272
|
-
logger.console.log(
|
|
4273
|
-
"[trails-sdk] destinationAmountUsd",
|
|
4274
|
-
destinationAmountUsd,
|
|
4275
|
-
"[trails-sdk] depositAmountUsd",
|
|
4276
|
-
depositAmountUsd,
|
|
4277
|
-
"[trails-sdk] diff",
|
|
4278
|
-
diff,
|
|
4279
|
-
)
|
|
4280
|
-
if (diff >= 0 && diff <= 0.02) {
|
|
4281
|
-
needsNativeFee = true
|
|
4282
|
-
}
|
|
4283
|
-
}
|
|
4284
|
-
|
|
4285
|
-
return needsNativeFee
|
|
4286
|
-
}
|
|
4287
|
-
|
|
4288
|
-
export type UseQuoteProps = {
|
|
4289
|
-
walletClient?: any // TODO: fix this, has to do with viem/wagmi versions
|
|
4290
|
-
fromTokenAddress?: string | null
|
|
4291
|
-
fromChainId?: number | null
|
|
4292
|
-
toTokenAddress?: string | null
|
|
4293
|
-
toChainId?: number | null
|
|
4294
|
-
toCalldata?: string | null
|
|
4295
|
-
swapAmount?: string | bigint
|
|
4296
|
-
toRecipient?: string | null
|
|
4297
|
-
tradeType?: TradeType | null
|
|
4298
|
-
slippageTolerance?: string | number | null
|
|
4299
|
-
onStatusUpdate?: ((transactionStates: TransactionState[]) => void) | null
|
|
4300
|
-
quoteProvider?: string | null
|
|
4301
|
-
gasless?: boolean
|
|
4302
|
-
paymasterUrl?: string
|
|
4303
|
-
selectedFeeToken?: {
|
|
4304
|
-
tokenAddress: string
|
|
4305
|
-
tokenSymbol?: string
|
|
4306
|
-
} | null
|
|
4307
|
-
}
|
|
4308
|
-
|
|
4309
|
-
export type SwapReturn = {
|
|
4310
|
-
originTransaction: {
|
|
4311
|
-
transactionHash?: string | null
|
|
4312
|
-
explorerUrl?: string | null
|
|
4313
|
-
receipt: TransactionReceipt | MetaTxnReceipt | null
|
|
4314
|
-
}
|
|
4315
|
-
destinationTransaction: {
|
|
4316
|
-
transactionHash?: string | null
|
|
4317
|
-
explorerUrl?: string | null
|
|
4318
|
-
receipt: MetaTxnReceipt | null
|
|
4319
|
-
}
|
|
4320
|
-
totalCompletionSeconds?: number
|
|
4321
|
-
}
|
|
4322
|
-
|
|
4323
|
-
export type QuoteProviderInfo = {
|
|
4324
|
-
id: string
|
|
4325
|
-
name: string
|
|
4326
|
-
url: string
|
|
4327
|
-
}
|
|
4328
|
-
|
|
4329
|
-
export type Quote = {
|
|
4330
|
-
fromAmount: string
|
|
4331
|
-
fromAmountMin: string
|
|
4332
|
-
toAmount: string
|
|
4333
|
-
toAmountMin: string
|
|
4334
|
-
originToken: SupportedToken
|
|
4335
|
-
destinationToken: SupportedToken
|
|
4336
|
-
originChain: Chain
|
|
4337
|
-
destinationChain: Chain
|
|
4338
|
-
fees: PrepareSendFees
|
|
4339
|
-
slippageTolerance: string
|
|
4340
|
-
priceImpact: string
|
|
4341
|
-
completionEstimateSeconds: number
|
|
4342
|
-
transactionStates?: TransactionState[]
|
|
4343
|
-
originTokenRate?: string
|
|
4344
|
-
quoteProvider?: QuoteProviderInfo | null
|
|
4345
|
-
destinationTokenRate?: string
|
|
4346
|
-
fromAmountUsdDisplay?: string
|
|
4347
|
-
toAmountUsdDisplay?: string
|
|
4348
|
-
gasCostUsd?: number
|
|
4349
|
-
gasCostUsdDisplay?: string
|
|
4350
|
-
gasCost?: string
|
|
4351
|
-
gasCostFormatted?: string
|
|
4352
|
-
}
|
|
4353
|
-
|
|
4354
|
-
export type UseQuoteReturn = {
|
|
4355
|
-
quote: Quote | null
|
|
4356
|
-
swap: (() => Promise<SwapReturn | null>) | null
|
|
4357
|
-
isLoadingQuote: boolean
|
|
4358
|
-
quoteError: unknown
|
|
4359
|
-
refetchQuote: () => void
|
|
4360
|
-
}
|
|
4361
|
-
|
|
4362
|
-
export function useQuote({
|
|
4363
|
-
walletClient,
|
|
4364
|
-
fromTokenAddress,
|
|
4365
|
-
fromChainId,
|
|
4366
|
-
toTokenAddress,
|
|
4367
|
-
toChainId,
|
|
4368
|
-
swapAmount,
|
|
4369
|
-
tradeType,
|
|
4370
|
-
toRecipient,
|
|
4371
|
-
toCalldata,
|
|
4372
|
-
slippageTolerance,
|
|
4373
|
-
onStatusUpdate,
|
|
4374
|
-
quoteProvider,
|
|
4375
|
-
gasless,
|
|
4376
|
-
paymasterUrl,
|
|
4377
|
-
selectedFeeToken,
|
|
4378
|
-
relayerEnv,
|
|
4379
|
-
nodeGatewayEnv,
|
|
4380
|
-
}: Partial<
|
|
4381
|
-
UseQuoteProps & { relayerEnv?: RelayerEnv; nodeGatewayEnv?: SequenceEnv }
|
|
4382
|
-
> = {}): UseQuoteReturn {
|
|
4383
|
-
// Set node gateway environment override for this quote session
|
|
4384
|
-
if (nodeGatewayEnv) {
|
|
4385
|
-
;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
|
|
4386
|
-
}
|
|
4387
|
-
|
|
4388
|
-
const apiClient = useAPIClient()
|
|
4389
|
-
const trailsClient = useTrailsClient()
|
|
4390
|
-
const { getRelayer } = useRelayers({
|
|
4391
|
-
env: relayerEnv || (getSequenceEnv() as RelayerEnv),
|
|
4392
|
-
})
|
|
4393
|
-
const indexerGatewayClient = useIndexerGatewayClient()
|
|
4394
|
-
|
|
4395
|
-
const { supportedTokens } = useSupportedTokens()
|
|
4396
|
-
|
|
4397
|
-
const { data, isLoading, error, refetch } = useQuery({
|
|
4398
|
-
queryKey: [
|
|
4399
|
-
"quote",
|
|
4400
|
-
fromTokenAddress,
|
|
4401
|
-
fromChainId,
|
|
4402
|
-
toTokenAddress,
|
|
4403
|
-
toChainId,
|
|
4404
|
-
swapAmount?.toString(),
|
|
4405
|
-
toRecipient,
|
|
4406
|
-
toCalldata,
|
|
4407
|
-
tradeType,
|
|
4408
|
-
slippageTolerance,
|
|
4409
|
-
quoteProvider,
|
|
4410
|
-
],
|
|
4411
|
-
queryFn: async () => {
|
|
4412
|
-
try {
|
|
4413
|
-
if (
|
|
4414
|
-
!walletClient ||
|
|
4415
|
-
!apiClient ||
|
|
4416
|
-
!trailsClient ||
|
|
4417
|
-
!fromTokenAddress ||
|
|
4418
|
-
!toTokenAddress ||
|
|
4419
|
-
!swapAmount ||
|
|
4420
|
-
!toRecipient ||
|
|
4421
|
-
!fromChainId ||
|
|
4422
|
-
!toChainId ||
|
|
4423
|
-
!indexerGatewayClient
|
|
4424
|
-
) {
|
|
4425
|
-
return null
|
|
4426
|
-
}
|
|
4427
|
-
|
|
4428
|
-
// Get token balance using async method
|
|
4429
|
-
const { balances } = await getTokenBalancesWithPrices({
|
|
4430
|
-
account: walletClient.account!.address,
|
|
4431
|
-
indexerGatewayClient,
|
|
4432
|
-
apiClient,
|
|
4433
|
-
})
|
|
4434
|
-
|
|
4435
|
-
const originTokenBalance = balances.find(
|
|
4436
|
-
(b) =>
|
|
4437
|
-
b.chainId === fromChainId &&
|
|
4438
|
-
(b.contractAddress?.toLowerCase() ===
|
|
4439
|
-
fromTokenAddress.toLowerCase() ||
|
|
4440
|
-
(!b.contractAddress && fromTokenAddress === zeroAddress)),
|
|
4441
|
-
)
|
|
4442
|
-
|
|
4443
|
-
const originTokenBalanceAmount = originTokenBalance?.balance ?? "0"
|
|
4444
|
-
const destinationRelayer = getRelayer(toChainId)
|
|
4445
|
-
const originRelayer = getRelayer(fromChainId)
|
|
4446
|
-
|
|
4447
|
-
// Note: Disable this check for now to allow fetching a quote even when the origin balance is zero
|
|
4448
|
-
// if (originTokenBalanceAmount === "0") {
|
|
4449
|
-
// return null
|
|
4450
|
-
// }
|
|
4451
|
-
|
|
4452
|
-
// logger.console.log("supportedTokens", supportedTokens)
|
|
4453
|
-
|
|
4454
|
-
const originToken = supportedTokens?.find(
|
|
4455
|
-
(token) =>
|
|
4456
|
-
token.contractAddress?.toLowerCase() ===
|
|
4457
|
-
fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
|
|
4458
|
-
)
|
|
4459
|
-
const destinationToken = supportedTokens?.find(
|
|
4460
|
-
(token) =>
|
|
4461
|
-
token.contractAddress?.toLowerCase() ===
|
|
4462
|
-
toTokenAddress?.toLowerCase() && token.chainId === toChainId,
|
|
4463
|
-
)
|
|
4464
|
-
|
|
4465
|
-
const sourceTokenDecimals = originToken?.decimals
|
|
4466
|
-
if (!sourceTokenDecimals) {
|
|
4467
|
-
logger.console.error(
|
|
4468
|
-
"[trails-sdk] [useQuote] Missing source token decimals:",
|
|
4469
|
-
{
|
|
4470
|
-
originToken,
|
|
4471
|
-
fromTokenAddress,
|
|
4472
|
-
fromChainId,
|
|
4473
|
-
},
|
|
4474
|
-
)
|
|
4475
|
-
throw new Error("Source token decimals not found")
|
|
4476
|
-
}
|
|
4477
|
-
const destinationTokenDecimals = destinationToken?.decimals
|
|
4478
|
-
if (!destinationTokenDecimals) {
|
|
4479
|
-
logger.console.error(
|
|
4480
|
-
"[trails-sdk] Missing destination token decimals:",
|
|
4481
|
-
{
|
|
4482
|
-
destinationToken,
|
|
4483
|
-
toTokenAddress,
|
|
4484
|
-
toChainId,
|
|
4485
|
-
},
|
|
4486
|
-
)
|
|
4487
|
-
throw new Error("Destination token decimals not found")
|
|
4488
|
-
}
|
|
4489
|
-
const destinationTokenSymbol = destinationToken?.symbol ?? ""
|
|
4490
|
-
const originTokenSymbol = originToken?.symbol ?? ""
|
|
4491
|
-
|
|
4492
|
-
const options = {
|
|
4493
|
-
account: walletClient.account!,
|
|
4494
|
-
originTokenAddress: fromTokenAddress,
|
|
4495
|
-
originChainId: fromChainId,
|
|
4496
|
-
originTokenBalance: originTokenBalanceAmount,
|
|
4497
|
-
destinationChainId: toChainId,
|
|
4498
|
-
recipient: toRecipient,
|
|
4499
|
-
destinationTokenAddress: toTokenAddress,
|
|
4500
|
-
swapAmount: swapAmount.toString(),
|
|
4501
|
-
tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
|
|
4502
|
-
originTokenSymbol: originTokenSymbol,
|
|
4503
|
-
destinationTokenSymbol: destinationTokenSymbol,
|
|
4504
|
-
destinationCalldata: toCalldata as string,
|
|
4505
|
-
client: walletClient,
|
|
4506
|
-
apiClient,
|
|
4507
|
-
trailsClient,
|
|
4508
|
-
originRelayer,
|
|
4509
|
-
destinationRelayer,
|
|
4510
|
-
sourceTokenDecimals,
|
|
4511
|
-
destinationTokenDecimals,
|
|
4512
|
-
fee: "0",
|
|
4513
|
-
dryMode: false,
|
|
4514
|
-
onTransactionStateChange: onStatusUpdate ?? (() => {}),
|
|
4515
|
-
slippageTolerance: slippageTolerance?.toString(),
|
|
4516
|
-
quoteProvider: quoteProvider,
|
|
4517
|
-
gasless: gasless ?? false,
|
|
4518
|
-
paymasterUrl: paymasterUrl,
|
|
4519
|
-
selectedFeeToken: selectedFeeToken ?? undefined,
|
|
4520
|
-
}
|
|
4521
|
-
|
|
4522
|
-
logger.console.log("[trails-sdk] options", options)
|
|
4523
|
-
|
|
4524
|
-
const { quote: prepareSendQuote, send } = await prepareSend(options)
|
|
4525
|
-
|
|
4526
|
-
const quote = {
|
|
4527
|
-
fromAmount: prepareSendQuote.originAmount,
|
|
4528
|
-
toAmount: prepareSendQuote.destinationAmount,
|
|
4529
|
-
fromAmountMin: prepareSendQuote.originAmountMin,
|
|
4530
|
-
toAmountMin: prepareSendQuote.destinationAmountMin,
|
|
4531
|
-
originToken: prepareSendQuote.originToken,
|
|
4532
|
-
destinationToken: prepareSendQuote.destinationToken,
|
|
4533
|
-
originChain: prepareSendQuote.originChain,
|
|
4534
|
-
destinationChain: prepareSendQuote.destinationChain,
|
|
4535
|
-
fees: prepareSendQuote.fees,
|
|
4536
|
-
priceImpact: prepareSendQuote.priceImpact,
|
|
4537
|
-
completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
|
|
4538
|
-
slippageTolerance: prepareSendQuote.slippageTolerance,
|
|
4539
|
-
transactionStates: prepareSendQuote.transactionStates,
|
|
4540
|
-
originTokenRate: prepareSendQuote.originTokenRate,
|
|
4541
|
-
destinationTokenRate: prepareSendQuote.destinationTokenRate,
|
|
4542
|
-
quoteProvider: prepareSendQuote.quoteProvider,
|
|
4543
|
-
fromAmountUsdDisplay:
|
|
4544
|
-
prepareSendQuote.originAmountUsdDisplay ?? undefined,
|
|
4545
|
-
toAmountUsdDisplay:
|
|
4546
|
-
prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
|
|
4547
|
-
gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
|
|
4548
|
-
gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
|
|
4549
|
-
gasCost: prepareSendQuote.gasCost ?? undefined,
|
|
4550
|
-
gasCostFormatted: prepareSendQuote.gasCostFormatted ?? undefined,
|
|
4551
|
-
}
|
|
4552
|
-
|
|
4553
|
-
const swap = async (): Promise<SwapReturn> => {
|
|
4554
|
-
const {
|
|
4555
|
-
originUserTxReceipt,
|
|
4556
|
-
destinationMetaTxnReceipt,
|
|
4557
|
-
totalCompletionSeconds,
|
|
4558
|
-
} = await send({
|
|
4559
|
-
selectedFeeToken: selectedFeeToken ?? undefined,
|
|
4560
|
-
})
|
|
4561
|
-
|
|
4562
|
-
return {
|
|
4563
|
-
originTransaction: {
|
|
4564
|
-
transactionHash: originUserTxReceipt?.transactionHash,
|
|
4565
|
-
explorerUrl: getExplorerUrl({
|
|
4566
|
-
txHash: originUserTxReceipt?.transactionHash as string,
|
|
4567
|
-
chainId: fromChainId,
|
|
4568
|
-
}),
|
|
4569
|
-
receipt: originUserTxReceipt,
|
|
4570
|
-
},
|
|
4571
|
-
destinationTransaction: {
|
|
4572
|
-
transactionHash: destinationMetaTxnReceipt?.txnHash,
|
|
4573
|
-
explorerUrl: getExplorerUrl({
|
|
4574
|
-
txHash: destinationMetaTxnReceipt?.txnHash as string,
|
|
4575
|
-
chainId: toChainId,
|
|
4576
|
-
}),
|
|
4577
|
-
receipt: destinationMetaTxnReceipt,
|
|
4578
|
-
},
|
|
4579
|
-
totalCompletionSeconds,
|
|
4580
|
-
}
|
|
4581
|
-
}
|
|
4582
|
-
|
|
4583
|
-
return {
|
|
4584
|
-
quote,
|
|
4585
|
-
swap,
|
|
4586
|
-
}
|
|
4587
|
-
} catch (error) {
|
|
4588
|
-
logger.console.error(
|
|
4589
|
-
"[trails-sdk] [useQuote] Error getting quote:",
|
|
4590
|
-
error,
|
|
4591
|
-
)
|
|
4592
|
-
throw getFullErrorMessage(error)
|
|
4593
|
-
}
|
|
4594
|
-
},
|
|
4595
|
-
// Prevent unnecessary refetching
|
|
4596
|
-
enabled: Boolean(
|
|
4597
|
-
walletClient &&
|
|
4598
|
-
apiClient &&
|
|
4599
|
-
fromTokenAddress &&
|
|
4600
|
-
toTokenAddress &&
|
|
4601
|
-
swapAmount &&
|
|
4602
|
-
toRecipient &&
|
|
4603
|
-
fromChainId &&
|
|
4604
|
-
toChainId &&
|
|
4605
|
-
indexerGatewayClient,
|
|
4606
|
-
),
|
|
4607
|
-
staleTime: 30 * 1000, // Consider data fresh for 30 seconds
|
|
4608
|
-
refetchOnWindowFocus: false, // Don't refetch when window regains focus
|
|
4609
|
-
refetchOnMount: false, // Don't refetch on component remount if data exists
|
|
4610
|
-
refetchInterval: false, // Disable automatic polling
|
|
4611
|
-
retry: 2, // Limit retry attempts
|
|
4612
|
-
refetchOnReconnect: true, // Refetch when network reconnects
|
|
4613
|
-
})
|
|
4614
|
-
|
|
4615
|
-
return {
|
|
4616
|
-
quote: data?.quote || null,
|
|
4617
|
-
swap: data?.swap || null,
|
|
4618
|
-
isLoadingQuote: isLoading,
|
|
4619
|
-
quoteError: error,
|
|
4620
|
-
refetchQuote: () => refetch(),
|
|
4621
|
-
}
|
|
4622
|
-
}
|
|
4623
|
-
|
|
4624
|
-
export function getFeesFromIntent(
|
|
4625
|
-
intent: GetIntentCallsPayloadsReturn,
|
|
4626
|
-
{
|
|
4627
|
-
tradeType,
|
|
4628
|
-
fromAmountUsd,
|
|
4629
|
-
toAmountUsd,
|
|
4630
|
-
}: { tradeType: TradeType; fromAmountUsd: number; toAmountUsd: number },
|
|
4631
|
-
): PrepareSendFees {
|
|
4632
|
-
const totalFeeAmountUsd = intent.payloads?.trailsFee?.totalFeeUSD ?? 0
|
|
4633
|
-
const totalFeeAmountUsdDisplay = formatUsdAmountDisplay(totalFeeAmountUsd)
|
|
4634
|
-
|
|
4635
|
-
logger.console.log("[trails-sdk] getFeesFromIntent", {
|
|
4636
|
-
tradeType,
|
|
4637
|
-
fromAmountUsd,
|
|
4638
|
-
toAmountUsd,
|
|
4639
|
-
totalFeeAmountUsd,
|
|
4640
|
-
totalFeeAmountUsdDisplay,
|
|
4641
|
-
})
|
|
4642
|
-
|
|
4643
|
-
return {
|
|
4644
|
-
feeTokenAddress: intent.payloads.trailsFee?.feeToken ?? zeroAddress,
|
|
4645
|
-
totalFeeAmount: intent.payloads.trailsFee?.totalFeeAmount ?? "0",
|
|
4646
|
-
totalFeeAmountUsd: totalFeeAmountUsd.toString(),
|
|
4647
|
-
totalFeeAmountUsdDisplay,
|
|
4648
|
-
}
|
|
4649
|
-
}
|
|
4650
|
-
|
|
4651
|
-
export function getSlippageToleranceFromIntent(
|
|
4652
|
-
intent: GetIntentCallsPayloadsReturn,
|
|
4653
|
-
): string {
|
|
4654
|
-
return intent.payloads.quote?.maxSlippage?.toString() ?? "0"
|
|
4655
|
-
}
|
|
4656
|
-
|
|
4657
|
-
export function getPriceImpactFromIntent(
|
|
4658
|
-
intent: GetIntentCallsPayloadsReturn,
|
|
4659
|
-
): string {
|
|
4660
|
-
return intent.payloads?.quote?.priceImpact?.toString() ?? "0"
|
|
4661
|
-
}
|
|
4662
|
-
|
|
4663
|
-
export function getPriceImpactUsdFromIntent(
|
|
4664
|
-
intent: GetIntentCallsPayloadsReturn,
|
|
4665
|
-
): string {
|
|
4666
|
-
// Temporary type assertion until API types are regenerated
|
|
4667
|
-
return (intent.payloads?.quote as any)?.priceImpactUsd?.toString() ?? "0"
|
|
4668
|
-
}
|
|
4669
|
-
|
|
4670
|
-
export function getFeesFromRelaySdkQuote(quote: RelayQuote): PrepareSendFees {
|
|
4671
|
-
const totalFeeAmountUsd = quote?.fees?.relayer?.amount ?? 0
|
|
4672
|
-
const totalFeeAmountUsdDisplay = formatUsdAmountDisplay(totalFeeAmountUsd)
|
|
4673
|
-
return {
|
|
4674
|
-
feeTokenAddress: quote?.fees?.relayer?.currency?.address ?? zeroAddress,
|
|
4675
|
-
totalFeeAmount: quote?.fees?.relayer?.amount ?? "0",
|
|
4676
|
-
totalFeeAmountUsd: totalFeeAmountUsd.toString(),
|
|
4677
|
-
totalFeeAmountUsdDisplay,
|
|
4678
|
-
}
|
|
4679
|
-
}
|
|
4680
|
-
|
|
4681
|
-
export function getSlippageToleranceFromRelaySdkQuote(
|
|
4682
|
-
quote: RelayQuote,
|
|
4683
|
-
): string {
|
|
4684
|
-
return (
|
|
4685
|
-
Number(quote?.details?.slippageTolerance?.origin?.percent ?? "0") / 100
|
|
4686
|
-
).toString()
|
|
4687
|
-
}
|
|
4688
|
-
|
|
4689
|
-
export function getPriceImpactFromRelaySdkQuote(quote: RelayQuote): string {
|
|
4690
|
-
return (quote?.details?.swapImpact?.percent ?? 0).toString()
|
|
4691
|
-
}
|
|
4692
|
-
|
|
4693
|
-
export function getPriceImpactUsdFromRelaySdkQuote(quote: RelayQuote): string {
|
|
4694
|
-
return (quote?.details?.swapImpact?.usd ?? 0).toString()
|
|
4695
|
-
}
|
|
4696
|
-
|
|
4697
|
-
export function getZeroFees(): PrepareSendFees {
|
|
4698
|
-
return {
|
|
4699
|
-
feeTokenAddress: zeroAddress,
|
|
4700
|
-
totalFeeAmount: "0",
|
|
4701
|
-
totalFeeAmountUsd: "0",
|
|
4702
|
-
totalFeeAmountUsdDisplay: formatUsdAmountDisplay(0),
|
|
4703
|
-
}
|
|
4704
|
-
}
|
|
4705
|
-
|
|
4706
|
-
// TODO: make this dyanamic
|
|
4707
|
-
export function getCompletionEstimateSeconds({
|
|
4708
|
-
originChainId,
|
|
4709
|
-
destinationChainId,
|
|
4710
|
-
}: {
|
|
4711
|
-
originChainId: number
|
|
4712
|
-
destinationChainId: number
|
|
4713
|
-
}): number {
|
|
4714
|
-
if (originChainId === mainnet.id && destinationChainId === mainnet.id) {
|
|
4715
|
-
return 60
|
|
4716
|
-
}
|
|
4717
|
-
|
|
4718
|
-
if (originChainId === mainnet.id || destinationChainId === mainnet.id) {
|
|
4719
|
-
return 45
|
|
4720
|
-
}
|
|
4721
|
-
|
|
4722
|
-
return 15
|
|
4723
|
-
}
|
|
4724
|
-
|
|
4725
|
-
export async function getNormalizedQuoteObject({
|
|
4726
|
-
originDepositAddress,
|
|
4727
|
-
destinationDepositAddress,
|
|
4728
|
-
destinationAddress,
|
|
4729
|
-
destinationCalldata,
|
|
4730
|
-
originChainId,
|
|
4731
|
-
destinationChainId,
|
|
4732
|
-
originAmount,
|
|
4733
|
-
originAmountMin,
|
|
4734
|
-
destinationAmount,
|
|
4735
|
-
destinationAmountMin,
|
|
4736
|
-
originTokenAddress,
|
|
4737
|
-
destinationTokenAddress,
|
|
4738
|
-
originTokenPriceUsd,
|
|
4739
|
-
destinationTokenPriceUsd,
|
|
4740
|
-
transactionStates,
|
|
4741
|
-
fees,
|
|
4742
|
-
slippageTolerance,
|
|
4743
|
-
priceImpact,
|
|
4744
|
-
priceImpactUsd,
|
|
4745
|
-
originNativeTokenPriceUsd,
|
|
4746
|
-
quoteProvider,
|
|
4747
|
-
noSufficientBalance,
|
|
4748
|
-
estimatedGasLimit,
|
|
4749
|
-
}: {
|
|
4750
|
-
originDepositAddress?: string
|
|
4751
|
-
destinationDepositAddress?: string
|
|
4752
|
-
destinationAddress?: string
|
|
4753
|
-
destinationCalldata?: string
|
|
4754
|
-
originChainId: number
|
|
4755
|
-
destinationChainId?: number
|
|
4756
|
-
originAmount: string
|
|
4757
|
-
originAmountMin?: string
|
|
4758
|
-
destinationAmount: string
|
|
4759
|
-
destinationAmountMin?: string
|
|
4760
|
-
originTokenAddress: string
|
|
4761
|
-
destinationTokenAddress?: string
|
|
4762
|
-
originTokenPriceUsd?: string | null
|
|
4763
|
-
destinationTokenPriceUsd?: string | null
|
|
4764
|
-
transactionStates?: TransactionState[]
|
|
4765
|
-
fees?: PrepareSendFees
|
|
4766
|
-
slippageTolerance?: string
|
|
4767
|
-
priceImpact?: string
|
|
4768
|
-
priceImpactUsd?: string
|
|
4769
|
-
originNativeTokenPriceUsd?: number | null
|
|
4770
|
-
quoteProvider?: string
|
|
4771
|
-
noSufficientBalance?: boolean
|
|
4772
|
-
estimatedGasLimit?: bigint
|
|
4773
|
-
}): Promise<PrepareSendQuote> {
|
|
4774
|
-
if (!destinationChainId) {
|
|
4775
|
-
throw new Error("Destination chain id is required")
|
|
4776
|
-
}
|
|
4777
|
-
|
|
4778
|
-
if (!destinationTokenAddress && originChainId === destinationChainId) {
|
|
4779
|
-
destinationTokenAddress = originTokenAddress
|
|
4780
|
-
}
|
|
4781
|
-
|
|
4782
|
-
if (!destinationTokenAddress && originChainId === destinationChainId) {
|
|
4783
|
-
destinationTokenAddress = originTokenAddress
|
|
4784
|
-
}
|
|
4785
|
-
|
|
4786
|
-
if (!destinationTokenAddress) {
|
|
4787
|
-
throw new Error("Destination token address is required")
|
|
4788
|
-
}
|
|
4789
|
-
|
|
4790
|
-
const originToken = await getTokenInfo(originChainId, originTokenAddress)
|
|
4791
|
-
const destinationToken = await getTokenInfo(
|
|
4792
|
-
destinationChainId,
|
|
4793
|
-
destinationTokenAddress,
|
|
4794
|
-
)
|
|
4795
|
-
|
|
4796
|
-
const originChain = getChainInfo(originChainId)
|
|
4797
|
-
const destinationChain = getChainInfo(destinationChainId)
|
|
4798
|
-
|
|
4799
|
-
if (!originToken || !destinationToken || !originChain || !destinationChain) {
|
|
4800
|
-
logger.console.error("[trails-sdk] Token or chain not found", {
|
|
4801
|
-
originToken,
|
|
4802
|
-
destinationToken,
|
|
4803
|
-
originChain,
|
|
4804
|
-
destinationChain,
|
|
4805
|
-
})
|
|
4806
|
-
throw new Error("Token or chain not found")
|
|
4807
|
-
}
|
|
4808
|
-
|
|
4809
|
-
const originAmountMinFormatted = formatRawAmount(
|
|
4810
|
-
originAmountMin || "0",
|
|
4811
|
-
originToken.decimals,
|
|
4812
|
-
)
|
|
4813
|
-
const originAmountMinUsdFormatted = formatAmount(
|
|
4814
|
-
calcAmountUsdPrice({
|
|
4815
|
-
amount: originAmountMinFormatted,
|
|
4816
|
-
usdPrice: originTokenPriceUsd,
|
|
4817
|
-
}),
|
|
4818
|
-
)
|
|
4819
|
-
const originAmountMinUsdDisplay = formatUsdAmountDisplay(
|
|
4820
|
-
originAmountMinUsdFormatted,
|
|
4821
|
-
)
|
|
4822
|
-
|
|
4823
|
-
const destinationAmountMinFormatted = formatRawAmount(
|
|
4824
|
-
destinationAmountMin || "0",
|
|
4825
|
-
destinationToken.decimals,
|
|
4826
|
-
)
|
|
4827
|
-
const destinationAmountMinUsdFormatted = formatAmount(
|
|
4828
|
-
calcAmountUsdPrice({
|
|
4829
|
-
amount: destinationAmountMinFormatted,
|
|
4830
|
-
usdPrice: destinationTokenPriceUsd,
|
|
4831
|
-
}),
|
|
4832
|
-
)
|
|
4833
|
-
const destinationAmountMinUsdDisplay = formatUsdAmountDisplay(
|
|
4834
|
-
destinationAmountMinUsdFormatted,
|
|
4835
|
-
)
|
|
4836
|
-
|
|
4837
|
-
const originAmountFormatted = formatRawAmount(
|
|
4838
|
-
originAmount,
|
|
4839
|
-
originToken.decimals,
|
|
4840
|
-
)
|
|
4841
|
-
const originAmountUsdFormatted = formatAmount(
|
|
4842
|
-
calcAmountUsdPrice({
|
|
4843
|
-
amount: originAmountFormatted,
|
|
4844
|
-
usdPrice: originTokenPriceUsd,
|
|
4845
|
-
}),
|
|
4846
|
-
)
|
|
4847
|
-
const originAmountUsdDisplay = formatUsdAmountDisplay(
|
|
4848
|
-
originAmountUsdFormatted,
|
|
4849
|
-
)
|
|
4850
|
-
|
|
4851
|
-
const destinationAmountFormatted = formatRawAmount(
|
|
4852
|
-
destinationAmount,
|
|
4853
|
-
destinationToken.decimals,
|
|
4854
|
-
)
|
|
4855
|
-
const destinationAmountUsdFormatted = formatAmount(
|
|
4856
|
-
calcAmountUsdPrice({
|
|
4857
|
-
amount: destinationAmountFormatted,
|
|
4858
|
-
usdPrice: destinationTokenPriceUsd,
|
|
4859
|
-
}),
|
|
4860
|
-
)
|
|
4861
|
-
const destinationAmountUsdDisplay = formatUsdAmountDisplay(
|
|
4862
|
-
destinationAmountUsdFormatted,
|
|
4863
|
-
)
|
|
4864
|
-
|
|
4865
|
-
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
4866
|
-
|
|
4867
|
-
const publicClient = createPublicClient({
|
|
4868
|
-
chain: originChain,
|
|
4869
|
-
transport: http(),
|
|
4870
|
-
})
|
|
4871
|
-
|
|
4872
|
-
let gasCostUsd: number = 0
|
|
4873
|
-
let gasCostUsdDisplay: string = "0"
|
|
4874
|
-
let gasCost: string = "0"
|
|
4875
|
-
let gasCostFormatted: string = "0"
|
|
4876
|
-
try {
|
|
4877
|
-
if (originNativeTokenPriceUsd) {
|
|
4878
|
-
// Use the actual estimated gas limit if provided, otherwise use default
|
|
4879
|
-
// This ensures the quote gas cost matches what will be used in the actual transaction
|
|
4880
|
-
const gasLimitForCost = estimatedGasLimit || DEFAULT_MIN_GASLIMIT
|
|
4881
|
-
|
|
4882
|
-
logger.console.log(
|
|
4883
|
-
"[trails-sdk][gas-estimation] Calculating gas cost for quote with gasLimit:",
|
|
4884
|
-
gasLimitForCost,
|
|
4885
|
-
"(estimated:",
|
|
4886
|
-
estimatedGasLimit,
|
|
4887
|
-
"default:",
|
|
4888
|
-
DEFAULT_MIN_GASLIMIT,
|
|
4889
|
-
")",
|
|
4890
|
-
)
|
|
4891
|
-
|
|
4892
|
-
const gasCostWei = await estimateGasCost(publicClient, gasLimitForCost)
|
|
4893
|
-
gasCost = gasCostWei.toString()
|
|
4894
|
-
gasCostFormatted = formatUnits(gasCostWei, 18) // Native tokens always use 18 decimals
|
|
4895
|
-
|
|
4896
|
-
// Calculate USD value
|
|
4897
|
-
gasCostUsd = await estimateGasCostUsd(
|
|
4898
|
-
publicClient,
|
|
4899
|
-
originNativeTokenPriceUsd,
|
|
4900
|
-
gasLimitForCost,
|
|
4901
|
-
)
|
|
4902
|
-
gasCostUsdDisplay = formatUsdAmountDisplay(gasCostUsd)
|
|
4903
|
-
|
|
4904
|
-
logger.console.log(
|
|
4905
|
-
"[trails-sdk][gas-estimation] Quote gas cost calculated:",
|
|
4906
|
-
{
|
|
4907
|
-
gasCostWei,
|
|
4908
|
-
gasCost,
|
|
4909
|
-
gasCostFormatted,
|
|
4910
|
-
gasCostUsd,
|
|
4911
|
-
gasCostUsdDisplay,
|
|
4912
|
-
},
|
|
4913
|
-
)
|
|
4914
|
-
}
|
|
4915
|
-
} catch (error) {
|
|
4916
|
-
logger.console.error(
|
|
4917
|
-
"[trails-sdk][gas-estimation] Error estimating gas cost for quote display",
|
|
4918
|
-
error,
|
|
4919
|
-
)
|
|
4920
|
-
}
|
|
4921
|
-
|
|
4922
|
-
// Calculate exchange rates
|
|
4923
|
-
const originTokenPrice = originTokenPriceUsd ? Number(originTokenPriceUsd) : 0
|
|
4924
|
-
const destinationTokenPrice = destinationTokenPriceUsd
|
|
4925
|
-
? Number(destinationTokenPriceUsd)
|
|
4926
|
-
: 0
|
|
4927
|
-
const exchangeRates = getTokenExchangeRates(
|
|
4928
|
-
originTokenPrice,
|
|
4929
|
-
destinationTokenPrice,
|
|
4930
|
-
originToken.symbol,
|
|
4931
|
-
destinationToken.symbol,
|
|
4932
|
-
)
|
|
4933
|
-
|
|
4934
|
-
let quoteProviderUrl = ""
|
|
4935
|
-
if (quoteProvider === "cctp") {
|
|
4936
|
-
quoteProviderUrl = "https://www.circle.com/"
|
|
4937
|
-
} else if (quoteProvider === "relay") {
|
|
4938
|
-
quoteProviderUrl = "https://relay.link/"
|
|
4939
|
-
} else if (quoteProvider === "lifi") {
|
|
4940
|
-
quoteProviderUrl = "https://li.fi/"
|
|
4941
|
-
}
|
|
4942
|
-
|
|
4943
|
-
const quoteProviderNames = {
|
|
4944
|
-
cctp: "Circle CCTP",
|
|
4945
|
-
relay: "Relay",
|
|
4946
|
-
lifi: "LiFi",
|
|
4947
|
-
}
|
|
4948
|
-
|
|
4949
|
-
const quoteProviderName =
|
|
4950
|
-
quoteProviderNames[quoteProvider as keyof typeof quoteProviderNames] ||
|
|
4951
|
-
quoteProvider ||
|
|
4952
|
-
""
|
|
4953
|
-
const quoteProviderInfo: QuoteProviderInfo = {
|
|
4954
|
-
id: quoteProvider || "",
|
|
4955
|
-
name: quoteProviderName || "",
|
|
4956
|
-
url: quoteProviderUrl || "",
|
|
4957
|
-
}
|
|
4958
|
-
|
|
4959
|
-
const priceImpactUsdDisplay = formatUsdAmountDisplay(priceImpactUsd)
|
|
4960
|
-
|
|
4961
|
-
return {
|
|
4962
|
-
originDepositAddress: originDepositAddress || "",
|
|
4963
|
-
destinationDepositAddress: destinationDepositAddress || "",
|
|
4964
|
-
destinationAddress: destinationAddress || "",
|
|
4965
|
-
destinationCalldata: hasCustomCalldata ? destinationCalldata || "" : "",
|
|
4966
|
-
originAmount: originAmount || "0",
|
|
4967
|
-
originAmountMin: originAmountMin || originAmount || "0",
|
|
4968
|
-
originAmountMinUsdFormatted: originAmountMinUsdFormatted || "0",
|
|
4969
|
-
originAmountMinUsdDisplay: originAmountMinUsdDisplay || "0",
|
|
4970
|
-
destinationAmount: destinationAmount || "0",
|
|
4971
|
-
destinationAmountMin: destinationAmountMin || destinationAmount || "0",
|
|
4972
|
-
destinationAmountMinUsdFormatted: destinationAmountMinUsdFormatted || "0",
|
|
4973
|
-
destinationAmountMinUsdDisplay: destinationAmountMinUsdDisplay || "0",
|
|
4974
|
-
originAmountFormatted,
|
|
4975
|
-
originAmountUsdFormatted,
|
|
4976
|
-
originAmountUsdDisplay,
|
|
4977
|
-
destinationAmountFormatted,
|
|
4978
|
-
destinationAmountUsdFormatted,
|
|
4979
|
-
destinationAmountUsdDisplay,
|
|
4980
|
-
originToken,
|
|
4981
|
-
destinationToken,
|
|
4982
|
-
fees: fees || getZeroFees(),
|
|
4983
|
-
slippageTolerance: slippageTolerance || "0",
|
|
4984
|
-
priceImpact: priceImpact || "0",
|
|
4985
|
-
priceImpactUsdDisplay: priceImpactUsdDisplay || "0",
|
|
4986
|
-
originChain,
|
|
4987
|
-
destinationChain,
|
|
4988
|
-
completionEstimateSeconds: getCompletionEstimateSeconds({
|
|
4989
|
-
originChainId,
|
|
4990
|
-
destinationChainId,
|
|
4991
|
-
}),
|
|
4992
|
-
transactionStates: transactionStates || [],
|
|
4993
|
-
gasCostUsd,
|
|
4994
|
-
gasCostUsdDisplay,
|
|
4995
|
-
gasCost,
|
|
4996
|
-
gasCostFormatted,
|
|
4997
|
-
originTokenRate: exchangeRates.originTokenRate,
|
|
4998
|
-
destinationTokenRate: exchangeRates.destinationTokenRate,
|
|
4999
|
-
originAmountDisplay: formatAmountDisplay(originAmountFormatted),
|
|
5000
|
-
destinationAmountDisplay: formatAmountDisplay(destinationAmountFormatted),
|
|
5001
|
-
originAmountMinDisplay: formatAmountDisplay(originAmountMinFormatted),
|
|
5002
|
-
destinationAmountMinDisplay: formatAmountDisplay(
|
|
5003
|
-
destinationAmountMinFormatted,
|
|
5004
|
-
),
|
|
5005
|
-
quoteProvider: quoteProviderInfo,
|
|
5006
|
-
noSufficientBalance: noSufficientBalance || false,
|
|
5007
|
-
}
|
|
5008
|
-
}
|
|
5009
|
-
|
|
5010
|
-
function getTokenExchangeRates(
|
|
5011
|
-
originTokenPrice: number,
|
|
5012
|
-
destinationTokenPrice: number,
|
|
5013
|
-
originTokenSymbol: string,
|
|
5014
|
-
destinationTokenSymbol: string,
|
|
5015
|
-
): { originTokenRate: string; destinationTokenRate: string } {
|
|
5016
|
-
if (originTokenPrice === 0 || destinationTokenPrice === 0) {
|
|
5017
|
-
return {
|
|
5018
|
-
originTokenRate: "0",
|
|
5019
|
-
destinationTokenRate: "0",
|
|
5020
|
-
}
|
|
5021
|
-
}
|
|
5022
|
-
|
|
5023
|
-
const originToDestination = originTokenPrice / destinationTokenPrice
|
|
5024
|
-
const destinationToOrigin = destinationTokenPrice / originTokenPrice
|
|
5025
|
-
|
|
5026
|
-
// Format the rates with appropriate precision
|
|
5027
|
-
const originTokenRate = formatAmountDisplay(originToDestination, {
|
|
5028
|
-
maxFractionDigits: destinationTokenSymbol === "USDC" ? 2 : 7,
|
|
5029
|
-
})
|
|
5030
|
-
const destinationTokenRate = formatAmountDisplay(destinationToOrigin, {
|
|
5031
|
-
maxFractionDigits: originTokenSymbol === "USDC" ? 2 : 7,
|
|
5032
|
-
})
|
|
5033
|
-
|
|
5034
|
-
return {
|
|
5035
|
-
originTokenRate: `1 ${originTokenSymbol} = ${originTokenRate} ${destinationTokenSymbol}`,
|
|
5036
|
-
destinationTokenRate: `1 ${destinationTokenSymbol} = ${destinationTokenRate} ${originTokenSymbol}`,
|
|
5037
|
-
}
|
|
5038
|
-
}
|
|
408
|
+
// Re-export useQuote types and hook from the new location
|
|
409
|
+
export type {
|
|
410
|
+
UseQuoteProps,
|
|
411
|
+
Quote,
|
|
412
|
+
UseQuoteReturn,
|
|
413
|
+
SwapReturn,
|
|
414
|
+
QuoteProviderInfo,
|
|
415
|
+
} from "./widget/hooks/useQuote.js"
|
|
416
|
+
export { useQuote } from "./widget/hooks/useQuote.js"
|
|
417
|
+
|
|
418
|
+
// Re-export quote and fee utilities from transactionIntent for backward compatibility
|
|
419
|
+
export {
|
|
420
|
+
getFeesFromIntent,
|
|
421
|
+
getSlippageToleranceFromIntent,
|
|
422
|
+
getPriceImpactFromIntent,
|
|
423
|
+
getPriceImpactUsdFromIntent,
|
|
424
|
+
getFeesFromRelaySdkQuote,
|
|
425
|
+
getSlippageToleranceFromRelaySdkQuote,
|
|
426
|
+
getPriceImpactFromRelaySdkQuote,
|
|
427
|
+
getPriceImpactUsdFromRelaySdkQuote,
|
|
428
|
+
getZeroFees,
|
|
429
|
+
getCompletionEstimateSeconds,
|
|
430
|
+
getNormalizedQuoteObject,
|
|
431
|
+
} from "./transactionIntent/index.js"
|