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