0xtrails 0.1.13 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aave.d.ts.map +1 -1
- package/dist/analytics.d.ts +12 -2
- package/dist/analytics.d.ts.map +1 -1
- package/dist/apiClient.d.ts +1 -1
- package/dist/apiClient.d.ts.map +1 -1
- package/dist/{ccip-D3gTQONK.js → ccip-BbfANth7.js} +12 -12
- package/dist/cctp.d.ts.map +1 -1
- package/dist/cctpqueue.d.ts +3 -3
- package/dist/cctpqueue.d.ts.map +1 -1
- package/dist/chains.d.ts.map +1 -1
- package/dist/config.d.ts +18 -5
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +6 -5
- package/dist/constants.d.ts.map +1 -1
- package/dist/contractUtils.d.ts +2 -0
- package/dist/contractUtils.d.ts.map +1 -1
- package/dist/customChains.d.ts +24 -0
- package/dist/customChains.d.ts.map +1 -0
- package/dist/gasless.d.ts +19 -7
- package/dist/gasless.d.ts.map +1 -1
- package/dist/{index-CnUM7lKf.js → index-WpIVoh3X.js} +36741 -31761
- package/dist/index.d.ts +5 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +405 -394
- package/dist/indexerClient.d.ts +10 -0
- package/dist/indexerClient.d.ts.map +1 -1
- package/dist/intentEntrypoint.d.ts +122 -0
- package/dist/intentEntrypoint.d.ts.map +1 -0
- package/dist/intents.d.ts +5 -3
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxnMonitor.d.ts.map +1 -1
- package/dist/morpho.d.ts.map +1 -1
- package/dist/pools.d.ts +3 -1
- package/dist/pools.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +18 -9
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/prices.d.ts +1 -1
- package/dist/prices.d.ts.map +1 -1
- package/dist/relaySdk.d.ts.map +1 -1
- package/dist/relayer.d.ts.map +1 -1
- package/dist/toast.d.ts +9 -0
- package/dist/toast.d.ts.map +1 -0
- package/dist/tokenBalances.d.ts +6 -2
- package/dist/tokenBalances.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/trails.d.ts +6 -5
- package/dist/trails.d.ts.map +1 -1
- package/dist/trailsClient.d.ts +12 -0
- package/dist/trailsClient.d.ts.map +1 -0
- package/dist/trailsRouter.d.ts +22 -0
- package/dist/trailsRouter.d.ts.map +1 -0
- package/dist/transactions.d.ts +8 -1
- package/dist/transactions.d.ts.map +1 -1
- package/dist/wallets.d.ts.map +1 -1
- package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
- package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/components/AccountSettings.d.ts +7 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -0
- package/dist/widget/components/ChainList.d.ts +0 -1
- package/dist/widget/components/ChainList.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +46 -0
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -0
- package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts +9 -0
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -0
- package/dist/widget/components/DebugMenu.d.ts.map +1 -1
- package/dist/widget/components/DebugScreensList.d.ts.map +1 -1
- package/dist/widget/components/DebugToast.d.ts +3 -0
- package/dist/widget/components/DebugToast.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/EarnPools.d.ts.map +1 -1
- package/dist/widget/components/FeeOption.d.ts +22 -0
- package/dist/widget/components/FeeOption.d.ts.map +1 -0
- package/dist/widget/components/FeeOptions.d.ts +13 -17
- package/dist/widget/components/FeeOptions.d.ts.map +1 -1
- package/dist/widget/components/Fund.d.ts +44 -0
- package/dist/widget/components/Fund.d.ts.map +1 -0
- package/dist/widget/components/FundMethods.d.ts +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/FundSendForm.d.ts.map +1 -1
- package/dist/widget/components/Identicon.d.ts +9 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -0
- package/dist/widget/components/MeshConnectExchanges.d.ts +5 -2
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectFlow.d.ts +2 -0
- package/dist/widget/components/MeshConnectFlow.d.ts.map +1 -1
- package/dist/widget/components/NativeGasOption.d.ts +12 -0
- package/dist/widget/components/NativeGasOption.d.ts.map +1 -0
- package/dist/widget/components/Pay.d.ts +46 -0
- package/dist/widget/components/Pay.d.ts.map +1 -0
- package/dist/widget/components/PaySendForm.d.ts.map +1 -1
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receive.d.ts.map +1 -1
- package/dist/widget/components/RecentTokens.d.ts.map +1 -1
- package/dist/widget/components/Recipients.d.ts +9 -0
- package/dist/widget/components/Recipients.d.ts.map +1 -0
- package/dist/widget/components/RefundWarning.d.ts +9 -0
- package/dist/widget/components/RefundWarning.d.ts.map +1 -0
- package/dist/widget/components/SimpleSwap.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts +1 -5
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/ThemeSyncer.d.ts +6 -0
- package/dist/widget/components/ThemeSyncer.d.ts.map +1 -0
- package/dist/widget/components/Toast.d.ts +24 -0
- package/dist/widget/components/Toast.d.ts.map +1 -0
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
- package/dist/widget/components/TruncatedAddress.d.ts +2 -0
- package/dist/widget/components/TruncatedAddress.d.ts.map +1 -1
- package/dist/widget/components/UserPreferences.d.ts +7 -0
- package/dist/widget/components/UserPreferences.d.ts.map +1 -0
- package/dist/widget/hooks/useBack.d.ts +2 -0
- package/dist/widget/hooks/useBack.d.ts.map +1 -1
- package/dist/widget/hooks/useBalanceVisible.d.ts +1 -0
- package/dist/widget/hooks/useBalanceVisible.d.ts.map +1 -1
- package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
- package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
- package/dist/widget/hooks/useDebugScreens.d.ts +1 -1
- package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts +54 -0
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -0
- package/dist/widget/hooks/useIntentTransactionHistory.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts +34 -0
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -0
- package/dist/widget/hooks/useRecipients.d.ts +17 -0
- package/dist/widget/hooks/useRecipients.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts +32 -0
- package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts +14 -0
- package/dist/widget/hooks/useSelectedMeshExchange.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts +12 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -0
- package/dist/widget/hooks/useSendForm.d.ts +10 -13
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/hooks/useSwapAmount.d.ts +13 -0
- package/dist/widget/hooks/useSwapAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useSwapSettings.d.ts +16 -0
- package/dist/widget/hooks/useSwapSettings.d.ts.map +1 -0
- package/dist/widget/hooks/useTargetAmount.d.ts +5 -0
- package/dist/widget/hooks/useTargetAmount.d.ts.map +1 -0
- package/dist/widget/hooks/useTheme.d.ts +14 -0
- package/dist/widget/hooks/useTheme.d.ts.map +1 -0
- package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +9 -0
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +38 -36
- package/src/aave.ts +6 -1
- package/src/analytics.ts +109 -53
- package/src/apiClient.ts +1 -1
- package/src/cctp.ts +6 -2
- package/src/cctpqueue.ts +7 -7
- package/src/chains.ts +18 -0
- package/src/config.ts +63 -17
- package/src/constants.ts +20 -16
- package/src/contractUtils.ts +33 -2
- package/src/customChains.ts +24 -0
- package/src/gasless.ts +162 -109
- package/src/index.ts +11 -1
- package/src/indexerClient.ts +73 -1
- package/src/intentEntrypoint.ts +218 -0
- package/src/intents.ts +85 -54
- package/src/metaTxnMonitor.ts +1 -0
- package/src/morpho.ts +13 -2
- package/src/pools.ts +68 -86
- package/src/prepareSend.ts +1719 -967
- package/src/prices.ts +51 -7
- package/src/relaySdk.ts +6 -4
- package/src/relayer.ts +6 -3
- package/src/toast.ts +110 -0
- package/src/tokenBalances.ts +112 -20
- package/src/tokens.ts +70 -7
- package/src/trails.ts +81 -80
- package/src/trailsClient.ts +48 -0
- package/src/{proxyCaller.ts → trailsRouter.ts} +25 -20
- package/src/transactions.ts +30 -88
- package/src/umd.tsx +1 -1
- package/src/wallets.ts +2 -1
- package/src/widget/assets/sequence-logo.svg +15 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +18 -159
- package/src/widget/components/AccountIntentTransactionHistory.tsx +346 -63
- package/src/widget/components/AccountSettings.tsx +102 -0
- package/src/widget/components/ChainFilterDropdown.tsx +1 -1
- package/src/widget/components/ChainList.tsx +10 -20
- package/src/widget/components/ClassicSwap.tsx +921 -0
- package/src/widget/components/ConfigDisplay.tsx +41 -5
- package/src/widget/components/ConnectWallet.tsx +168 -11
- package/src/widget/components/ConnectedWallets.tsx +342 -0
- package/src/widget/components/DebugMenu.tsx +2 -0
- package/src/widget/components/DebugScreensList.tsx +3 -0
- package/src/widget/components/DebugToast.tsx +63 -0
- package/src/widget/components/Earn.tsx +112 -143
- package/src/widget/components/EarnPools.tsx +2 -4
- package/src/widget/components/EarnPoolsFilters.tsx +6 -6
- package/src/widget/components/FeeOption.tsx +78 -0
- package/src/widget/components/FeeOptions.tsx +192 -127
- package/src/widget/components/Fund.tsx +1236 -0
- package/src/widget/components/FundMethods.tsx +4 -4
- package/src/widget/components/FundSendForm.tsx +1 -34
- package/src/widget/components/Identicon.tsx +158 -0
- package/src/widget/components/MeshConnectExchanges.tsx +32 -3
- package/src/widget/components/MeshConnectFlow.tsx +23 -4
- package/src/widget/components/NativeGasOption.tsx +99 -0
- package/src/widget/components/Pay.tsx +1092 -0
- package/src/widget/components/PaySendForm.tsx +1 -38
- package/src/widget/components/QuoteDetails.tsx +1 -30
- package/src/widget/components/Receipt.tsx +1 -1
- package/src/widget/components/Receive.tsx +4 -2
- package/src/widget/components/RecentTokens.tsx +2 -1
- package/src/widget/components/Recipients.tsx +448 -0
- package/src/widget/components/RefundWarning.tsx +61 -0
- package/src/widget/components/ScreenHeader.tsx +1 -1
- package/src/widget/components/SimpleSwap.tsx +74 -58
- package/src/widget/components/Swap.tsx +35 -853
- package/src/widget/components/SwapSettings.tsx +5 -11
- package/src/widget/components/ThemeProvider.tsx +32 -0
- package/src/widget/components/ThemeSyncer.tsx +47 -0
- package/src/widget/components/Toast.tsx +315 -0
- package/src/widget/components/TokenList.tsx +2 -34
- package/src/widget/components/TokenSelector.tsx +14 -3
- package/src/widget/components/TransactionDetails.tsx +153 -13
- package/src/widget/components/TransferPendingVertical.tsx +1 -1
- package/src/widget/components/TruncatedAddress.tsx +5 -1
- package/src/widget/components/UserPreferences.tsx +155 -0
- package/src/widget/components/WalletList.tsx +1 -1
- package/src/widget/hooks/useBack.tsx +4 -0
- package/src/widget/hooks/useBalanceVisible.tsx +40 -2
- package/src/widget/hooks/useCheckout.ts +13 -0
- package/src/widget/hooks/useCurrentScreen.tsx +4 -0
- package/src/widget/hooks/useDebugScreens.ts +12 -2
- package/src/widget/hooks/useDefaultTokenSelection.tsx +471 -0
- package/src/widget/hooks/useIntentTransactionHistory.ts +212 -0
- package/src/widget/hooks/usePayMessage.tsx +370 -0
- package/src/widget/hooks/useRecipients.ts +168 -0
- package/src/widget/hooks/useSelectedFeeToken.tsx +299 -0
- package/src/widget/hooks/useSelectedMeshExchange.tsx +46 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +48 -0
- package/src/widget/hooks/useSendForm.ts +257 -49
- package/src/widget/hooks/useSwapAmount.tsx +50 -0
- package/src/widget/hooks/useSwapSettings.tsx +100 -0
- package/src/widget/hooks/useTargetAmount.ts +23 -0
- package/src/widget/hooks/useTheme.tsx +80 -0
- package/src/widget/hooks/useTokenList.ts +20 -11
- package/src/widget/index.css +45 -21
- package/src/widget/widget.tsx +294 -136
- package/dist/address.d.ts +0 -2
- package/dist/address.d.ts.map +0 -1
- package/dist/proxyCaller.d.ts +0 -21
- package/dist/proxyCaller.d.ts.map +0 -1
- package/src/address.ts +0 -6
package/src/prepareSend.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import type {
|
|
2
|
-
|
|
2
|
+
GetIntentCallsPayloadParams,
|
|
3
3
|
GetIntentCallsPayloadsReturn,
|
|
4
4
|
IntentPrecondition,
|
|
5
|
-
SequenceAPIClient,
|
|
6
5
|
} from "@0xsequence/trails-api"
|
|
6
|
+
import type { TrailsAPIClient } from "@0xsequence/trails-api"
|
|
7
7
|
import type { Relayer } from "@0xsequence/wallet-core"
|
|
8
8
|
import { useQuery } from "@tanstack/react-query"
|
|
9
9
|
import type {
|
|
@@ -52,11 +52,11 @@ import { queueCCTPTransfer } from "./cctpqueue.js"
|
|
|
52
52
|
import { getChainInfo, getTestnetChainInfo } from "./chains.js"
|
|
53
53
|
import { attemptSwitchChain } from "./chainSwitch.js"
|
|
54
54
|
import {
|
|
55
|
-
getSameChainSwapDifferentIntentAddresses,
|
|
56
55
|
getSequenceEnv,
|
|
57
56
|
getSlippageTolerance,
|
|
57
|
+
type SequenceEnv,
|
|
58
58
|
} from "./config.js"
|
|
59
|
-
import { intentEntrypoints
|
|
59
|
+
import { intentEntrypoints } from "./constants.js"
|
|
60
60
|
import {
|
|
61
61
|
decodeGuestModuleEvents,
|
|
62
62
|
decodeTrailsTokenSweeperEvents,
|
|
@@ -66,10 +66,16 @@ import { InsufficientBalanceError } from "./error.js"
|
|
|
66
66
|
import { estimateGasCostUsd } from "./estimate.js"
|
|
67
67
|
import { getExplorerUrl } from "./explorer.js"
|
|
68
68
|
import {
|
|
69
|
-
|
|
69
|
+
getNeedsIntentEntrypointApproval,
|
|
70
70
|
getPermitCalls,
|
|
71
71
|
getPermitSignature,
|
|
72
|
+
getUserNonce,
|
|
73
|
+
signIntent,
|
|
72
74
|
} from "./gasless.js"
|
|
75
|
+
import {
|
|
76
|
+
getIntentEntrypointFeeOptions,
|
|
77
|
+
isIntentEntrypointSupported,
|
|
78
|
+
} from "./intentEntrypoint.js"
|
|
73
79
|
import { useIndexerGatewayClient } from "./indexerClient.js"
|
|
74
80
|
import {
|
|
75
81
|
commitIntentConfig,
|
|
@@ -87,10 +93,6 @@ import {
|
|
|
87
93
|
} from "./paymasterSend.js"
|
|
88
94
|
import { findFirstPreconditionForChainId } from "./preconditions.js"
|
|
89
95
|
import { calcAmountUsdPrice, getTokenPrice } from "./prices.js"
|
|
90
|
-
import {
|
|
91
|
-
TRAILS_CONTRACT_PLACEHOLDER_AMOUNT,
|
|
92
|
-
wrapCalldataWithProxyCallerIfNeeded,
|
|
93
|
-
} from "./proxyCaller.js"
|
|
94
96
|
import { getQueryParam } from "./queryParams.js"
|
|
95
97
|
import type { MetaTxnReceipt, RelayerEnv } from "./relayer.js"
|
|
96
98
|
import { useRelayers } from "./relayer.js"
|
|
@@ -118,6 +120,10 @@ import {
|
|
|
118
120
|
useSupportedTokens,
|
|
119
121
|
type SupportedToken,
|
|
120
122
|
} from "./tokens.js"
|
|
123
|
+
import {
|
|
124
|
+
TRAILS_ROUTER_PLACEHOLDER_AMOUNT,
|
|
125
|
+
wrapCalldataWithTrailsRouterIfNeeded,
|
|
126
|
+
} from "./trailsRouter.js"
|
|
121
127
|
import type {
|
|
122
128
|
TransactionState,
|
|
123
129
|
TransactionStateStatus,
|
|
@@ -127,6 +133,9 @@ import { requestWithTimeout } from "./utils.js"
|
|
|
127
133
|
import type { CheckoutOnHandlers } from "./widget/hooks/useCheckout.js"
|
|
128
134
|
import { logger } from "./logger.js"
|
|
129
135
|
import { getIsCustomCalldata } from "./contractUtils.js"
|
|
136
|
+
import type { SequenceAPIClient } from "@0xsequence/api"
|
|
137
|
+
import { useTrailsClient } from "./trailsClient.js"
|
|
138
|
+
import { updatePersistentToast } from "./toast.js"
|
|
130
139
|
|
|
131
140
|
export enum TradeType {
|
|
132
141
|
EXACT_INPUT = "EXACT_INPUT",
|
|
@@ -150,6 +159,7 @@ export type PrepareSendOptions = {
|
|
|
150
159
|
client?: WalletClient
|
|
151
160
|
dryMode: boolean
|
|
152
161
|
apiClient: SequenceAPIClient
|
|
162
|
+
trailsClient: TrailsAPIClient
|
|
153
163
|
originRelayer: Relayer.Standard.Rpc.RpcRelayer
|
|
154
164
|
destinationRelayer: Relayer.Standard.Rpc.RpcRelayer
|
|
155
165
|
destinationCalldata?: string
|
|
@@ -167,6 +177,7 @@ export type PrepareSendOptions = {
|
|
|
167
177
|
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
168
178
|
checkoutOnHandlers?: CheckoutOnHandlers
|
|
169
179
|
refundAddress?: string
|
|
180
|
+
selectedFeeToken?: any
|
|
170
181
|
}
|
|
171
182
|
|
|
172
183
|
export type PrepareSendFees = {
|
|
@@ -215,7 +226,6 @@ export type PrepareSendQuote = {
|
|
|
215
226
|
destinationTokenRate: string
|
|
216
227
|
quoteProvider: QuoteProviderInfo | null
|
|
217
228
|
noSufficientBalance: boolean
|
|
218
|
-
minimumNotMet: boolean
|
|
219
229
|
}
|
|
220
230
|
|
|
221
231
|
export type PrepareSendReturn = {
|
|
@@ -223,10 +233,10 @@ export type PrepareSendReturn = {
|
|
|
223
233
|
feeOptions?: any
|
|
224
234
|
send: ({
|
|
225
235
|
onOriginSend,
|
|
226
|
-
|
|
236
|
+
selectedFeeToken,
|
|
227
237
|
}: {
|
|
228
238
|
onOriginSend?: () => void
|
|
229
|
-
|
|
239
|
+
selectedFeeToken?: any
|
|
230
240
|
}) => Promise<SendReturn>
|
|
231
241
|
}
|
|
232
242
|
|
|
@@ -316,7 +326,7 @@ function getIntentArgs(
|
|
|
316
326
|
slippageTolerance: string, // 0.03 = 3%
|
|
317
327
|
tradeType: TradeType,
|
|
318
328
|
provider?: string | null,
|
|
319
|
-
):
|
|
329
|
+
): GetIntentCallsPayloadParams {
|
|
320
330
|
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
321
331
|
|
|
322
332
|
if (!provider || provider === "auto") {
|
|
@@ -363,6 +373,7 @@ export async function prepareSend(
|
|
|
363
373
|
client: walletClient,
|
|
364
374
|
dryMode = false,
|
|
365
375
|
apiClient,
|
|
376
|
+
trailsClient,
|
|
366
377
|
originRelayer,
|
|
367
378
|
destinationRelayer,
|
|
368
379
|
destinationCalldata,
|
|
@@ -377,13 +388,26 @@ export async function prepareSend(
|
|
|
377
388
|
fundMethod,
|
|
378
389
|
mode,
|
|
379
390
|
checkoutOnHandlers,
|
|
391
|
+
selectedFeeToken,
|
|
380
392
|
} = options
|
|
381
393
|
let { sourceTokenPriceUsd, destinationTokenPriceUsd } = options
|
|
382
394
|
|
|
395
|
+
if (!sourceTokenDecimals) {
|
|
396
|
+
throw new Error("Source token decimals not provided")
|
|
397
|
+
}
|
|
398
|
+
if (!destinationTokenDecimals) {
|
|
399
|
+
throw new Error("Destination token decimals not provided")
|
|
400
|
+
}
|
|
401
|
+
|
|
383
402
|
let hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
384
403
|
const mainSignerAddress = account.address
|
|
385
404
|
const transactionStates: TransactionState[] = []
|
|
386
405
|
|
|
406
|
+
// Validate recipient is not zero address
|
|
407
|
+
if (recipient === zeroAddress) {
|
|
408
|
+
throw new Error("Recipient address cannot be zero address")
|
|
409
|
+
}
|
|
410
|
+
|
|
387
411
|
// Validate CCTP destination token requirement
|
|
388
412
|
validateCctpDestinationToken(
|
|
389
413
|
destinationTokenAddress,
|
|
@@ -394,22 +418,22 @@ export async function prepareSend(
|
|
|
394
418
|
let effectiveDestinationAddress = recipient
|
|
395
419
|
let effectiveDestinationCalldata = destinationCalldata
|
|
396
420
|
|
|
397
|
-
if (
|
|
421
|
+
if (
|
|
422
|
+
!hasCustomCalldata &&
|
|
423
|
+
tradeType === TradeType.EXACT_INPUT &&
|
|
424
|
+
destinationTokenAddress !== zeroAddress
|
|
425
|
+
) {
|
|
398
426
|
// we need to set custom calldata for the cctp transfer in order to have destination intent adddress execution needed for metatxn tracking
|
|
399
427
|
effectiveDestinationCalldata = getERC20TransferData({
|
|
400
428
|
recipient,
|
|
401
|
-
amount: BigInt(
|
|
429
|
+
amount: BigInt(TRAILS_ROUTER_PLACEHOLDER_AMOUNT),
|
|
402
430
|
})
|
|
403
431
|
effectiveDestinationAddress = destinationTokenAddress
|
|
404
432
|
hasCustomCalldata = true
|
|
405
433
|
}
|
|
406
434
|
|
|
407
|
-
if (
|
|
408
|
-
|
|
409
|
-
destinationTokenAddress !== zeroAddress &&
|
|
410
|
-
tradeType === TradeType.EXACT_INPUT
|
|
411
|
-
) {
|
|
412
|
-
const wrapResult = wrapCalldataWithProxyCallerIfNeeded({
|
|
435
|
+
if (hasCustomCalldata && tradeType === TradeType.EXACT_INPUT) {
|
|
436
|
+
const wrapResult = wrapCalldataWithTrailsRouterIfNeeded({
|
|
413
437
|
token: destinationTokenAddress,
|
|
414
438
|
target: effectiveDestinationAddress,
|
|
415
439
|
calldata: effectiveDestinationCalldata as `0x${string}`,
|
|
@@ -421,7 +445,7 @@ export async function prepareSend(
|
|
|
421
445
|
})
|
|
422
446
|
|
|
423
447
|
if (wrapResult) {
|
|
424
|
-
effectiveDestinationAddress = wrapResult.
|
|
448
|
+
effectiveDestinationAddress = wrapResult.trailsRouterAddress
|
|
425
449
|
effectiveDestinationCalldata = wrapResult.encodedCalldata
|
|
426
450
|
}
|
|
427
451
|
}
|
|
@@ -562,13 +586,7 @@ export async function prepareSend(
|
|
|
562
586
|
})
|
|
563
587
|
|
|
564
588
|
// additional execute tx on same chain if needed
|
|
565
|
-
if (
|
|
566
|
-
isToSameChain &&
|
|
567
|
-
hasCustomCalldata &&
|
|
568
|
-
!isToSameToken &&
|
|
569
|
-
// TODO: remove this once it's stable https://github.com/0xsequence/stack/pull/1273
|
|
570
|
-
getSameChainSwapDifferentIntentAddresses()
|
|
571
|
-
) {
|
|
589
|
+
if (isToSameChain && hasCustomCalldata && !isToSameToken) {
|
|
572
590
|
transactionStates.push({
|
|
573
591
|
transactionHash: "",
|
|
574
592
|
explorerUrl: "",
|
|
@@ -654,6 +672,7 @@ export async function prepareSend(
|
|
|
654
672
|
recipient: effectiveDestinationAddress,
|
|
655
673
|
destinationCalldata: effectiveDestinationCalldata,
|
|
656
674
|
apiClient,
|
|
675
|
+
trailsClient,
|
|
657
676
|
sourceTokenPriceUsd,
|
|
658
677
|
destinationTokenPriceUsd,
|
|
659
678
|
sourceTokenDecimals,
|
|
@@ -677,6 +696,7 @@ export async function prepareSend(
|
|
|
677
696
|
fundMethod,
|
|
678
697
|
mode,
|
|
679
698
|
checkoutOnHandlers,
|
|
699
|
+
selectedFeeToken,
|
|
680
700
|
})
|
|
681
701
|
}
|
|
682
702
|
|
|
@@ -692,7 +712,7 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
692
712
|
destinationTokenSymbol,
|
|
693
713
|
recipient,
|
|
694
714
|
destinationCalldata,
|
|
695
|
-
|
|
715
|
+
trailsClient,
|
|
696
716
|
sourceTokenPriceUsd,
|
|
697
717
|
destinationTokenPriceUsd,
|
|
698
718
|
sourceTokenDecimals,
|
|
@@ -716,6 +736,7 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
716
736
|
fundMethod,
|
|
717
737
|
mode,
|
|
718
738
|
checkoutOnHandlers,
|
|
739
|
+
selectedFeeToken,
|
|
719
740
|
}: {
|
|
720
741
|
mainSignerAddress: string
|
|
721
742
|
originChainId: number
|
|
@@ -729,6 +750,7 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
729
750
|
recipient: string
|
|
730
751
|
destinationCalldata?: string
|
|
731
752
|
apiClient: SequenceAPIClient
|
|
753
|
+
trailsClient: TrailsAPIClient
|
|
732
754
|
sourceTokenPriceUsd?: number | null
|
|
733
755
|
destinationTokenPriceUsd?: number | null
|
|
734
756
|
sourceTokenDecimals: number
|
|
@@ -752,6 +774,7 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
752
774
|
fundMethod?: string
|
|
753
775
|
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
754
776
|
checkoutOnHandlers?: CheckoutOnHandlers
|
|
777
|
+
selectedFeeToken?: any
|
|
755
778
|
}): Promise<PrepareSendReturn> {
|
|
756
779
|
const testnet = isTestnetDebugMode()
|
|
757
780
|
const useCctp = getUseCctp(
|
|
@@ -799,10 +822,22 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
799
822
|
quote,
|
|
800
823
|
send: async ({
|
|
801
824
|
onOriginSend,
|
|
825
|
+
selectedFeeToken: runtimeSelectedFeeToken,
|
|
802
826
|
}: {
|
|
803
827
|
onOriginSend?: () => void
|
|
804
|
-
|
|
828
|
+
selectedFeeToken?: any
|
|
805
829
|
}): Promise<SendReturn> => {
|
|
830
|
+
// Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
|
|
831
|
+
const effectiveSelectedFeeToken =
|
|
832
|
+
runtimeSelectedFeeToken ?? selectedFeeToken
|
|
833
|
+
logger.console.log(
|
|
834
|
+
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called (LEGACY PATH):",
|
|
835
|
+
{
|
|
836
|
+
runtimeSelectedFeeToken,
|
|
837
|
+
prepareTimeSelectedFeeToken: selectedFeeToken,
|
|
838
|
+
effectiveSelectedFeeToken,
|
|
839
|
+
},
|
|
840
|
+
)
|
|
806
841
|
const originChain = testnet ? getTestnetChainInfo(chain)! : chain
|
|
807
842
|
const destinationChain = testnet
|
|
808
843
|
? getTestnetChainInfo(destinationChainId)!
|
|
@@ -993,8 +1028,10 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
993
1028
|
logger.console.log("[trails-sdk] Creating intent with args:", intentArgs)
|
|
994
1029
|
|
|
995
1030
|
const intent = await getIntentCallsPayloadsFromIntents(
|
|
996
|
-
|
|
997
|
-
|
|
1031
|
+
trailsClient,
|
|
1032
|
+
{
|
|
1033
|
+
params: intentArgs,
|
|
1034
|
+
},
|
|
998
1035
|
{
|
|
999
1036
|
originTokenSymbol,
|
|
1000
1037
|
destinationTokenSymbol,
|
|
@@ -1003,18 +1040,21 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1003
1040
|
)
|
|
1004
1041
|
logger.console.log("[trails-sdk] Got intent:", intent)
|
|
1005
1042
|
|
|
1006
|
-
if (
|
|
1043
|
+
if (
|
|
1044
|
+
!intent.payloads.preconditions?.length ||
|
|
1045
|
+
!intent.payloads.calls?.length
|
|
1046
|
+
) {
|
|
1007
1047
|
throw new Error("Invalid intent")
|
|
1008
1048
|
}
|
|
1009
1049
|
|
|
1010
|
-
const originIntentAddress = intent.originIntentAddress
|
|
1050
|
+
const originIntentAddress = intent.payloads.originIntentAddress
|
|
1011
1051
|
logger.console.log(
|
|
1012
1052
|
"[trails-sdk] origin intent address:",
|
|
1013
1053
|
originIntentAddress.toString(),
|
|
1014
1054
|
)
|
|
1015
1055
|
|
|
1016
1056
|
const firstPrecondition = findFirstPreconditionForChainId(
|
|
1017
|
-
intent.preconditions,
|
|
1057
|
+
intent.payloads.preconditions,
|
|
1018
1058
|
originChainId,
|
|
1019
1059
|
)
|
|
1020
1060
|
|
|
@@ -1025,8 +1065,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1025
1065
|
const firstPreconditionMin = firstPrecondition?.data?.min?.toString()
|
|
1026
1066
|
const depositAmount = firstPreconditionMin
|
|
1027
1067
|
|
|
1028
|
-
const quoteToAmount = intent.quote.toAmount
|
|
1029
|
-
const quoteToAmountMin = intent.quote.toAmountMin
|
|
1068
|
+
const quoteToAmount = intent.payloads.quote.toAmount
|
|
1069
|
+
const quoteToAmountMin = intent.payloads.quote.toAmountMin
|
|
1030
1070
|
|
|
1031
1071
|
const originSendAmountFormatted = formatRawAmount(
|
|
1032
1072
|
depositAmount,
|
|
@@ -1070,7 +1110,6 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1070
1110
|
)
|
|
1071
1111
|
|
|
1072
1112
|
let noSufficientBalance = false
|
|
1073
|
-
let minimumNotMet = false
|
|
1074
1113
|
|
|
1075
1114
|
if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
|
|
1076
1115
|
const { hasEnoughBalance } = await checkAccountBalance({
|
|
@@ -1085,13 +1124,9 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1085
1124
|
}
|
|
1086
1125
|
}
|
|
1087
1126
|
|
|
1088
|
-
if (Number(depositAmountUsd) < Number(MINIMUM_USD_AMOUNT_FOR_SWAP)) {
|
|
1089
|
-
minimumNotMet = true
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
1127
|
const quote = await getNormalizedQuoteObject({
|
|
1093
1128
|
originDepositAddress: originIntentAddress,
|
|
1094
|
-
destinationDepositAddress: intent.destinationIntentAddress,
|
|
1129
|
+
destinationDepositAddress: intent.payloads.destinationIntentAddress,
|
|
1095
1130
|
destinationAddress: recipient,
|
|
1096
1131
|
destinationCalldata,
|
|
1097
1132
|
originAmount: depositAmount,
|
|
@@ -1114,9 +1149,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1114
1149
|
priceImpactUsd: getPriceImpactUsdFromIntent(intent),
|
|
1115
1150
|
transactionStates,
|
|
1116
1151
|
originNativeTokenPriceUsd,
|
|
1117
|
-
quoteProvider: intent?.quote?.quoteProvider,
|
|
1152
|
+
quoteProvider: intent.payloads?.quote?.quoteProvider,
|
|
1118
1153
|
noSufficientBalance,
|
|
1119
|
-
minimumNotMet,
|
|
1120
1154
|
})
|
|
1121
1155
|
|
|
1122
1156
|
// Call onCheckoutQuote callback if provided
|
|
@@ -1124,245 +1158,294 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1124
1158
|
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
1125
1159
|
}
|
|
1126
1160
|
|
|
1127
|
-
//
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1161
|
+
// Get intent entrypoint fee options if supported, gasless is enabled, and fundMethod is wallet
|
|
1162
|
+
let intentEntrypointFeeOptions: any = null
|
|
1163
|
+
if (gasless && fundMethod === "wallet") {
|
|
1164
|
+
try {
|
|
1165
|
+
logger.console.log(
|
|
1166
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled and fundMethod is wallet, checking intent entrypoint support for chain:",
|
|
1167
|
+
originChainId,
|
|
1168
|
+
)
|
|
1169
|
+
if (isIntentEntrypointSupported(originChainId)) {
|
|
1170
|
+
logger.console.log(
|
|
1171
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Chain supported, fetching fee options...",
|
|
1172
|
+
)
|
|
1173
|
+
intentEntrypointFeeOptions = await getIntentEntrypointFeeOptions({
|
|
1174
|
+
trailsClient,
|
|
1175
|
+
userAddress: mainSignerAddress as `0x${string}`,
|
|
1176
|
+
tokenAddress: originTokenAddress as `0x${string}`,
|
|
1177
|
+
amount: depositAmount,
|
|
1178
|
+
intentAddress: originIntentAddress as `0x${string}`,
|
|
1179
|
+
chainId: originChainId,
|
|
1180
|
+
})
|
|
1181
|
+
logger.console.log(
|
|
1182
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Intent entrypoint fee options:",
|
|
1183
|
+
intentEntrypointFeeOptions,
|
|
1184
|
+
)
|
|
1185
|
+
} else {
|
|
1186
|
+
logger.console.log(
|
|
1187
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Chain not supported for intent entrypoint:",
|
|
1188
|
+
originChainId,
|
|
1189
|
+
)
|
|
1190
|
+
}
|
|
1191
|
+
} catch (error) {
|
|
1192
|
+
logger.console.error(
|
|
1193
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Error getting intent entrypoint fee options:",
|
|
1194
|
+
error,
|
|
1195
|
+
)
|
|
1196
|
+
}
|
|
1197
|
+
} else {
|
|
1198
|
+
logger.console.log(
|
|
1199
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled or fundMethod is not wallet, skipping intent entrypoint fee options fetch",
|
|
1200
|
+
)
|
|
1201
|
+
}
|
|
1154
1202
|
|
|
1155
1203
|
return {
|
|
1156
1204
|
quote,
|
|
1205
|
+
feeOptions: intentEntrypointFeeOptions,
|
|
1157
1206
|
send: async ({
|
|
1158
1207
|
onOriginSend,
|
|
1208
|
+
selectedFeeToken: runtimeSelectedFeeToken,
|
|
1159
1209
|
}: {
|
|
1160
1210
|
onOriginSend?: () => void
|
|
1161
|
-
|
|
1211
|
+
selectedFeeToken?: any
|
|
1162
1212
|
}): Promise<SendReturn> => {
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1213
|
+
try {
|
|
1214
|
+
// Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
|
|
1215
|
+
const effectiveSelectedFeeToken =
|
|
1216
|
+
runtimeSelectedFeeToken ?? selectedFeeToken
|
|
1217
|
+
logger.console.log(
|
|
1218
|
+
"[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called with:",
|
|
1219
|
+
{
|
|
1220
|
+
runtimeSelectedFeeToken,
|
|
1221
|
+
prepareTimeSelectedFeeToken: selectedFeeToken,
|
|
1222
|
+
effectiveSelectedFeeToken,
|
|
1223
|
+
},
|
|
1224
|
+
)
|
|
1225
|
+
await commitIntentConfig(
|
|
1226
|
+
trailsClient,
|
|
1227
|
+
mainSignerAddress,
|
|
1228
|
+
intent.payloads.calls,
|
|
1229
|
+
intent.payloads.preconditions,
|
|
1230
|
+
{
|
|
1231
|
+
originTokenSymbol,
|
|
1232
|
+
destinationTokenSymbol,
|
|
1233
|
+
},
|
|
1234
|
+
intentArgs as IntentRequestParams,
|
|
1235
|
+
)
|
|
1174
1236
|
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1237
|
+
if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
|
|
1238
|
+
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
1239
|
+
account,
|
|
1240
|
+
tokenAddress: originTokenAddress,
|
|
1241
|
+
depositAmount,
|
|
1242
|
+
publicClient,
|
|
1243
|
+
})
|
|
1182
1244
|
|
|
1183
|
-
|
|
1184
|
-
|
|
1245
|
+
if (!hasEnoughBalance) {
|
|
1246
|
+
throw balanceError
|
|
1247
|
+
}
|
|
1185
1248
|
}
|
|
1186
|
-
}
|
|
1187
1249
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1250
|
+
logger.console.log("[trails-sdk] sending origin transaction")
|
|
1251
|
+
const usingLIfi = false
|
|
1252
|
+
let needsNativeFee = false
|
|
1191
1253
|
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1254
|
+
if (usingLIfi) {
|
|
1255
|
+
needsNativeFee = getNeedsLifiNativeFee({
|
|
1256
|
+
originTokenAddress,
|
|
1257
|
+
destinationTokenAmount: swapAmount,
|
|
1258
|
+
destinationTokenDecimals,
|
|
1259
|
+
sourceTokenDecimals,
|
|
1260
|
+
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
1261
|
+
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
1262
|
+
depositAmount,
|
|
1263
|
+
})
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
|
|
1267
|
+
logger.console.log(
|
|
1268
|
+
"[trails-sdk] sourceTokenPriceUsd",
|
|
1269
|
+
sourceTokenPriceUsd,
|
|
1270
|
+
)
|
|
1271
|
+
logger.console.log(
|
|
1272
|
+
"[trails-sdk] destinationTokenPriceUsd",
|
|
1273
|
+
destinationTokenPriceUsd,
|
|
1274
|
+
)
|
|
1275
|
+
logger.console.log(
|
|
1276
|
+
"[trails-sdk] sourceTokenDecimals",
|
|
1197
1277
|
sourceTokenDecimals,
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1278
|
+
)
|
|
1279
|
+
logger.console.log(
|
|
1280
|
+
"[trails-sdk] destinationTokenDecimals",
|
|
1281
|
+
destinationTokenDecimals,
|
|
1282
|
+
)
|
|
1203
1283
|
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
sourceTokenPriceUsd,
|
|
1208
|
-
)
|
|
1209
|
-
logger.console.log(
|
|
1210
|
-
"[trails-sdk] destinationTokenPriceUsd",
|
|
1211
|
-
destinationTokenPriceUsd,
|
|
1212
|
-
)
|
|
1213
|
-
logger.console.log(
|
|
1214
|
-
"[trails-sdk] sourceTokenDecimals",
|
|
1215
|
-
sourceTokenDecimals,
|
|
1216
|
-
)
|
|
1217
|
-
logger.console.log(
|
|
1218
|
-
"[trails-sdk] destinationTokenDecimals",
|
|
1219
|
-
destinationTokenDecimals,
|
|
1220
|
-
)
|
|
1284
|
+
let originUserTxReceipt: TransactionReceipt | null = null
|
|
1285
|
+
let originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
1286
|
+
let destinationMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
1221
1287
|
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1288
|
+
const testnet = isTestnetDebugMode()
|
|
1289
|
+
const effectiveOriginChain = testnet
|
|
1290
|
+
? getTestnetChainInfo(chain)!
|
|
1291
|
+
: chain
|
|
1292
|
+
const effectiveOriginTokenAddress = testnet
|
|
1293
|
+
? getTestnetOriginTokenAddress(effectiveOriginChain.id)
|
|
1294
|
+
: originTokenAddress
|
|
1225
1295
|
|
|
1226
|
-
|
|
1227
|
-
const effectiveOriginChain = testnet ? getTestnetChainInfo(chain)! : chain
|
|
1228
|
-
const effectiveOriginTokenAddress = testnet
|
|
1229
|
-
? getTestnetOriginTokenAddress(effectiveOriginChain.id)
|
|
1230
|
-
: originTokenAddress
|
|
1296
|
+
logger.console.log("[trails-sdk] testnet", testnet)
|
|
1231
1297
|
|
|
1232
|
-
|
|
1298
|
+
const destinationPublicClient = createPublicClient({
|
|
1299
|
+
chain: getChainInfo(destinationChainId)!,
|
|
1300
|
+
transport: http(),
|
|
1301
|
+
})
|
|
1233
1302
|
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1303
|
+
const depositPromise = async () => {
|
|
1304
|
+
logger.console.log(
|
|
1305
|
+
"[trails-sdk] depositPromise called - starting deposit transaction",
|
|
1306
|
+
)
|
|
1307
|
+
logger.console.log("[trails-sdk] fundMethod value:", fundMethod)
|
|
1238
1308
|
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1309
|
+
// Skip wallet deposit if fund method is qr-code
|
|
1310
|
+
if (fundMethod === "qr-code" || fundMethod === "exchange") {
|
|
1311
|
+
logger.console.log(
|
|
1312
|
+
"[trails-sdk] Skipping wallet deposit for QR code mode - fundMethod is:",
|
|
1313
|
+
fundMethod,
|
|
1314
|
+
)
|
|
1315
|
+
return
|
|
1316
|
+
}
|
|
1244
1317
|
|
|
1245
|
-
// Skip wallet deposit if fund method is qr-code
|
|
1246
|
-
if (fundMethod === "qr-code" || fundMethod === "exchange") {
|
|
1247
1318
|
logger.console.log(
|
|
1248
|
-
"[trails-sdk]
|
|
1249
|
-
|
|
1319
|
+
"[trails-sdk] Calling attemptUserDepositTx with params:",
|
|
1320
|
+
{
|
|
1321
|
+
originTokenAddress: effectiveOriginTokenAddress,
|
|
1322
|
+
gasless,
|
|
1323
|
+
paymasterUrl,
|
|
1324
|
+
chain: effectiveOriginChain.id,
|
|
1325
|
+
account: account.address,
|
|
1326
|
+
firstPreconditionMin,
|
|
1327
|
+
originIntentAddress,
|
|
1328
|
+
fee,
|
|
1329
|
+
dryMode,
|
|
1330
|
+
feeOptions: intentEntrypointFeeOptions,
|
|
1331
|
+
selectedFeeToken: effectiveSelectedFeeToken,
|
|
1332
|
+
selectedFeeTokenType: typeof effectiveSelectedFeeToken,
|
|
1333
|
+
selectedFeeTokenValue: JSON.stringify(effectiveSelectedFeeToken),
|
|
1334
|
+
},
|
|
1250
1335
|
)
|
|
1251
|
-
return
|
|
1252
|
-
}
|
|
1253
1336
|
|
|
1254
|
-
|
|
1255
|
-
"[trails-sdk] Calling attemptUserDepositTx with params:",
|
|
1256
|
-
{
|
|
1337
|
+
originUserTxReceipt = await attemptUserDepositTx({
|
|
1257
1338
|
originTokenAddress: effectiveOriginTokenAddress,
|
|
1258
1339
|
gasless,
|
|
1259
1340
|
paymasterUrl,
|
|
1260
|
-
chain: effectiveOriginChain
|
|
1261
|
-
account
|
|
1341
|
+
chain: effectiveOriginChain,
|
|
1342
|
+
account,
|
|
1343
|
+
originRelayer,
|
|
1262
1344
|
firstPreconditionMin,
|
|
1263
1345
|
originIntentAddress,
|
|
1346
|
+
onOriginSend,
|
|
1347
|
+
publicClient,
|
|
1348
|
+
walletClient,
|
|
1349
|
+
destinationTokenDecimals,
|
|
1350
|
+
sourceTokenDecimals,
|
|
1264
1351
|
fee,
|
|
1265
1352
|
dryMode,
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
publicClient,
|
|
1280
|
-
walletClient,
|
|
1281
|
-
destinationTokenDecimals,
|
|
1282
|
-
sourceTokenDecimals,
|
|
1283
|
-
fee,
|
|
1284
|
-
dryMode,
|
|
1285
|
-
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
1286
|
-
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
1287
|
-
swapAmount,
|
|
1288
|
-
onTransactionStateChange,
|
|
1289
|
-
transactionStates,
|
|
1290
|
-
fundMethod,
|
|
1291
|
-
originTokenSymbol,
|
|
1292
|
-
destinationTokenSymbol,
|
|
1293
|
-
depositAmountUsd,
|
|
1294
|
-
})
|
|
1353
|
+
sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
|
|
1354
|
+
destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
|
|
1355
|
+
swapAmount,
|
|
1356
|
+
onTransactionStateChange,
|
|
1357
|
+
transactionStates,
|
|
1358
|
+
fundMethod,
|
|
1359
|
+
originTokenSymbol,
|
|
1360
|
+
destinationTokenSymbol,
|
|
1361
|
+
depositAmountUsd,
|
|
1362
|
+
feeOptions: intentEntrypointFeeOptions,
|
|
1363
|
+
trailsClient,
|
|
1364
|
+
selectedFeeToken: effectiveSelectedFeeToken,
|
|
1365
|
+
})
|
|
1295
1366
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1367
|
+
if (!originUserTxReceipt) {
|
|
1368
|
+
throw new Error("Failed to send origin transaction")
|
|
1369
|
+
}
|
|
1299
1370
|
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
onTransactionStateChange(transactionStates)
|
|
1306
|
-
}
|
|
1371
|
+
transactionStates[0] = getTransactionStateFromReceipt(
|
|
1372
|
+
originUserTxReceipt,
|
|
1373
|
+
originChainId,
|
|
1374
|
+
transactionStates[0]?.label,
|
|
1375
|
+
)
|
|
1307
1376
|
|
|
1308
|
-
const checkForDepositTx = async () => {
|
|
1309
|
-
while (true) {
|
|
1310
1377
|
try {
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1378
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents =
|
|
1379
|
+
decodeTrailsTokenSweeperEvents(originUserTxReceipt)
|
|
1380
|
+
transactionStates[0].decodedGuestModuleEvents =
|
|
1381
|
+
decodeGuestModuleEvents(originUserTxReceipt)
|
|
1382
|
+
transactionStates[0].refunded =
|
|
1383
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1384
|
+
(event) =>
|
|
1385
|
+
event.type === "Refund" || event.type === "RefundAndSweep",
|
|
1386
|
+
) !== -1
|
|
1315
1387
|
logger.console.log(
|
|
1316
|
-
"[trails-sdk]
|
|
1317
|
-
|
|
1388
|
+
"[trails-sdk] [GASLESS-FLOW] Gasless deposit events decoded",
|
|
1389
|
+
{
|
|
1390
|
+
chainId: originChainId,
|
|
1391
|
+
callFailed: (
|
|
1392
|
+
transactionStates[0].decodedGuestModuleEvents || []
|
|
1393
|
+
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1394
|
+
sweeperEvents: (
|
|
1395
|
+
transactionStates[0].decodedTrailsTokenSweeperEvents || []
|
|
1396
|
+
).length,
|
|
1397
|
+
refunded: transactionStates[0].refunded,
|
|
1398
|
+
},
|
|
1318
1399
|
)
|
|
1319
|
-
if (response.transactions.length > 0) {
|
|
1320
|
-
const tx = response.transactions[0]
|
|
1321
|
-
if (!tx?.txnHash) {
|
|
1322
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1323
|
-
continue
|
|
1324
|
-
}
|
|
1325
|
-
// const isReceive = tx.transfers.some(
|
|
1326
|
-
// (transfer) => transfer.transferType === "RECEIVE",
|
|
1327
|
-
// )
|
|
1328
|
-
// if (!isReceive) {
|
|
1329
|
-
// await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1330
|
-
// continue
|
|
1331
|
-
// }
|
|
1332
|
-
const originDepositTxReceipt =
|
|
1333
|
-
await publicClient.getTransactionReceipt({
|
|
1334
|
-
hash: tx.txnHash as `0x${string}`,
|
|
1335
|
-
})
|
|
1336
1400
|
|
|
1337
|
-
|
|
1401
|
+
// Check for transaction failure or refund
|
|
1402
|
+
const hasCallFailed = (
|
|
1403
|
+
transactionStates[0].decodedGuestModuleEvents || []
|
|
1404
|
+
).some((e: any) => e?.type === "CallFailed")
|
|
1338
1405
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1406
|
+
if (transactionStates[0].refunded || hasCallFailed) {
|
|
1407
|
+
const errorMessage = transactionStates[0].refunded
|
|
1408
|
+
? "Transaction was refunded"
|
|
1409
|
+
: "Transaction call failed"
|
|
1410
|
+
|
|
1411
|
+
logger.console.error(
|
|
1412
|
+
"[trails-sdk] [GASLESS-FLOW] Deposit transaction failed",
|
|
1413
|
+
{
|
|
1414
|
+
refunded: transactionStates[0].refunded,
|
|
1415
|
+
callFailed: hasCallFailed,
|
|
1416
|
+
},
|
|
1343
1417
|
)
|
|
1344
|
-
onTransactionStateChange(transactionStates)
|
|
1345
1418
|
|
|
1346
|
-
if
|
|
1347
|
-
|
|
1419
|
+
// Call onCheckoutError callback if provided
|
|
1420
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1421
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1348
1422
|
}
|
|
1349
|
-
break
|
|
1350
1423
|
}
|
|
1351
1424
|
} catch (error) {
|
|
1352
|
-
logger.console.error(
|
|
1425
|
+
logger.console.error(
|
|
1426
|
+
"[trails-sdk] Error decoding gasless deposit events",
|
|
1427
|
+
error,
|
|
1428
|
+
)
|
|
1353
1429
|
}
|
|
1354
|
-
|
|
1430
|
+
|
|
1431
|
+
onTransactionStateChange(transactionStates)
|
|
1432
|
+
|
|
1433
|
+
setTimeout(() => {
|
|
1434
|
+
const destinationChain = getChainInfo(destinationChainId)
|
|
1435
|
+
updatePersistentToast(
|
|
1436
|
+
"In Progress",
|
|
1437
|
+
`Your transaction to ${destinationChain?.name || "chain"} is in progress`,
|
|
1438
|
+
"info",
|
|
1439
|
+
)
|
|
1440
|
+
}, 1000)
|
|
1355
1441
|
}
|
|
1356
|
-
}
|
|
1357
1442
|
|
|
1358
|
-
|
|
1359
|
-
async () => {
|
|
1443
|
+
const checkForDepositTx = async () => {
|
|
1360
1444
|
while (true) {
|
|
1361
1445
|
try {
|
|
1362
1446
|
const response = await getAccountTransactionHistory({
|
|
1363
|
-
chainId:
|
|
1364
|
-
accountAddress:
|
|
1365
|
-
intent.destinationIntentAddress as `0x${string}`,
|
|
1447
|
+
chainId: originChainId,
|
|
1448
|
+
accountAddress: originIntentAddress,
|
|
1366
1449
|
})
|
|
1367
1450
|
logger.console.log(
|
|
1368
1451
|
"[trails-sdk] getAccountTransactionHistory response",
|
|
@@ -1381,19 +1464,24 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1381
1464
|
// await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1382
1465
|
// continue
|
|
1383
1466
|
// }
|
|
1384
|
-
const
|
|
1385
|
-
await
|
|
1467
|
+
const originDepositTxReceipt =
|
|
1468
|
+
await publicClient.getTransactionReceipt({
|
|
1386
1469
|
hash: tx.txnHash as `0x${string}`,
|
|
1387
1470
|
})
|
|
1388
1471
|
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1472
|
+
originUserTxReceipt = originDepositTxReceipt
|
|
1473
|
+
|
|
1474
|
+
transactionStates[0] = getTransactionStateFromReceipt(
|
|
1475
|
+
originDepositTxReceipt,
|
|
1476
|
+
originChainId,
|
|
1477
|
+
transactionStates[0]?.label,
|
|
1393
1478
|
)
|
|
1394
1479
|
onTransactionStateChange(transactionStates)
|
|
1395
1480
|
|
|
1396
|
-
|
|
1481
|
+
if (onOriginSend) {
|
|
1482
|
+
onOriginSend()
|
|
1483
|
+
}
|
|
1484
|
+
break
|
|
1397
1485
|
}
|
|
1398
1486
|
} catch (error) {
|
|
1399
1487
|
logger.console.error("Error checking for deposit tx", error)
|
|
@@ -1402,199 +1490,77 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1402
1490
|
}
|
|
1403
1491
|
}
|
|
1404
1492
|
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
// First phase: Send meta transactions and queue CCTP
|
|
1415
|
-
const originSendMetaTxnPromise = async () => {
|
|
1416
|
-
logger.console.log("[trails-sdk] Starting originSendMetaTxnPromise", {
|
|
1417
|
-
hasMetaTxn: !!intent.metaTxns[0],
|
|
1418
|
-
hasPrecondition: !!intent.preconditions[0],
|
|
1419
|
-
metaTxnId: intent.metaTxns[0]?.id,
|
|
1420
|
-
chainId: intent.metaTxns[0]?.chainId,
|
|
1421
|
-
})
|
|
1422
|
-
|
|
1423
|
-
if (intent.metaTxns[0] && intent.preconditions[0]) {
|
|
1424
|
-
// Extract fee quote from intent response using metatxnid as key
|
|
1425
|
-
const metaTxnId = intent.metaTxns[0].id
|
|
1426
|
-
const feeQuote = intent.feeQuotes?.[metaTxnId]
|
|
1427
|
-
logger.console.log(
|
|
1428
|
-
"[trails-sdk] Extracted fee quote for origin meta txn",
|
|
1429
|
-
{
|
|
1430
|
-
metaTxnId,
|
|
1431
|
-
feeQuote,
|
|
1432
|
-
hasFeeQuote: !!feeQuote,
|
|
1433
|
-
},
|
|
1434
|
-
)
|
|
1435
|
-
|
|
1436
|
-
logger.console.log(
|
|
1437
|
-
"[trails-sdk] Calling sendMetaTxAndWaitForReceipt for origin",
|
|
1438
|
-
{
|
|
1439
|
-
metaTxnId,
|
|
1440
|
-
chainId: intent.metaTxns[0].chainId,
|
|
1441
|
-
walletAddress: intent.metaTxns[0].walletAddress,
|
|
1442
|
-
contract: intent.metaTxns[0].contract,
|
|
1443
|
-
},
|
|
1444
|
-
)
|
|
1445
|
-
|
|
1446
|
-
const { waitForReceipt } = await sendMetaTxAndWaitForReceipt({
|
|
1447
|
-
metaTx: intent.metaTxns[0] as MetaTxn,
|
|
1448
|
-
relayer: originRelayer,
|
|
1449
|
-
precondition: intent.preconditions[0] as IntentPrecondition,
|
|
1450
|
-
feeQuote: feeQuote,
|
|
1451
|
-
})
|
|
1452
|
-
|
|
1453
|
-
logger.console.log(
|
|
1454
|
-
"[trails-sdk] Origin meta transaction sent successfully",
|
|
1455
|
-
{
|
|
1456
|
-
metaTxnId,
|
|
1457
|
-
},
|
|
1458
|
-
)
|
|
1459
|
-
|
|
1460
|
-
// Store the waitForReceipt function for later use
|
|
1461
|
-
originMetaTxnReceiptPromise = waitForReceipt
|
|
1462
|
-
} else {
|
|
1463
|
-
logger.console.warn(
|
|
1464
|
-
"[trails-sdk] Skipping origin sendMetaTxn - missing metaTxn or precondition",
|
|
1465
|
-
{
|
|
1466
|
-
hasMetaTxn: !!intent.metaTxns[0],
|
|
1467
|
-
hasPrecondition: !!intent.preconditions[0],
|
|
1468
|
-
},
|
|
1469
|
-
)
|
|
1470
|
-
}
|
|
1471
|
-
}
|
|
1472
|
-
|
|
1473
|
-
const destinationSendMetaTxnPromise = async () => {
|
|
1474
|
-
logger.console.log(
|
|
1475
|
-
"[trails-sdk] Starting destinationSendMetaTxnPromise",
|
|
1476
|
-
{
|
|
1477
|
-
quoteProvider: intent.quote.quoteProvider,
|
|
1478
|
-
hasQuoteProviderRequestId: !!intent.quote.quoteProviderRequestId,
|
|
1479
|
-
hasPrecondition1: !!intent.preconditions[1],
|
|
1480
|
-
hasMetaTxn1: !!intent.metaTxns[1],
|
|
1481
|
-
},
|
|
1482
|
-
)
|
|
1483
|
-
|
|
1484
|
-
if (
|
|
1485
|
-
intent.quote.quoteProvider === "relay" &&
|
|
1486
|
-
intent.quote.quoteProviderRequestId &&
|
|
1487
|
-
!intent.preconditions[1] &&
|
|
1488
|
-
!intent.metaTxns[1]
|
|
1489
|
-
) {
|
|
1490
|
-
logger.console.log(
|
|
1491
|
-
"[trails-sdk] Setting up relay destination promise",
|
|
1492
|
-
{
|
|
1493
|
-
quoteProviderRequestId: intent.quote.quoteProviderRequestId,
|
|
1494
|
-
},
|
|
1495
|
-
)
|
|
1496
|
-
// For relay, we'll wait for the receipt in the wait phase
|
|
1497
|
-
// Just store the requestId for later use
|
|
1498
|
-
destinationMetaTxnReceiptPromise = async (
|
|
1499
|
-
abortSignal?: AbortSignal,
|
|
1500
|
-
) => {
|
|
1501
|
-
logger.console.log(
|
|
1502
|
-
"[trails-sdk] waitForRelayDestinationTx starting",
|
|
1503
|
-
{
|
|
1504
|
-
quoteProviderRequestId: intent.quote.quoteProviderRequestId,
|
|
1505
|
-
aborted: abortSignal?.aborted,
|
|
1506
|
-
},
|
|
1507
|
-
)
|
|
1508
|
-
try {
|
|
1509
|
-
// Check if we should abort before starting
|
|
1510
|
-
if (abortSignal?.aborted) {
|
|
1511
|
-
logger.console.log(
|
|
1512
|
-
"[trails-sdk] Aborting relay destination tx due to abort signal",
|
|
1513
|
-
)
|
|
1514
|
-
return null
|
|
1515
|
-
}
|
|
1516
|
-
|
|
1517
|
-
const txHash = await waitForRelayDestinationTx(
|
|
1518
|
-
intent.quote.quoteProviderRequestId,
|
|
1519
|
-
)
|
|
1520
|
-
logger.console.log(
|
|
1521
|
-
"[trails-sdk] waitForRelayDestinationTx completed",
|
|
1522
|
-
{
|
|
1523
|
-
txHash,
|
|
1524
|
-
quoteProviderRequestId: intent.quote.quoteProviderRequestId,
|
|
1525
|
-
},
|
|
1526
|
-
)
|
|
1527
|
-
if (txHash) {
|
|
1493
|
+
const _checkForDestinationDepositTx: () => Promise<TransactionReceipt | null> =
|
|
1494
|
+
async () => {
|
|
1495
|
+
while (true) {
|
|
1496
|
+
try {
|
|
1497
|
+
const response = await getAccountTransactionHistory({
|
|
1498
|
+
chainId: destinationChainId,
|
|
1499
|
+
accountAddress: intent.payloads
|
|
1500
|
+
.destinationIntentAddress as `0x${string}`,
|
|
1501
|
+
})
|
|
1528
1502
|
logger.console.log(
|
|
1529
|
-
"[trails-sdk]
|
|
1530
|
-
|
|
1531
|
-
txHash,
|
|
1532
|
-
chainId: destinationChainId,
|
|
1533
|
-
},
|
|
1503
|
+
"[trails-sdk] getAccountTransactionHistory response",
|
|
1504
|
+
response,
|
|
1534
1505
|
)
|
|
1506
|
+
if (response.transactions.length > 0) {
|
|
1507
|
+
const tx = response.transactions[0]
|
|
1508
|
+
if (!tx?.txnHash) {
|
|
1509
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1510
|
+
continue
|
|
1511
|
+
}
|
|
1512
|
+
// const isReceive = tx.transfers.some(
|
|
1513
|
+
// (transfer) => transfer.transferType === "RECEIVE",
|
|
1514
|
+
// )
|
|
1515
|
+
// if (!isReceive) {
|
|
1516
|
+
// await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1517
|
+
// continue
|
|
1518
|
+
// }
|
|
1519
|
+
const destinationDepositTxReceipt =
|
|
1520
|
+
await destinationPublicClient.getTransactionReceipt({
|
|
1521
|
+
hash: tx.txnHash as `0x${string}`,
|
|
1522
|
+
})
|
|
1535
1523
|
|
|
1536
|
-
const destinationTxnReceipt =
|
|
1537
|
-
await destinationPublicClient.getTransactionReceipt({
|
|
1538
|
-
hash: txHash as `0x${string}`,
|
|
1539
|
-
})
|
|
1540
|
-
logger.console.log(
|
|
1541
|
-
"[trails-sdk] relay destinationTxnReceipt received",
|
|
1542
|
-
{
|
|
1543
|
-
txHash,
|
|
1544
|
-
blockNumber: destinationTxnReceipt?.blockNumber,
|
|
1545
|
-
status: destinationTxnReceipt?.status,
|
|
1546
|
-
gasUsed: destinationTxnReceipt?.gasUsed,
|
|
1547
|
-
},
|
|
1548
|
-
)
|
|
1549
|
-
if (transactionStates[2]) {
|
|
1550
1524
|
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1551
|
-
|
|
1525
|
+
destinationDepositTxReceipt,
|
|
1552
1526
|
destinationChainId,
|
|
1553
1527
|
transactionStates[2]?.label,
|
|
1554
1528
|
)
|
|
1529
|
+
onTransactionStateChange(transactionStates)
|
|
1530
|
+
|
|
1531
|
+
return destinationDepositTxReceipt
|
|
1555
1532
|
}
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
} else {
|
|
1559
|
-
logger.console.warn(
|
|
1560
|
-
"[trails-sdk] No txHash returned from waitForRelayDestinationTx",
|
|
1561
|
-
{
|
|
1562
|
-
quoteProviderRequestId: intent.quote.quoteProviderRequestId,
|
|
1563
|
-
},
|
|
1564
|
-
)
|
|
1565
|
-
}
|
|
1566
|
-
} catch (error: unknown) {
|
|
1567
|
-
logger.console.error(
|
|
1568
|
-
"[trails-sdk] Error waiting for relay destination tx",
|
|
1569
|
-
{
|
|
1570
|
-
error: error instanceof Error ? error.message : String(error),
|
|
1571
|
-
quoteProviderRequestId: intent.quote.quoteProviderRequestId,
|
|
1572
|
-
},
|
|
1573
|
-
)
|
|
1574
|
-
if (transactionStates?.[2]) {
|
|
1575
|
-
transactionStates[2].state = "failed"
|
|
1576
|
-
onTransactionStateChange(transactionStates)
|
|
1533
|
+
} catch (error) {
|
|
1534
|
+
logger.console.error("Error checking for deposit tx", error)
|
|
1577
1535
|
}
|
|
1578
|
-
|
|
1536
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1579
1537
|
}
|
|
1580
|
-
return null
|
|
1581
1538
|
}
|
|
1582
|
-
} else {
|
|
1583
|
-
logger.console.log(
|
|
1584
|
-
"[trails-sdk] Setting up destination meta transaction promise (non-relay)",
|
|
1585
|
-
{
|
|
1586
|
-
quoteProvider: intent.quote.quoteProvider,
|
|
1587
|
-
hasMetaTxn1: !!intent.metaTxns[1],
|
|
1588
|
-
hasPrecondition1: !!intent.preconditions[1],
|
|
1589
|
-
},
|
|
1590
|
-
)
|
|
1591
1539
|
|
|
1592
|
-
|
|
1540
|
+
// Variables to store the waitForReceipt functions
|
|
1541
|
+
let originMetaTxnReceiptPromise:
|
|
1542
|
+
| (() => Promise<MetaTxnReceipt | null>)
|
|
1543
|
+
| null = null
|
|
1544
|
+
let destinationMetaTxnReceiptPromise:
|
|
1545
|
+
| ((abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>)
|
|
1546
|
+
| ((abortSignal?: AbortSignal) => Promise<TransactionReceipt | null>)
|
|
1547
|
+
| null = null
|
|
1548
|
+
|
|
1549
|
+
// First phase: Send meta transactions and queue CCTP
|
|
1550
|
+
const originSendMetaTxnPromise = async () => {
|
|
1551
|
+
logger.console.log("[trails-sdk] Starting originSendMetaTxnPromise", {
|
|
1552
|
+
hasMetaTxn: !!intent.payloads.metaTxns[0],
|
|
1553
|
+
hasPrecondition: !!intent.payloads.preconditions[0],
|
|
1554
|
+
metaTxnId: intent.payloads.metaTxns[0]?.id,
|
|
1555
|
+
chainId: intent.payloads.metaTxns[0]?.chainId,
|
|
1556
|
+
})
|
|
1557
|
+
|
|
1558
|
+
if (intent.payloads.metaTxns[0] && intent.payloads.preconditions[0]) {
|
|
1593
1559
|
// Extract fee quote from intent response using metatxnid as key
|
|
1594
|
-
const metaTxnId = intent.metaTxns[
|
|
1595
|
-
const feeQuote = intent.feeQuotes?.[metaTxnId]
|
|
1560
|
+
const metaTxnId = intent.payloads.metaTxns[0].id
|
|
1561
|
+
const feeQuote = intent.payloads.feeQuotes?.[metaTxnId]
|
|
1596
1562
|
logger.console.log(
|
|
1597
|
-
"[trails-sdk] Extracted fee quote for
|
|
1563
|
+
"[trails-sdk] Extracted fee quote for origin meta txn",
|
|
1598
1564
|
{
|
|
1599
1565
|
metaTxnId,
|
|
1600
1566
|
feeQuote,
|
|
@@ -1603,318 +1569,597 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1603
1569
|
)
|
|
1604
1570
|
|
|
1605
1571
|
logger.console.log(
|
|
1606
|
-
"[trails-sdk] Calling sendMetaTxAndWaitForReceipt for
|
|
1572
|
+
"[trails-sdk] Calling sendMetaTxAndWaitForReceipt for origin",
|
|
1607
1573
|
{
|
|
1608
1574
|
metaTxnId,
|
|
1609
|
-
chainId: intent.metaTxns[
|
|
1610
|
-
walletAddress: intent.metaTxns[
|
|
1611
|
-
contract: intent.metaTxns[
|
|
1575
|
+
chainId: intent.payloads.metaTxns[0].chainId,
|
|
1576
|
+
walletAddress: intent.payloads.metaTxns[0].walletAddress,
|
|
1577
|
+
contract: intent.payloads.metaTxns[0].contract,
|
|
1612
1578
|
},
|
|
1613
1579
|
)
|
|
1614
1580
|
|
|
1615
1581
|
const { waitForReceipt } = await sendMetaTxAndWaitForReceipt({
|
|
1616
|
-
metaTx: intent.metaTxns[
|
|
1617
|
-
relayer:
|
|
1618
|
-
precondition: intent.
|
|
1582
|
+
metaTx: intent.payloads.metaTxns[0] as MetaTxn,
|
|
1583
|
+
relayer: originRelayer,
|
|
1584
|
+
precondition: intent.payloads
|
|
1585
|
+
.preconditions[0] as IntentPrecondition,
|
|
1619
1586
|
feeQuote: feeQuote,
|
|
1620
1587
|
})
|
|
1621
1588
|
|
|
1622
1589
|
logger.console.log(
|
|
1623
|
-
"[trails-sdk]
|
|
1590
|
+
"[trails-sdk] Origin meta transaction sent successfully",
|
|
1624
1591
|
{
|
|
1625
1592
|
metaTxnId,
|
|
1626
1593
|
},
|
|
1627
1594
|
)
|
|
1628
1595
|
|
|
1629
1596
|
// Store the waitForReceipt function for later use
|
|
1630
|
-
|
|
1597
|
+
originMetaTxnReceiptPromise = waitForReceipt
|
|
1631
1598
|
} else {
|
|
1632
1599
|
logger.console.warn(
|
|
1633
|
-
"[trails-sdk] Skipping
|
|
1600
|
+
"[trails-sdk] Skipping origin sendMetaTxn - missing metaTxn or precondition",
|
|
1634
1601
|
{
|
|
1635
|
-
hasMetaTxn: !!intent.metaTxns[
|
|
1636
|
-
hasPrecondition: !!intent.preconditions[
|
|
1602
|
+
hasMetaTxn: !!intent.payloads.metaTxns[0],
|
|
1603
|
+
hasPrecondition: !!intent.payloads.preconditions[0],
|
|
1637
1604
|
},
|
|
1638
1605
|
)
|
|
1639
1606
|
}
|
|
1640
|
-
// } else if (intent.destinationIntentAddress) {
|
|
1641
|
-
// destinationMetaTxnReceiptPromise = checkForDestinationDepositTx
|
|
1642
|
-
// }
|
|
1643
1607
|
}
|
|
1644
|
-
}
|
|
1645
1608
|
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
sourceChainId: originChainId,
|
|
1658
|
-
destinationChainId: destinationChainId,
|
|
1659
|
-
})
|
|
1660
|
-
break
|
|
1661
|
-
}
|
|
1662
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
} else {
|
|
1666
|
-
queueCctpPromise = () => Promise.resolve()
|
|
1667
|
-
}
|
|
1609
|
+
const destinationSendMetaTxnPromise = async () => {
|
|
1610
|
+
logger.console.log(
|
|
1611
|
+
"[trails-sdk] Starting destinationSendMetaTxnPromise",
|
|
1612
|
+
{
|
|
1613
|
+
quoteProvider: intent.payloads.quote.quoteProvider,
|
|
1614
|
+
hasQuoteProviderRequestId:
|
|
1615
|
+
!!intent.payloads.quote.quoteProviderRequestId,
|
|
1616
|
+
hasPrecondition1: !!intent.payloads.preconditions[1],
|
|
1617
|
+
hasMetaTxn1: !!intent.payloads.metaTxns[1],
|
|
1618
|
+
},
|
|
1619
|
+
)
|
|
1668
1620
|
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1621
|
+
if (
|
|
1622
|
+
intent.payloads.quote.quoteProvider === "relay" &&
|
|
1623
|
+
intent.payloads.quote.quoteProviderRequestId &&
|
|
1624
|
+
!intent.payloads.preconditions[1] &&
|
|
1625
|
+
!intent.payloads.metaTxns[1]
|
|
1626
|
+
) {
|
|
1627
|
+
logger.console.log(
|
|
1628
|
+
"[trails-sdk] Setting up relay destination promise",
|
|
1629
|
+
{
|
|
1630
|
+
quoteProviderRequestId:
|
|
1631
|
+
intent.payloads.quote.quoteProviderRequestId,
|
|
1632
|
+
},
|
|
1633
|
+
)
|
|
1634
|
+
// For relay, we'll wait for the receipt in the wait phase
|
|
1635
|
+
// Just store the requestId for later use
|
|
1636
|
+
destinationMetaTxnReceiptPromise = async (
|
|
1637
|
+
abortSignal?: AbortSignal,
|
|
1638
|
+
) => {
|
|
1639
|
+
logger.console.log(
|
|
1640
|
+
"[trails-sdk] waitForRelayDestinationTx starting",
|
|
1641
|
+
{
|
|
1642
|
+
quoteProviderRequestId:
|
|
1643
|
+
intent.payloads.quote.quoteProviderRequestId,
|
|
1644
|
+
aborted: abortSignal?.aborted,
|
|
1645
|
+
},
|
|
1646
|
+
)
|
|
1647
|
+
try {
|
|
1648
|
+
// Check if we should abort before starting
|
|
1649
|
+
if (abortSignal?.aborted) {
|
|
1650
|
+
logger.console.log(
|
|
1651
|
+
"[trails-sdk] Aborting relay destination tx due to abort signal",
|
|
1652
|
+
)
|
|
1653
|
+
return null
|
|
1654
|
+
}
|
|
1672
1655
|
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1656
|
+
const txHash = await waitForRelayDestinationTx(
|
|
1657
|
+
intent.payloads.quote.quoteProviderRequestId,
|
|
1658
|
+
)
|
|
1659
|
+
logger.console.log(
|
|
1660
|
+
"[trails-sdk] waitForRelayDestinationTx completed",
|
|
1661
|
+
{
|
|
1662
|
+
txHash,
|
|
1663
|
+
quoteProviderRequestId:
|
|
1664
|
+
intent.payloads.quote.quoteProviderRequestId,
|
|
1665
|
+
},
|
|
1666
|
+
)
|
|
1667
|
+
if (txHash) {
|
|
1668
|
+
logger.console.log(
|
|
1669
|
+
"[trails-sdk] Fetching transaction receipt for relay destination",
|
|
1670
|
+
{
|
|
1671
|
+
txHash,
|
|
1672
|
+
chainId: destinationChainId,
|
|
1673
|
+
},
|
|
1674
|
+
)
|
|
1675
|
+
|
|
1676
|
+
const destinationTxnReceipt =
|
|
1677
|
+
await destinationPublicClient.getTransactionReceipt({
|
|
1678
|
+
hash: txHash as `0x${string}`,
|
|
1679
|
+
})
|
|
1680
|
+
logger.console.log(
|
|
1681
|
+
"[trails-sdk] relay destinationTxnReceipt received",
|
|
1682
|
+
{
|
|
1683
|
+
txHash,
|
|
1684
|
+
blockNumber: destinationTxnReceipt?.blockNumber,
|
|
1685
|
+
status: destinationTxnReceipt?.status,
|
|
1686
|
+
gasUsed: destinationTxnReceipt?.gasUsed,
|
|
1687
|
+
},
|
|
1688
|
+
)
|
|
1689
|
+
if (transactionStates[2]) {
|
|
1690
|
+
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1691
|
+
destinationTxnReceipt,
|
|
1692
|
+
destinationChainId,
|
|
1693
|
+
transactionStates[2]?.label,
|
|
1694
|
+
)
|
|
1695
|
+
}
|
|
1696
|
+
onTransactionStateChange(transactionStates)
|
|
1697
|
+
return destinationTxnReceipt
|
|
1698
|
+
} else {
|
|
1699
|
+
logger.console.warn(
|
|
1700
|
+
"[trails-sdk] No txHash returned from waitForRelayDestinationTx",
|
|
1701
|
+
{
|
|
1702
|
+
quoteProviderRequestId:
|
|
1703
|
+
intent.payloads.quote.quoteProviderRequestId,
|
|
1704
|
+
},
|
|
1705
|
+
)
|
|
1706
|
+
}
|
|
1707
|
+
} catch (error: unknown) {
|
|
1708
|
+
logger.console.error(
|
|
1709
|
+
"[trails-sdk] Error waiting for relay destination tx",
|
|
1710
|
+
{
|
|
1711
|
+
error:
|
|
1712
|
+
error instanceof Error ? error.message : String(error),
|
|
1713
|
+
quoteProviderRequestId:
|
|
1714
|
+
intent.payloads.quote.quoteProviderRequestId,
|
|
1715
|
+
},
|
|
1716
|
+
)
|
|
1717
|
+
if (transactionStates?.[2]) {
|
|
1718
|
+
transactionStates[2].state = "failed"
|
|
1719
|
+
onTransactionStateChange(transactionStates)
|
|
1720
|
+
}
|
|
1721
|
+
throw error
|
|
1722
|
+
}
|
|
1723
|
+
return null
|
|
1724
|
+
}
|
|
1725
|
+
} else {
|
|
1726
|
+
logger.console.log(
|
|
1727
|
+
"[trails-sdk] Setting up destination meta transaction promise (non-relay)",
|
|
1728
|
+
{
|
|
1729
|
+
quoteProvider: intent.payloads.quote.quoteProvider,
|
|
1730
|
+
hasMetaTxn1: !!intent.payloads.metaTxns[1],
|
|
1731
|
+
hasPrecondition1: !!intent.payloads.preconditions[1],
|
|
1732
|
+
},
|
|
1733
|
+
)
|
|
1677
1734
|
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1735
|
+
if (
|
|
1736
|
+
intent.payloads.metaTxns[1] &&
|
|
1737
|
+
intent.payloads.preconditions[1]
|
|
1738
|
+
) {
|
|
1739
|
+
// Extract fee quote from intent response using metatxnid as key
|
|
1740
|
+
const metaTxnId = intent.payloads.metaTxns[1].id
|
|
1741
|
+
const feeQuote = intent.payloads.feeQuotes?.[metaTxnId]
|
|
1742
|
+
logger.console.log(
|
|
1743
|
+
"[trails-sdk] Extracted fee quote for destination meta txn",
|
|
1744
|
+
{
|
|
1745
|
+
metaTxnId,
|
|
1746
|
+
feeQuote,
|
|
1747
|
+
hasFeeQuote: !!feeQuote,
|
|
1748
|
+
},
|
|
1749
|
+
)
|
|
1682
1750
|
|
|
1683
|
-
|
|
1751
|
+
logger.console.log(
|
|
1752
|
+
"[trails-sdk] Calling sendMetaTxAndWaitForReceipt for destination",
|
|
1753
|
+
{
|
|
1754
|
+
metaTxnId,
|
|
1755
|
+
chainId: intent.payloads.metaTxns[1].chainId,
|
|
1756
|
+
walletAddress: intent.payloads.metaTxns[1].walletAddress,
|
|
1757
|
+
contract: intent.payloads.metaTxns[1].contract,
|
|
1758
|
+
},
|
|
1759
|
+
)
|
|
1684
1760
|
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1761
|
+
const { waitForReceipt } = await sendMetaTxAndWaitForReceipt({
|
|
1762
|
+
metaTx: intent.payloads.metaTxns[1] as MetaTxn,
|
|
1763
|
+
relayer: destinationRelayer,
|
|
1764
|
+
precondition: intent.payloads
|
|
1765
|
+
.preconditions[1] as IntentPrecondition,
|
|
1766
|
+
feeQuote: feeQuote,
|
|
1767
|
+
})
|
|
1689
1768
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
originMetaTxnReceipt = await originMetaTxnReceiptPromise()
|
|
1769
|
+
logger.console.log(
|
|
1770
|
+
"[trails-sdk] Destination meta transaction sent successfully",
|
|
1771
|
+
{
|
|
1772
|
+
metaTxnId,
|
|
1773
|
+
},
|
|
1774
|
+
)
|
|
1697
1775
|
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1776
|
+
// Store the waitForReceipt function for later use
|
|
1777
|
+
destinationMetaTxnReceiptPromise = waitForReceipt
|
|
1778
|
+
} else {
|
|
1779
|
+
logger.console.warn(
|
|
1780
|
+
"[trails-sdk] Skipping destination sendMetaTxn - missing metaTxn or precondition",
|
|
1781
|
+
{
|
|
1782
|
+
hasMetaTxn: !!intent.payloads.metaTxns[1],
|
|
1783
|
+
hasPrecondition: !!intent.payloads.preconditions[1],
|
|
1784
|
+
},
|
|
1703
1785
|
)
|
|
1704
|
-
|
|
1786
|
+
}
|
|
1787
|
+
// } else if (intent.payloads.destinationIntentAddress) {
|
|
1788
|
+
// destinationMetaTxnReceiptPromise = checkForDestinationDepositTx
|
|
1789
|
+
// }
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1705
1792
|
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1793
|
+
let queueCctpPromise: (() => Promise<void>) | null = null
|
|
1794
|
+
|
|
1795
|
+
const isCctp = intent.payloads.quote.quoteProvider === "cctp"
|
|
1796
|
+
if (isCctp) {
|
|
1797
|
+
queueCctpPromise = async () => {
|
|
1798
|
+
while (true) {
|
|
1799
|
+
const originMetaTxnHash = originMetaTxnReceipt?.txnHash
|
|
1800
|
+
if (originMetaTxnHash) {
|
|
1801
|
+
await queueCCTPTransfer({
|
|
1802
|
+
trailsClient,
|
|
1803
|
+
sourceTxHash: originMetaTxnHash,
|
|
1804
|
+
sourceChainId: originChainId,
|
|
1805
|
+
destinationChainId: destinationChainId,
|
|
1709
1806
|
})
|
|
1710
|
-
|
|
1711
|
-
decodeTrailsTokenSweeperEvents(receipt)
|
|
1712
|
-
transactionStates[1].decodedGuestModuleEvents =
|
|
1713
|
-
decodeGuestModuleEvents(receipt)
|
|
1714
|
-
transactionStates[1].refunded =
|
|
1715
|
-
transactionStates[1].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1716
|
-
(event) =>
|
|
1717
|
-
event.type === "Refund" ||
|
|
1718
|
-
event.type === "RefundAndSweep",
|
|
1719
|
-
) !== -1
|
|
1720
|
-
logger.console.log("[trails-sdk] Origin meta-tx events", {
|
|
1721
|
-
chainId: originChainId,
|
|
1722
|
-
callFailed: (
|
|
1723
|
-
transactionStates[1].decodedGuestModuleEvents || []
|
|
1724
|
-
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1725
|
-
sweeperEvents: (
|
|
1726
|
-
transactionStates[1].decodedTrailsTokenSweeperEvents || []
|
|
1727
|
-
).length,
|
|
1728
|
-
refunded: transactionStates[1].refunded,
|
|
1729
|
-
})
|
|
1730
|
-
onTransactionStateChange(transactionStates)
|
|
1731
|
-
} catch (error) {
|
|
1732
|
-
logger.console.error("Error decoding origin tx events", error)
|
|
1807
|
+
break
|
|
1733
1808
|
}
|
|
1809
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
1734
1810
|
}
|
|
1735
|
-
} catch (error) {
|
|
1736
|
-
logger.console.error(
|
|
1737
|
-
"[trails-sdk] Error waiting for origin receipt:",
|
|
1738
|
-
error,
|
|
1739
|
-
)
|
|
1740
1811
|
}
|
|
1741
1812
|
} else {
|
|
1742
|
-
|
|
1743
|
-
"[trails-sdk] No origin meta transaction receipt promise to wait for",
|
|
1744
|
-
)
|
|
1813
|
+
queueCctpPromise = () => Promise.resolve()
|
|
1745
1814
|
}
|
|
1746
|
-
}
|
|
1747
1815
|
|
|
1748
|
-
|
|
1816
|
+
checkForDepositTx().catch((error) => {
|
|
1817
|
+
console.error("Error checking for deposit tx", error)
|
|
1818
|
+
})
|
|
1819
|
+
|
|
1820
|
+
// Phase 1: Send meta transactions and queue CCTP
|
|
1749
1821
|
logger.console.log(
|
|
1750
|
-
"[trails-sdk]
|
|
1822
|
+
"[trails-sdk] Starting Phase 1: Sending meta transactions and queuing CCTP",
|
|
1751
1823
|
)
|
|
1752
|
-
if (destinationMetaTxnReceiptPromise) {
|
|
1753
|
-
try {
|
|
1754
|
-
// Create abort controller for cancelling destination polling
|
|
1755
|
-
const abortController = new AbortController()
|
|
1756
|
-
|
|
1757
|
-
// Race between destination receipt and failure polling
|
|
1758
|
-
const destinationReceiptPromise = destinationMetaTxnReceiptPromise
|
|
1759
|
-
? destinationMetaTxnReceiptPromise(abortController.signal)
|
|
1760
|
-
: Promise.resolve(null)
|
|
1761
|
-
|
|
1762
|
-
const failurePollingPromise = new Promise<null>((resolve) => {
|
|
1763
|
-
const pollForFailures = () => {
|
|
1764
|
-
const isPreviousTxCallFailed =
|
|
1765
|
-
transactionStates?.some((tx) => tx.state === "failed") ||
|
|
1766
|
-
transactionStates?.some((tx) =>
|
|
1767
|
-
tx?.decodedGuestModuleEvents?.some(
|
|
1768
|
-
(event) => event.type === "CallFailed",
|
|
1769
|
-
),
|
|
1770
|
-
)
|
|
1771
1824
|
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
abortController.abort()
|
|
1777
|
-
resolve(null)
|
|
1778
|
-
} else {
|
|
1779
|
-
// Continue polling every 1 second
|
|
1780
|
-
setTimeout(pollForFailures, 1000)
|
|
1781
|
-
}
|
|
1782
|
-
}
|
|
1783
|
-
pollForFailures()
|
|
1784
|
-
})
|
|
1825
|
+
await Promise.all([
|
|
1826
|
+
originSendMetaTxnPromise(),
|
|
1827
|
+
destinationSendMetaTxnPromise(),
|
|
1828
|
+
])
|
|
1785
1829
|
|
|
1786
|
-
|
|
1787
|
-
destinationReceiptPromise,
|
|
1788
|
-
failurePollingPromise,
|
|
1789
|
-
])) as MetaTxnReceipt
|
|
1830
|
+
logger.console.log("[trails-sdk] Phase 1 completed successfully")
|
|
1790
1831
|
|
|
1832
|
+
// Phase 2: Wait for receipts and execute deposit
|
|
1833
|
+
logger.console.log(
|
|
1834
|
+
"[trails-sdk] Starting Phase 2: Waiting for receipts and executing deposit",
|
|
1835
|
+
)
|
|
1836
|
+
|
|
1837
|
+
const waitForOriginMetaTxnReceiptPromise = async () => {
|
|
1838
|
+
logger.console.log(
|
|
1839
|
+
"[trails-sdk] Waiting for origin meta transaction receipt",
|
|
1840
|
+
)
|
|
1841
|
+
if (originMetaTxnReceiptPromise) {
|
|
1842
|
+
try {
|
|
1843
|
+
originMetaTxnReceipt = await originMetaTxnReceiptPromise()
|
|
1844
|
+
|
|
1845
|
+
if (originMetaTxnReceipt && transactionStates[1]) {
|
|
1846
|
+
transactionStates[1] = getTransactionStateFromReceipt(
|
|
1847
|
+
originMetaTxnReceipt,
|
|
1848
|
+
originChainId,
|
|
1849
|
+
transactionStates[1]?.label,
|
|
1850
|
+
)
|
|
1851
|
+
onTransactionStateChange(transactionStates)
|
|
1852
|
+
|
|
1853
|
+
try {
|
|
1854
|
+
const receipt = await publicClient.getTransactionReceipt({
|
|
1855
|
+
hash: originMetaTxnReceipt.txnHash as `0x${string}`,
|
|
1856
|
+
})
|
|
1857
|
+
transactionStates[1].decodedTrailsTokenSweeperEvents =
|
|
1858
|
+
decodeTrailsTokenSweeperEvents(receipt)
|
|
1859
|
+
transactionStates[1].decodedGuestModuleEvents =
|
|
1860
|
+
decodeGuestModuleEvents(receipt)
|
|
1861
|
+
transactionStates[1].refunded =
|
|
1862
|
+
transactionStates[1].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1863
|
+
(event) =>
|
|
1864
|
+
event.type === "Refund" ||
|
|
1865
|
+
event.type === "RefundAndSweep",
|
|
1866
|
+
) !== -1
|
|
1867
|
+
logger.console.log("[trails-sdk] Origin meta-tx events", {
|
|
1868
|
+
chainId: originChainId,
|
|
1869
|
+
callFailed: (
|
|
1870
|
+
transactionStates[1].decodedGuestModuleEvents || []
|
|
1871
|
+
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1872
|
+
sweeperEvents: (
|
|
1873
|
+
transactionStates[1].decodedTrailsTokenSweeperEvents || []
|
|
1874
|
+
).length,
|
|
1875
|
+
refunded: transactionStates[1].refunded,
|
|
1876
|
+
})
|
|
1877
|
+
|
|
1878
|
+
// Check for transaction failure or refund
|
|
1879
|
+
const hasCallFailed = (
|
|
1880
|
+
transactionStates[1].decodedGuestModuleEvents || []
|
|
1881
|
+
).some((e: any) => e?.type === "CallFailed")
|
|
1882
|
+
|
|
1883
|
+
if (transactionStates[1].refunded || hasCallFailed) {
|
|
1884
|
+
const errorMessage = transactionStates[1].refunded
|
|
1885
|
+
? "Origin transaction was refunded"
|
|
1886
|
+
: "Origin transaction call failed"
|
|
1887
|
+
|
|
1888
|
+
logger.console.error("[trails-sdk] Origin meta-tx failed", {
|
|
1889
|
+
refunded: transactionStates[1].refunded,
|
|
1890
|
+
callFailed: hasCallFailed,
|
|
1891
|
+
})
|
|
1892
|
+
|
|
1893
|
+
// Call onCheckoutError callback if provided
|
|
1894
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1895
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
onTransactionStateChange(transactionStates)
|
|
1900
|
+
} catch (error) {
|
|
1901
|
+
logger.console.error("Error decoding origin tx events", error)
|
|
1902
|
+
}
|
|
1903
|
+
}
|
|
1904
|
+
} catch (error) {
|
|
1905
|
+
logger.console.error(
|
|
1906
|
+
"[trails-sdk] Error waiting for origin receipt:",
|
|
1907
|
+
error,
|
|
1908
|
+
)
|
|
1909
|
+
}
|
|
1910
|
+
} else {
|
|
1791
1911
|
logger.console.log(
|
|
1792
|
-
"[trails-sdk]
|
|
1793
|
-
destinationMetaTxnReceipt,
|
|
1912
|
+
"[trails-sdk] No origin meta transaction receipt promise to wait for",
|
|
1794
1913
|
)
|
|
1914
|
+
}
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
const waitForDestinationMetaTxnReceiptPromise = async () => {
|
|
1918
|
+
logger.console.log(
|
|
1919
|
+
"[trails-sdk] Waiting for destination meta transaction receipt",
|
|
1920
|
+
)
|
|
1921
|
+
if (destinationMetaTxnReceiptPromise) {
|
|
1922
|
+
try {
|
|
1923
|
+
// Create abort controller for cancelling destination polling
|
|
1924
|
+
const abortController = new AbortController()
|
|
1925
|
+
|
|
1926
|
+
// Race between destination receipt and failure polling
|
|
1927
|
+
const destinationReceiptPromise = destinationMetaTxnReceiptPromise
|
|
1928
|
+
? destinationMetaTxnReceiptPromise(abortController.signal)
|
|
1929
|
+
: Promise.resolve(null)
|
|
1930
|
+
|
|
1931
|
+
const failurePollingPromise = new Promise<null>((resolve) => {
|
|
1932
|
+
const pollForFailures = () => {
|
|
1933
|
+
const isPreviousTxCallFailed =
|
|
1934
|
+
transactionStates?.some((tx) => tx.state === "failed") ||
|
|
1935
|
+
transactionStates?.some((tx) =>
|
|
1936
|
+
tx?.decodedGuestModuleEvents?.some(
|
|
1937
|
+
(event) => event.type === "CallFailed",
|
|
1938
|
+
),
|
|
1939
|
+
)
|
|
1940
|
+
|
|
1941
|
+
if (isPreviousTxCallFailed) {
|
|
1942
|
+
logger.console.log(
|
|
1943
|
+
"[trails-sdk] Aborting destination meta transaction due to previous transaction failure",
|
|
1944
|
+
)
|
|
1945
|
+
abortController.abort()
|
|
1946
|
+
resolve(null)
|
|
1947
|
+
} else {
|
|
1948
|
+
// Continue polling every 1 second
|
|
1949
|
+
setTimeout(pollForFailures, 1000)
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1952
|
+
pollForFailures()
|
|
1953
|
+
})
|
|
1954
|
+
|
|
1955
|
+
destinationMetaTxnReceipt = (await Promise.race([
|
|
1956
|
+
destinationReceiptPromise,
|
|
1957
|
+
failurePollingPromise,
|
|
1958
|
+
])) as MetaTxnReceipt
|
|
1795
1959
|
|
|
1796
|
-
|
|
1797
|
-
|
|
1960
|
+
logger.console.log(
|
|
1961
|
+
"[trails-sdk] destinationMetaTxnReceipt",
|
|
1798
1962
|
destinationMetaTxnReceipt,
|
|
1799
|
-
destinationChainId,
|
|
1800
|
-
transactionStates[2]?.label,
|
|
1801
1963
|
)
|
|
1802
|
-
onTransactionStateChange(transactionStates)
|
|
1803
1964
|
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
decodeTrailsTokenSweeperEvents(receipt)
|
|
1811
|
-
transactionStates[2].decodedGuestModuleEvents =
|
|
1812
|
-
decodeGuestModuleEvents(receipt)
|
|
1813
|
-
transactionStates[2].refunded =
|
|
1814
|
-
transactionStates[2].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1815
|
-
(event) =>
|
|
1816
|
-
event.type === "Refund" ||
|
|
1817
|
-
event.type === "RefundAndSweep" ||
|
|
1818
|
-
event.type === "Sweep",
|
|
1819
|
-
) !== -1
|
|
1820
|
-
logger.console.log("[trails-sdk] Destination meta-tx events", {
|
|
1821
|
-
chainId: destinationChainId,
|
|
1822
|
-
callFailed: (
|
|
1823
|
-
transactionStates[2].decodedGuestModuleEvents || []
|
|
1824
|
-
).filter((e: any) => e?.type === "CallFailed").length,
|
|
1825
|
-
sweeperEvents: (
|
|
1826
|
-
transactionStates[2].decodedTrailsTokenSweeperEvents || []
|
|
1827
|
-
).length,
|
|
1828
|
-
refunded: transactionStates[2].refunded,
|
|
1829
|
-
})
|
|
1965
|
+
if (destinationMetaTxnReceipt && transactionStates[2]) {
|
|
1966
|
+
transactionStates[2] = getTransactionStateFromReceipt(
|
|
1967
|
+
destinationMetaTxnReceipt,
|
|
1968
|
+
destinationChainId,
|
|
1969
|
+
transactionStates[2]?.label,
|
|
1970
|
+
)
|
|
1830
1971
|
onTransactionStateChange(transactionStates)
|
|
1831
|
-
|
|
1832
|
-
|
|
1972
|
+
|
|
1973
|
+
try {
|
|
1974
|
+
const receipt =
|
|
1975
|
+
await destinationPublicClient.getTransactionReceipt({
|
|
1976
|
+
hash: destinationMetaTxnReceipt.txnHash as `0x${string}`,
|
|
1977
|
+
})
|
|
1978
|
+
transactionStates[2].decodedTrailsTokenSweeperEvents =
|
|
1979
|
+
decodeTrailsTokenSweeperEvents(receipt)
|
|
1980
|
+
transactionStates[2].decodedGuestModuleEvents =
|
|
1981
|
+
decodeGuestModuleEvents(receipt)
|
|
1982
|
+
transactionStates[2].refunded =
|
|
1983
|
+
transactionStates[2].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1984
|
+
(event) =>
|
|
1985
|
+
event.type === "Refund" ||
|
|
1986
|
+
event.type === "RefundAndSweep",
|
|
1987
|
+
) !== -1 ||
|
|
1988
|
+
(transactionStates[2].decodedTrailsTokenSweeperEvents.findIndex(
|
|
1989
|
+
(event) => event.type === "Sweep",
|
|
1990
|
+
) !== -1 &&
|
|
1991
|
+
transactionStates[2].decodedGuestModuleEvents.findIndex(
|
|
1992
|
+
(event) => event.type === "CallFailed",
|
|
1993
|
+
) !== -1)
|
|
1994
|
+
logger.console.log(
|
|
1995
|
+
"[trails-sdk] Destination meta-tx events",
|
|
1996
|
+
{
|
|
1997
|
+
chainId: destinationChainId,
|
|
1998
|
+
callFailed: (
|
|
1999
|
+
transactionStates[2].decodedGuestModuleEvents || []
|
|
2000
|
+
).filter((e: any) => e?.type === "CallFailed").length,
|
|
2001
|
+
sweeperEvents: (
|
|
2002
|
+
transactionStates[2].decodedTrailsTokenSweeperEvents ||
|
|
2003
|
+
[]
|
|
2004
|
+
).length,
|
|
2005
|
+
refunded: transactionStates[2].refunded,
|
|
2006
|
+
},
|
|
2007
|
+
)
|
|
2008
|
+
|
|
2009
|
+
// Check for transaction failure or refund
|
|
2010
|
+
const hasCallFailed = (
|
|
2011
|
+
transactionStates[2].decodedGuestModuleEvents || []
|
|
2012
|
+
).some((e: any) => e?.type === "CallFailed")
|
|
2013
|
+
|
|
2014
|
+
if (transactionStates[2].refunded || hasCallFailed) {
|
|
2015
|
+
const errorMessage = transactionStates[2].refunded
|
|
2016
|
+
? "Destination transaction was refunded"
|
|
2017
|
+
: "Destination transaction call failed"
|
|
2018
|
+
|
|
2019
|
+
logger.console.error(
|
|
2020
|
+
"[trails-sdk] Destination meta-tx failed",
|
|
2021
|
+
{
|
|
2022
|
+
refunded: transactionStates[2].refunded,
|
|
2023
|
+
callFailed: hasCallFailed,
|
|
2024
|
+
},
|
|
2025
|
+
)
|
|
2026
|
+
|
|
2027
|
+
// Call onCheckoutError callback if provided
|
|
2028
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2029
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
|
|
2033
|
+
onTransactionStateChange(transactionStates)
|
|
2034
|
+
} catch (error) {
|
|
2035
|
+
console.error("Error decoding destination tx events", error)
|
|
2036
|
+
}
|
|
1833
2037
|
}
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
error,
|
|
1839
|
-
)
|
|
1840
|
-
// For relay transactions, this might be expected if still waiting
|
|
1841
|
-
if (intent.quote.quoteProvider === "relay") {
|
|
1842
|
-
logger.console.log(
|
|
1843
|
-
"[trails-sdk] Relay transaction still waiting, this is normal",
|
|
2038
|
+
} catch (error) {
|
|
2039
|
+
console.error(
|
|
2040
|
+
"[trails-sdk] Error waiting for destination receipt:",
|
|
2041
|
+
error,
|
|
1844
2042
|
)
|
|
2043
|
+
// For relay transactions, this might be expected if still waiting
|
|
2044
|
+
if (intent.payloads.quote.quoteProvider === "relay") {
|
|
2045
|
+
logger.console.log(
|
|
2046
|
+
"[trails-sdk] Relay transaction still waiting, this is normal",
|
|
2047
|
+
)
|
|
2048
|
+
}
|
|
1845
2049
|
}
|
|
2050
|
+
} else {
|
|
2051
|
+
logger.console.log(
|
|
2052
|
+
"[trails-sdk] No destination meta transaction receipt promise to wait for",
|
|
2053
|
+
)
|
|
1846
2054
|
}
|
|
1847
|
-
} else {
|
|
1848
|
-
logger.console.log(
|
|
1849
|
-
"[trails-sdk] No destination meta transaction receipt promise to wait for",
|
|
1850
|
-
)
|
|
1851
2055
|
}
|
|
1852
|
-
}
|
|
1853
2056
|
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2057
|
+
logger.console.log(
|
|
2058
|
+
"[trails-sdk] Executing Phase 2 Promise.all with deposit",
|
|
2059
|
+
)
|
|
2060
|
+
logger.console.log(
|
|
2061
|
+
"[trails-sdk] About to call depositPromise - fundMethod is:",
|
|
2062
|
+
fundMethod,
|
|
2063
|
+
)
|
|
1861
2064
|
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
2065
|
+
await Promise.all([
|
|
2066
|
+
depositPromise(),
|
|
2067
|
+
waitForOriginMetaTxnReceiptPromise(),
|
|
2068
|
+
waitForDestinationMetaTxnReceiptPromise(),
|
|
2069
|
+
queueCctpPromise(),
|
|
2070
|
+
])
|
|
2071
|
+
logger.console.log("[trails-sdk] Phase 2 completed successfully")
|
|
1869
2072
|
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
2073
|
+
// Track payment completion for different chain and different token
|
|
2074
|
+
if (originUserTxReceipt && destinationMetaTxnReceipt) {
|
|
2075
|
+
trackPaymentCompleted({
|
|
2076
|
+
userAddress: account.address,
|
|
2077
|
+
originIntentAddress,
|
|
2078
|
+
originTxHash: (originUserTxReceipt as TransactionReceipt)
|
|
2079
|
+
.transactionHash,
|
|
2080
|
+
destinationTxHash: (destinationMetaTxnReceipt as MetaTxnReceipt)
|
|
2081
|
+
?.txnHash,
|
|
2082
|
+
originChainId,
|
|
2083
|
+
destinationChainId,
|
|
2084
|
+
mode,
|
|
2085
|
+
fundMethod,
|
|
2086
|
+
originTokenSymbol,
|
|
2087
|
+
originTokenAddress,
|
|
2088
|
+
destinationTokenAddress,
|
|
2089
|
+
destinationTokenSymbol,
|
|
2090
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2091
|
+
destinationTokenAmountUsd:
|
|
2092
|
+
effectiveDestinationTokenAmountUsd?.toString(),
|
|
2093
|
+
})
|
|
1891
2094
|
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
2095
|
+
// Call onCheckoutComplete callback if provided
|
|
2096
|
+
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
2097
|
+
checkoutOnHandlers.triggerCheckoutComplete()
|
|
2098
|
+
}
|
|
2099
|
+
} else {
|
|
2100
|
+
if (
|
|
2101
|
+
transactionStates[1] &&
|
|
2102
|
+
transactionStates[1]?.transactionHash === "" &&
|
|
2103
|
+
transactionStates[1]?.state === "pending"
|
|
2104
|
+
) {
|
|
2105
|
+
transactionStates[1].state = "aborted"
|
|
2106
|
+
onTransactionStateChange(transactionStates)
|
|
2107
|
+
}
|
|
2108
|
+
if (
|
|
2109
|
+
transactionStates[2] &&
|
|
2110
|
+
transactionStates[2]?.transactionHash === "" &&
|
|
2111
|
+
transactionStates[2]?.state === "pending"
|
|
2112
|
+
) {
|
|
2113
|
+
transactionStates[2].state = "aborted"
|
|
2114
|
+
onTransactionStateChange(transactionStates)
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
// Track payment error if transactions didn't complete successfully
|
|
2118
|
+
trackPaymentError({
|
|
2119
|
+
error:
|
|
2120
|
+
"Payment transactions possibly did not complete successfully. Was not able to receive both origin and destination meta transaction receipts. May be an API error.",
|
|
2121
|
+
userAddress: account.address,
|
|
2122
|
+
originIntentAddress,
|
|
2123
|
+
mode,
|
|
2124
|
+
fundMethod,
|
|
2125
|
+
originChainId,
|
|
2126
|
+
destinationChainId,
|
|
2127
|
+
originTokenSymbol,
|
|
2128
|
+
originTokenAddress,
|
|
2129
|
+
destinationTokenAddress,
|
|
2130
|
+
destinationTokenSymbol,
|
|
2131
|
+
})
|
|
2132
|
+
|
|
2133
|
+
// Call onCheckoutError callback if provided
|
|
2134
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2135
|
+
checkoutOnHandlers.triggerCheckoutError(
|
|
2136
|
+
"Payment transactions did not complete successfully",
|
|
2137
|
+
)
|
|
2138
|
+
}
|
|
1904
2139
|
}
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2140
|
+
|
|
2141
|
+
return {
|
|
2142
|
+
originUserTxReceipt,
|
|
2143
|
+
originMetaTxnReceipt,
|
|
2144
|
+
destinationMetaTxnReceipt,
|
|
2145
|
+
totalCompletionSeconds: await getTxTimeDiff(
|
|
2146
|
+
transactionStates[0],
|
|
2147
|
+
transactionStates[2],
|
|
2148
|
+
),
|
|
1912
2149
|
}
|
|
2150
|
+
} catch (error) {
|
|
2151
|
+
const errorMessage =
|
|
2152
|
+
error instanceof Error
|
|
2153
|
+
? error.message
|
|
2154
|
+
: "Unknown error occurred during transaction"
|
|
2155
|
+
logger.console.error(
|
|
2156
|
+
"[trails-sdk] Error in sendHandlerForDifferentChainDifferentToken:",
|
|
2157
|
+
error,
|
|
2158
|
+
)
|
|
1913
2159
|
|
|
1914
|
-
// Track payment error
|
|
2160
|
+
// Track payment error
|
|
1915
2161
|
trackPaymentError({
|
|
1916
|
-
error:
|
|
1917
|
-
"Payment transactions possibly did not complete successfully. Was not able to receive both origin and destination meta transaction receipts. May be an API error.",
|
|
2162
|
+
error: errorMessage,
|
|
1918
2163
|
userAddress: account.address,
|
|
1919
2164
|
originIntentAddress,
|
|
1920
2165
|
mode,
|
|
@@ -1929,20 +2174,11 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1929
2174
|
|
|
1930
2175
|
// Call onCheckoutError callback if provided
|
|
1931
2176
|
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
1932
|
-
checkoutOnHandlers.triggerCheckoutError(
|
|
1933
|
-
"Payment transactions did not complete successfully",
|
|
1934
|
-
)
|
|
2177
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
1935
2178
|
}
|
|
1936
|
-
}
|
|
1937
2179
|
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
originMetaTxnReceipt,
|
|
1941
|
-
destinationMetaTxnReceipt,
|
|
1942
|
-
totalCompletionSeconds: await getTxTimeDiff(
|
|
1943
|
-
transactionStates[0],
|
|
1944
|
-
transactionStates[2],
|
|
1945
|
-
),
|
|
2180
|
+
// Re-throw the error so caller can handle if needed
|
|
2181
|
+
throw error
|
|
1946
2182
|
}
|
|
1947
2183
|
},
|
|
1948
2184
|
}
|
|
@@ -2002,7 +2238,6 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2002
2238
|
})
|
|
2003
2239
|
|
|
2004
2240
|
let noSufficientBalance = false
|
|
2005
|
-
const minimumNotMet = false
|
|
2006
2241
|
|
|
2007
2242
|
const { hasEnoughBalance } = await checkAccountBalance({
|
|
2008
2243
|
account,
|
|
@@ -2033,7 +2268,6 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2033
2268
|
slippageTolerance,
|
|
2034
2269
|
quoteProvider: "",
|
|
2035
2270
|
noSufficientBalance,
|
|
2036
|
-
minimumNotMet,
|
|
2037
2271
|
})
|
|
2038
2272
|
|
|
2039
2273
|
// Call onCheckoutQuote callback if provided
|
|
@@ -2047,183 +2281,240 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2047
2281
|
onOriginSend,
|
|
2048
2282
|
}: {
|
|
2049
2283
|
onOriginSend?: () => void
|
|
2050
|
-
feeTokenAddress?: string | null
|
|
2051
2284
|
}): Promise<SendReturn> => {
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2285
|
+
try {
|
|
2286
|
+
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
2287
|
+
account,
|
|
2288
|
+
tokenAddress: effectiveOriginTokenAddress,
|
|
2289
|
+
depositAmount: swapAmount,
|
|
2290
|
+
publicClient: effectivePublicClient,
|
|
2291
|
+
})
|
|
2058
2292
|
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2293
|
+
if (!hasEnoughBalance) {
|
|
2294
|
+
throw balanceError
|
|
2295
|
+
}
|
|
2062
2296
|
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2297
|
+
const depositAmountFormatted = Number(
|
|
2298
|
+
formatUnits(BigInt(swapAmount), originTokenDecimals),
|
|
2299
|
+
)
|
|
2300
|
+
const depositAmountUsd = calcAmountUsdPrice({
|
|
2301
|
+
amount: depositAmountFormatted,
|
|
2302
|
+
usdPrice: sourceTokenPriceUsd,
|
|
2303
|
+
})
|
|
2070
2304
|
|
|
2071
|
-
|
|
2072
|
-
|
|
2073
|
-
|
|
2074
|
-
? recipient
|
|
2075
|
-
: effectiveOriginTokenAddress === zeroAddress
|
|
2305
|
+
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
2306
|
+
const originCallParams = {
|
|
2307
|
+
to: hasCustomCalldata
|
|
2076
2308
|
? recipient
|
|
2077
|
-
: effectiveOriginTokenAddress
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
:
|
|
2081
|
-
?
|
|
2082
|
-
:
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2089
|
-
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2309
|
+
: effectiveOriginTokenAddress === zeroAddress
|
|
2310
|
+
? recipient
|
|
2311
|
+
: effectiveOriginTokenAddress,
|
|
2312
|
+
data: hasCustomCalldata
|
|
2313
|
+
? destinationCalldata
|
|
2314
|
+
: effectiveOriginTokenAddress === zeroAddress
|
|
2315
|
+
? "0x"
|
|
2316
|
+
: getERC20TransferData({
|
|
2317
|
+
recipient,
|
|
2318
|
+
amount: BigInt(swapAmount),
|
|
2319
|
+
}),
|
|
2320
|
+
value:
|
|
2321
|
+
effectiveOriginTokenAddress === zeroAddress
|
|
2322
|
+
? BigInt(swapAmount)
|
|
2323
|
+
: "0",
|
|
2324
|
+
chainId: effectiveOriginChainId,
|
|
2325
|
+
chain: effectiveOriginChain,
|
|
2326
|
+
}
|
|
2095
2327
|
|
|
2096
|
-
|
|
2097
|
-
const originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
2098
|
-
const destinationMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
2328
|
+
logger.console.log("[trails-sdk] origin call params", originCallParams)
|
|
2099
2329
|
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
})
|
|
2104
|
-
if (!dryMode) {
|
|
2105
|
-
try {
|
|
2106
|
-
onTransactionStateChange([
|
|
2107
|
-
{
|
|
2108
|
-
transactionHash: "",
|
|
2109
|
-
explorerUrl: "",
|
|
2110
|
-
chainId: effectiveOriginChainId,
|
|
2111
|
-
state: "pending",
|
|
2112
|
-
label: "Execute",
|
|
2113
|
-
},
|
|
2114
|
-
])
|
|
2115
|
-
} catch (error) {
|
|
2116
|
-
logger.console.error(
|
|
2117
|
-
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2118
|
-
error,
|
|
2119
|
-
)
|
|
2120
|
-
}
|
|
2330
|
+
let originUserTxReceipt: TransactionReceipt | null = null
|
|
2331
|
+
const originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
2332
|
+
const destinationMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
2121
2333
|
|
|
2122
|
-
|
|
2334
|
+
await attemptSwitchChain({
|
|
2335
|
+
walletClient,
|
|
2336
|
+
desiredChainId: effectiveOriginChainId,
|
|
2337
|
+
})
|
|
2338
|
+
if (!dryMode) {
|
|
2123
2339
|
try {
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2340
|
+
onTransactionStateChange([
|
|
2341
|
+
{
|
|
2342
|
+
transactionHash: "",
|
|
2343
|
+
explorerUrl: "",
|
|
2344
|
+
chainId: effectiveOriginChainId,
|
|
2345
|
+
state: "pending",
|
|
2346
|
+
label: "Execute",
|
|
2347
|
+
},
|
|
2348
|
+
])
|
|
2349
|
+
} catch (error) {
|
|
2350
|
+
logger.console.error(
|
|
2351
|
+
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2352
|
+
error,
|
|
2353
|
+
)
|
|
2354
|
+
}
|
|
2131
2355
|
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2356
|
+
if (hasCustomCalldata) {
|
|
2357
|
+
try {
|
|
2358
|
+
const needsApproval = await getNeedsApproval({
|
|
2359
|
+
publicClient: effectivePublicClient,
|
|
2360
|
+
token: effectiveOriginTokenAddress,
|
|
2361
|
+
account: account.address,
|
|
2136
2362
|
spender: recipient,
|
|
2137
2363
|
amount: maxUint256,
|
|
2138
|
-
chain: effectiveOriginChain,
|
|
2139
2364
|
})
|
|
2140
2365
|
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2366
|
+
if (needsApproval) {
|
|
2367
|
+
const txHash = await approveERC20({
|
|
2368
|
+
walletClient,
|
|
2369
|
+
tokenAddress: effectiveOriginTokenAddress,
|
|
2370
|
+
spender: recipient,
|
|
2371
|
+
amount: maxUint256,
|
|
2372
|
+
chain: effectiveOriginChain,
|
|
2373
|
+
})
|
|
2374
|
+
|
|
2375
|
+
logger.console.log("waiting for approve", txHash)
|
|
2376
|
+
await effectivePublicClient.waitForTransactionReceipt({
|
|
2377
|
+
hash: txHash,
|
|
2378
|
+
})
|
|
2379
|
+
logger.console.log("approve done")
|
|
2380
|
+
}
|
|
2381
|
+
} catch (error) {
|
|
2382
|
+
logger.console.error("[trails-sdk] Error approving ERC20", error)
|
|
2146
2383
|
}
|
|
2147
|
-
} catch (error) {
|
|
2148
|
-
logger.console.error("[trails-sdk] Error approving ERC20", error)
|
|
2149
2384
|
}
|
|
2150
|
-
}
|
|
2151
|
-
|
|
2152
|
-
logger.console.log("[trails-sdk] origin call params", originCallParams)
|
|
2153
|
-
const txHash = await sendOriginTransaction(
|
|
2154
|
-
account,
|
|
2155
|
-
walletClient,
|
|
2156
|
-
originCallParams as any,
|
|
2157
|
-
{
|
|
2158
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2159
|
-
},
|
|
2160
|
-
) // TODO: Add proper type
|
|
2161
2385
|
|
|
2162
|
-
|
|
2386
|
+
// Show persistent toast for checkout flow
|
|
2387
|
+
updatePersistentToast(
|
|
2388
|
+
"Payment Started",
|
|
2389
|
+
"Waiting for wallet confirmation...",
|
|
2390
|
+
"info",
|
|
2391
|
+
)
|
|
2163
2392
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2393
|
+
logger.console.log(
|
|
2394
|
+
"[trails-sdk] origin call params",
|
|
2395
|
+
originCallParams,
|
|
2396
|
+
)
|
|
2397
|
+
const txHash = await sendOriginTransaction(
|
|
2398
|
+
account,
|
|
2399
|
+
walletClient,
|
|
2400
|
+
originCallParams as any,
|
|
2401
|
+
{
|
|
2402
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2403
|
+
},
|
|
2404
|
+
) // TODO: Add proper type
|
|
2167
2405
|
|
|
2168
|
-
|
|
2169
|
-
const receipt = await effectivePublicClient.waitForTransactionReceipt({
|
|
2170
|
-
hash: txHash,
|
|
2171
|
-
})
|
|
2172
|
-
logger.console.log("[trails-sdk] receipt", receipt)
|
|
2173
|
-
originUserTxReceipt = receipt
|
|
2406
|
+
logger.console.log("[trails-sdk] origin tx", txHash)
|
|
2174
2407
|
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
userAddress: account.address,
|
|
2179
|
-
blockNumber: Number(receipt.blockNumber),
|
|
2180
|
-
originTokenAddress,
|
|
2181
|
-
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2182
|
-
})
|
|
2408
|
+
if (onOriginSend) {
|
|
2409
|
+
onOriginSend()
|
|
2410
|
+
}
|
|
2183
2411
|
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
transactionStates[0]?.label,
|
|
2190
|
-
),
|
|
2191
|
-
])
|
|
2192
|
-
} catch (error) {
|
|
2193
|
-
logger.console.error(
|
|
2194
|
-
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2195
|
-
error,
|
|
2412
|
+
// Wait for transaction receipt
|
|
2413
|
+
const receipt = await effectivePublicClient.waitForTransactionReceipt(
|
|
2414
|
+
{
|
|
2415
|
+
hash: txHash,
|
|
2416
|
+
},
|
|
2196
2417
|
)
|
|
2197
|
-
|
|
2418
|
+
logger.console.log("[trails-sdk] receipt", receipt)
|
|
2419
|
+
originUserTxReceipt = receipt
|
|
2198
2420
|
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
|
|
2421
|
+
trackTransactionConfirmed({
|
|
2422
|
+
transactionHash: txHash,
|
|
2423
|
+
chainId: effectiveOriginChainId,
|
|
2202
2424
|
userAddress: account.address,
|
|
2203
|
-
|
|
2204
|
-
originChainId: effectiveOriginChainId, // Same chain
|
|
2205
|
-
mode,
|
|
2206
|
-
fundMethod,
|
|
2425
|
+
blockNumber: Number(receipt.blockNumber),
|
|
2207
2426
|
originTokenAddress,
|
|
2208
2427
|
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2209
|
-
destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
|
|
2210
|
-
})
|
|
2211
|
-
} else if (originUserTxReceipt) {
|
|
2212
|
-
trackPaymentError({
|
|
2213
|
-
error: "Transaction failed",
|
|
2214
|
-
userAddress: account.address,
|
|
2215
|
-
mode,
|
|
2216
|
-
fundMethod,
|
|
2217
|
-
originTokenAddress,
|
|
2218
2428
|
})
|
|
2429
|
+
|
|
2430
|
+
// Remove persistent toast and show success
|
|
2431
|
+
const chainInfo = getChainInfo(effectiveOriginChainId)
|
|
2432
|
+
updatePersistentToast(
|
|
2433
|
+
"Transfer Confirmed",
|
|
2434
|
+
`Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
|
|
2435
|
+
"info",
|
|
2436
|
+
)
|
|
2437
|
+
|
|
2438
|
+
try {
|
|
2439
|
+
onTransactionStateChange([
|
|
2440
|
+
getTransactionStateFromReceipt(
|
|
2441
|
+
originUserTxReceipt,
|
|
2442
|
+
effectiveOriginChainId,
|
|
2443
|
+
transactionStates[0]?.label,
|
|
2444
|
+
),
|
|
2445
|
+
])
|
|
2446
|
+
} catch (error) {
|
|
2447
|
+
logger.console.error(
|
|
2448
|
+
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2449
|
+
error,
|
|
2450
|
+
)
|
|
2451
|
+
}
|
|
2452
|
+
|
|
2453
|
+
// Track payment completion for same-chain same-token transaction
|
|
2454
|
+
if (originUserTxReceipt && originUserTxReceipt.status === "success") {
|
|
2455
|
+
trackPaymentCompleted({
|
|
2456
|
+
userAddress: account.address,
|
|
2457
|
+
originTxHash: originUserTxReceipt.transactionHash,
|
|
2458
|
+
originChainId: effectiveOriginChainId, // Same chain
|
|
2459
|
+
mode,
|
|
2460
|
+
fundMethod,
|
|
2461
|
+
originTokenAddress,
|
|
2462
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2463
|
+
destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
|
|
2464
|
+
})
|
|
2465
|
+
|
|
2466
|
+
// Call onCheckoutComplete callback if provided
|
|
2467
|
+
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
2468
|
+
checkoutOnHandlers.triggerCheckoutComplete()
|
|
2469
|
+
}
|
|
2470
|
+
} else if (originUserTxReceipt) {
|
|
2471
|
+
trackPaymentError({
|
|
2472
|
+
error: "Transaction failed",
|
|
2473
|
+
userAddress: account.address,
|
|
2474
|
+
mode,
|
|
2475
|
+
fundMethod,
|
|
2476
|
+
originTokenAddress,
|
|
2477
|
+
})
|
|
2478
|
+
|
|
2479
|
+
// Call onCheckoutError callback if provided
|
|
2480
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2481
|
+
checkoutOnHandlers.triggerCheckoutError("Transaction failed")
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2219
2484
|
}
|
|
2220
|
-
}
|
|
2221
2485
|
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2486
|
+
return {
|
|
2487
|
+
originUserTxReceipt,
|
|
2488
|
+
originMetaTxnReceipt,
|
|
2489
|
+
destinationMetaTxnReceipt,
|
|
2490
|
+
totalCompletionSeconds: 0,
|
|
2491
|
+
}
|
|
2492
|
+
} catch (error) {
|
|
2493
|
+
const errorMessage =
|
|
2494
|
+
error instanceof Error
|
|
2495
|
+
? error.message
|
|
2496
|
+
: "Unknown error occurred during transaction"
|
|
2497
|
+
logger.console.error(
|
|
2498
|
+
"[trails-sdk] Error in sendHandlerForSameChainSameToken:",
|
|
2499
|
+
error,
|
|
2500
|
+
)
|
|
2501
|
+
|
|
2502
|
+
// Track payment error
|
|
2503
|
+
trackPaymentError({
|
|
2504
|
+
error: errorMessage,
|
|
2505
|
+
userAddress: account.address,
|
|
2506
|
+
mode,
|
|
2507
|
+
fundMethod,
|
|
2508
|
+
originTokenAddress,
|
|
2509
|
+
})
|
|
2510
|
+
|
|
2511
|
+
// Call onCheckoutError callback if provided
|
|
2512
|
+
if (checkoutOnHandlers?.triggerCheckoutError) {
|
|
2513
|
+
checkoutOnHandlers.triggerCheckoutError(errorMessage)
|
|
2514
|
+
}
|
|
2515
|
+
|
|
2516
|
+
// Re-throw the error so caller can handle if needed
|
|
2517
|
+
throw error
|
|
2227
2518
|
}
|
|
2228
2519
|
},
|
|
2229
2520
|
}
|
|
@@ -2390,7 +2681,6 @@ async function _sendHandlerForSameChainDifferentToken({
|
|
|
2390
2681
|
onOriginSend,
|
|
2391
2682
|
}: {
|
|
2392
2683
|
onOriginSend?: () => void
|
|
2393
|
-
feeTokenAddress?: string | null
|
|
2394
2684
|
}): Promise<SendReturn> => {
|
|
2395
2685
|
const { hasEnoughBalance, balanceError } = await checkAccountBalance({
|
|
2396
2686
|
account,
|
|
@@ -2502,6 +2792,10 @@ async function attemptGaslessDeposit({
|
|
|
2502
2792
|
walletClient,
|
|
2503
2793
|
chain,
|
|
2504
2794
|
account,
|
|
2795
|
+
trailsClient,
|
|
2796
|
+
originRelayer,
|
|
2797
|
+
feeOptions,
|
|
2798
|
+
selectedFeeToken,
|
|
2505
2799
|
}: {
|
|
2506
2800
|
paymasterUrl?: string
|
|
2507
2801
|
depositTokenAddress: string
|
|
@@ -2511,46 +2805,75 @@ async function attemptGaslessDeposit({
|
|
|
2511
2805
|
walletClient: WalletClient
|
|
2512
2806
|
chain: Chain
|
|
2513
2807
|
account: Account
|
|
2808
|
+
trailsClient: TrailsAPIClient
|
|
2514
2809
|
originRelayer: Relayer.Standard.Rpc.RpcRelayer
|
|
2515
2810
|
feeOptions: any
|
|
2516
|
-
|
|
2811
|
+
selectedFeeToken?: any
|
|
2517
2812
|
}): Promise<TransactionReceipt | null> {
|
|
2518
2813
|
let originUserTxReceipt: TransactionReceipt | null = null
|
|
2519
2814
|
const originChainId = chain.id
|
|
2520
|
-
|
|
2815
|
+
|
|
2816
|
+
logger.console.log(
|
|
2817
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] attemptGaslessDeposit called with:",
|
|
2818
|
+
{
|
|
2819
|
+
originChainId,
|
|
2820
|
+
depositTokenAddress,
|
|
2821
|
+
depositTokenAmount,
|
|
2822
|
+
depositRecipient,
|
|
2823
|
+
hasFeeOptions: !!feeOptions,
|
|
2824
|
+
feeOptionsLength: feeOptions?.feeOptions?.length,
|
|
2825
|
+
selectedFeeToken,
|
|
2826
|
+
hasSelectedFeeToken: !!selectedFeeToken,
|
|
2827
|
+
paymasterUrl,
|
|
2828
|
+
},
|
|
2829
|
+
)
|
|
2521
2830
|
|
|
2522
2831
|
const publicClient = createPublicClient({
|
|
2523
2832
|
chain,
|
|
2524
2833
|
transport: http(),
|
|
2525
2834
|
})
|
|
2526
2835
|
|
|
2527
|
-
const intentEntrypoint = intentEntrypoints[chain.id]
|
|
2528
|
-
logger.console.log("[trails-sdk]
|
|
2836
|
+
const intentEntrypoint = intentEntrypoints[chain.id]
|
|
2837
|
+
logger.console.log("[trails-sdk] [GASLESS-FLOW] Intent entrypoint check:", {
|
|
2838
|
+
chainId: chain.id,
|
|
2839
|
+
chainName: chain.name,
|
|
2840
|
+
intentEntrypoint,
|
|
2841
|
+
hasIntentEntrypoint: !!intentEntrypoint,
|
|
2842
|
+
availableChains: Object.keys(intentEntrypoints).map(Number),
|
|
2843
|
+
})
|
|
2844
|
+
|
|
2845
|
+
// If intent entrypoint is not available, fall back to old flow
|
|
2846
|
+
if (!intentEntrypoint) {
|
|
2847
|
+
logger.console.warn(
|
|
2848
|
+
`[trails-sdk] ⚠️ No intent entrypoint configured for chain ${chain.id} (${chain.name}). ` +
|
|
2849
|
+
`Gasless deposits with fee options are only supported on chains: ${Object.keys(intentEntrypoints).join(", ")}. ` +
|
|
2850
|
+
`Falling back to old flow (permit2/paymaster).`,
|
|
2851
|
+
)
|
|
2852
|
+
|
|
2853
|
+
let calls: Array<{
|
|
2854
|
+
to: string
|
|
2855
|
+
data: string
|
|
2856
|
+
value: string
|
|
2857
|
+
}> = []
|
|
2529
2858
|
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
}> = []
|
|
2859
|
+
if (paymasterUrl) {
|
|
2860
|
+
logger.console.log(
|
|
2861
|
+
"[trails-sdk] [GASLESS-FLOW] doing gasless with paymaster",
|
|
2862
|
+
)
|
|
2535
2863
|
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2864
|
+
// Switch to correct chain before requesting signatures
|
|
2865
|
+
logger.console.log(
|
|
2866
|
+
"[trails-sdk] [GASLESS-FLOW] Switching chain for paymaster flow",
|
|
2867
|
+
)
|
|
2868
|
+
await attemptSwitchChain({
|
|
2869
|
+
walletClient,
|
|
2870
|
+
desiredChainId: originChainId,
|
|
2871
|
+
})
|
|
2541
2872
|
|
|
2542
|
-
|
|
2543
|
-
calls = await getDepositToIntentCalls({
|
|
2873
|
+
const delegatorSmartAccount = await getDelegatorSmartAccount({
|
|
2544
2874
|
publicClient,
|
|
2545
|
-
walletClient,
|
|
2546
|
-
account,
|
|
2547
|
-
intentEntrypoint,
|
|
2548
|
-
depositTokenAddress: depositTokenAddress as `0x${string}`,
|
|
2549
|
-
depositTokenAmount: BigInt(depositTokenAmount),
|
|
2550
|
-
depositRecipient: depositRecipient as `0x${string}`,
|
|
2551
|
-
chain,
|
|
2552
2875
|
})
|
|
2553
|
-
|
|
2876
|
+
|
|
2554
2877
|
calls = await getPaymasterGaslessTransaction({
|
|
2555
2878
|
walletClient,
|
|
2556
2879
|
chain,
|
|
@@ -2559,65 +2882,54 @@ async function attemptGaslessDeposit({
|
|
|
2559
2882
|
recipient: depositRecipient as `0x${string}`,
|
|
2560
2883
|
delegatorSmartAccount,
|
|
2561
2884
|
})
|
|
2562
|
-
}
|
|
2563
|
-
|
|
2564
|
-
logger.console.log("[trails-sdk] calls", calls)
|
|
2565
|
-
|
|
2566
|
-
const txHash = await sendPaymasterGaslessTransaction({
|
|
2567
|
-
walletClient,
|
|
2568
|
-
publicClient,
|
|
2569
|
-
chain,
|
|
2570
|
-
paymasterUrl,
|
|
2571
|
-
delegatorSmartAccount,
|
|
2572
|
-
calls,
|
|
2573
|
-
})
|
|
2574
2885
|
|
|
2575
|
-
|
|
2576
|
-
onOriginSend()
|
|
2577
|
-
}
|
|
2886
|
+
logger.console.log("[trails-sdk] calls", calls)
|
|
2578
2887
|
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
const delegatorAccount = privateKeyToAccount(delegatorPrivateKey)
|
|
2588
|
-
const delegatorClient = createWalletClient({
|
|
2589
|
-
account: delegatorAccount,
|
|
2590
|
-
chain,
|
|
2591
|
-
transport: http(),
|
|
2592
|
-
})
|
|
2888
|
+
const txHash = await sendPaymasterGaslessTransaction({
|
|
2889
|
+
walletClient,
|
|
2890
|
+
publicClient,
|
|
2891
|
+
chain,
|
|
2892
|
+
paymasterUrl,
|
|
2893
|
+
delegatorSmartAccount,
|
|
2894
|
+
calls,
|
|
2895
|
+
})
|
|
2593
2896
|
|
|
2594
|
-
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
desiredChainId: originChainId,
|
|
2598
|
-
})
|
|
2897
|
+
if (onOriginSend) {
|
|
2898
|
+
onOriginSend()
|
|
2899
|
+
}
|
|
2599
2900
|
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2606
|
-
|
|
2607
|
-
|
|
2901
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
2902
|
+
hash: txHash as `0x${string}`,
|
|
2903
|
+
})
|
|
2904
|
+
logger.console.log("[trails-sdk] receipt", receipt)
|
|
2905
|
+
return receipt
|
|
2906
|
+
} else {
|
|
2907
|
+
logger.console.log(
|
|
2908
|
+
"[trails-sdk] [GASLESS-FLOW] doing gasless with sequence wallet",
|
|
2909
|
+
)
|
|
2910
|
+
const delegatorPrivateKey = generatePrivateKey()
|
|
2911
|
+
const delegatorAccount = privateKeyToAccount(delegatorPrivateKey)
|
|
2912
|
+
const delegatorClient = createWalletClient({
|
|
2913
|
+
account: delegatorAccount,
|
|
2914
|
+
chain,
|
|
2915
|
+
transport: http(),
|
|
2916
|
+
})
|
|
2608
2917
|
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
publicClient,
|
|
2918
|
+
logger.console.log("[trails-sdk] attempting to switch chain")
|
|
2919
|
+
await attemptSwitchChain({
|
|
2612
2920
|
walletClient,
|
|
2613
|
-
|
|
2614
|
-
intentEntrypoint,
|
|
2615
|
-
depositTokenAddress: depositTokenAddress as `0x${string}`,
|
|
2616
|
-
depositTokenAmount: BigInt(depositTokenAmount),
|
|
2617
|
-
depositRecipient: depositRecipient as `0x${string}`,
|
|
2618
|
-
chain,
|
|
2921
|
+
desiredChainId: originChainId,
|
|
2619
2922
|
})
|
|
2620
|
-
|
|
2923
|
+
|
|
2924
|
+
logger.console.log("[trails-sdk] creating sequence wallet")
|
|
2925
|
+
const sequenceWalletAddress = await simpleCreateSequenceWallet(
|
|
2926
|
+
delegatorAccount as any,
|
|
2927
|
+
)
|
|
2928
|
+
logger.console.log(
|
|
2929
|
+
"[trails-sdk] sequenceWalletAddress",
|
|
2930
|
+
sequenceWalletAddress,
|
|
2931
|
+
)
|
|
2932
|
+
|
|
2621
2933
|
const { signature, deadline } = await getPermitSignature({
|
|
2622
2934
|
publicClient,
|
|
2623
2935
|
walletClient,
|
|
@@ -2637,44 +2949,299 @@ async function attemptGaslessDeposit({
|
|
|
2637
2949
|
depositRecipient as `0x${string}`,
|
|
2638
2950
|
depositTokenAddress as `0x${string}`,
|
|
2639
2951
|
)
|
|
2952
|
+
|
|
2953
|
+
logger.console.log("[trails-sdk] calls", calls)
|
|
2954
|
+
|
|
2955
|
+
const sequenceTxHash = await sequenceSendTransaction(
|
|
2956
|
+
sequenceWalletAddress,
|
|
2957
|
+
delegatorClient,
|
|
2958
|
+
publicClient,
|
|
2959
|
+
calls,
|
|
2960
|
+
chain,
|
|
2961
|
+
)
|
|
2962
|
+
logger.console.log("[trails-sdk] sequenceTxHash", sequenceTxHash)
|
|
2963
|
+
if (onOriginSend) {
|
|
2964
|
+
onOriginSend()
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2967
|
+
const receipt = await publicClient.waitForTransactionReceipt({
|
|
2968
|
+
hash: sequenceTxHash as `0x${string}`,
|
|
2969
|
+
})
|
|
2970
|
+
logger.console.log("[trails-sdk] receipt", receipt)
|
|
2971
|
+
return receipt
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
|
|
2975
|
+
// NEW FLOW: Use Intent Entrypoint API with permit2 support
|
|
2976
|
+
logger.console.log(
|
|
2977
|
+
"[trails-sdk] Using Intent Entrypoint API flow with permit2 support for gasless deposit",
|
|
2978
|
+
)
|
|
2979
|
+
|
|
2980
|
+
// Switch to correct chain before requesting signatures
|
|
2981
|
+
logger.console.log(
|
|
2982
|
+
"[trails-sdk] [GASLESS-FLOW] Switching to chain before permit/intent signatures",
|
|
2983
|
+
{ originChainId },
|
|
2984
|
+
)
|
|
2985
|
+
await attemptSwitchChain({
|
|
2986
|
+
walletClient,
|
|
2987
|
+
desiredChainId: originChainId,
|
|
2988
|
+
})
|
|
2989
|
+
|
|
2990
|
+
try {
|
|
2991
|
+
const deadline = Math.floor(Date.now() / 1000) + 3600 // 1 hour from now
|
|
2992
|
+
const hasFeeOptions = Boolean(
|
|
2993
|
+
feeOptions && feeOptions.feeOptions?.length > 0,
|
|
2994
|
+
)
|
|
2995
|
+
|
|
2996
|
+
// 1. Check if we need approval - check if we have enough allowance for this transaction (deposit + fee)
|
|
2997
|
+
let requiredAmount = BigInt(depositTokenAmount)
|
|
2998
|
+
|
|
2999
|
+
// Match selectedFeeToken by tokenAddress to get the latest fee amount from feeOptions
|
|
3000
|
+
let selectedFeeOption = null
|
|
3001
|
+
if (selectedFeeToken && hasFeeOptions) {
|
|
3002
|
+
// Find matching fee option by tokenAddress to get latest amount
|
|
3003
|
+
selectedFeeOption = feeOptions.feeOptions.find(
|
|
3004
|
+
(opt: any) =>
|
|
3005
|
+
opt.tokenAddress?.toLowerCase() ===
|
|
3006
|
+
selectedFeeToken.tokenAddress?.toLowerCase(),
|
|
3007
|
+
)
|
|
3008
|
+
logger.console.log(
|
|
3009
|
+
"[trails-sdk] Matched selectedFeeToken to latest fee option:",
|
|
3010
|
+
{
|
|
3011
|
+
selectedFeeToken,
|
|
3012
|
+
matchedOption: selectedFeeOption,
|
|
3013
|
+
},
|
|
3014
|
+
)
|
|
3015
|
+
}
|
|
3016
|
+
|
|
3017
|
+
// Fallback to first fee option if no match or no selectedFeeToken
|
|
3018
|
+
if (!selectedFeeOption && hasFeeOptions) {
|
|
3019
|
+
selectedFeeOption = feeOptions.feeOptions[0]
|
|
3020
|
+
logger.console.log(
|
|
3021
|
+
"[trails-sdk] Using first fee option as fallback:",
|
|
3022
|
+
selectedFeeOption,
|
|
3023
|
+
)
|
|
3024
|
+
}
|
|
3025
|
+
|
|
3026
|
+
if (selectedFeeOption?.amount) {
|
|
3027
|
+
requiredAmount = requiredAmount + BigInt(selectedFeeOption.amount)
|
|
3028
|
+
logger.console.log("[trails-sdk] Including fee in required amount:", {
|
|
3029
|
+
depositAmount: depositTokenAmount,
|
|
3030
|
+
feeAmount: selectedFeeOption.amount,
|
|
3031
|
+
feeTokenAddress: selectedFeeOption.tokenAddress,
|
|
3032
|
+
totalRequired: requiredAmount.toString(),
|
|
3033
|
+
})
|
|
3034
|
+
}
|
|
3035
|
+
|
|
3036
|
+
const needsApproval = await getNeedsIntentEntrypointApproval({
|
|
3037
|
+
client: publicClient,
|
|
3038
|
+
token: depositTokenAddress as `0x${string}`,
|
|
3039
|
+
account: account.address,
|
|
3040
|
+
entrypoint: intentEntrypoint as `0x${string}`,
|
|
3041
|
+
amount: requiredAmount, // Check if we have enough allowance for this specific transaction
|
|
3042
|
+
})
|
|
3043
|
+
|
|
3044
|
+
logger.console.log(
|
|
3045
|
+
"[trails-sdk] [GASLESS-FLOW] Checking permit requirements",
|
|
3046
|
+
{
|
|
3047
|
+
userAddress: account.address,
|
|
3048
|
+
tokenAddress: depositTokenAddress,
|
|
3049
|
+
depositAmount: depositTokenAmount,
|
|
3050
|
+
requiredAmount: requiredAmount.toString(),
|
|
3051
|
+
intentAddress: depositRecipient,
|
|
3052
|
+
chainID: originChainId,
|
|
3053
|
+
deadline,
|
|
3054
|
+
needsApproval,
|
|
3055
|
+
},
|
|
3056
|
+
)
|
|
3057
|
+
|
|
3058
|
+
// 2. Get permit signature if approval needed
|
|
3059
|
+
let permitSignature: string | undefined
|
|
3060
|
+
let permitDeadline: number | undefined
|
|
3061
|
+
|
|
3062
|
+
if (needsApproval) {
|
|
3063
|
+
logger.console.log(
|
|
3064
|
+
"[trails-sdk] Getting permit signature for infinite approval",
|
|
3065
|
+
)
|
|
3066
|
+
|
|
3067
|
+
// Use infinite approval (maxUint256) so user doesn't need to approve again
|
|
3068
|
+
const permitAmount = maxUint256
|
|
3069
|
+
logger.console.log(
|
|
3070
|
+
"[trails-sdk] Using infinite approval for gasless deposits",
|
|
3071
|
+
{
|
|
3072
|
+
depositAmount: depositTokenAmount,
|
|
3073
|
+
permitAmount: permitAmount.toString(),
|
|
3074
|
+
},
|
|
3075
|
+
)
|
|
3076
|
+
|
|
3077
|
+
const permitSig = await getPermitSignature({
|
|
3078
|
+
publicClient,
|
|
3079
|
+
walletClient,
|
|
3080
|
+
signer: account.address,
|
|
3081
|
+
spender: intentEntrypoint as `0x${string}`,
|
|
3082
|
+
tokenAddress: depositTokenAddress as `0x${string}`,
|
|
3083
|
+
amount: permitAmount, // Infinite approval
|
|
3084
|
+
chain,
|
|
3085
|
+
deadline: BigInt(deadline),
|
|
3086
|
+
})
|
|
3087
|
+
permitSignature = permitSig.signature
|
|
3088
|
+
permitDeadline = Number(permitSig.deadline)
|
|
3089
|
+
logger.console.log(
|
|
3090
|
+
"[trails-sdk] Permit signature obtained for infinite approval",
|
|
3091
|
+
)
|
|
2640
3092
|
}
|
|
2641
3093
|
|
|
2642
|
-
|
|
2643
|
-
|
|
2644
|
-
|
|
2645
|
-
// originRelayer,
|
|
2646
|
-
// sequenceWalletAddress,
|
|
2647
|
-
// originChainId,
|
|
2648
|
-
// calls.map((call) => ({
|
|
2649
|
-
// to: call.to,
|
|
2650
|
-
// value: BigInt(call.value),
|
|
2651
|
-
// data: call.data,
|
|
2652
|
-
// gasLimit: BigInt(0),
|
|
2653
|
-
// delegateCall: false,
|
|
2654
|
-
// onlyFallback: false,
|
|
2655
|
-
// behaviorOnError: "revert",
|
|
2656
|
-
// })) as Payload.Call[],
|
|
2657
|
-
// )
|
|
2658
|
-
|
|
2659
|
-
// logger.console.log("[trails-sdk] feeOptions", feeOptions)
|
|
2660
|
-
|
|
2661
|
-
const sequenceTxHash = await sequenceSendTransaction(
|
|
2662
|
-
sequenceWalletAddress,
|
|
2663
|
-
delegatorClient,
|
|
3094
|
+
// 3. Get current nonce for the user
|
|
3095
|
+
logger.console.log("[trails-sdk] Getting user nonce")
|
|
3096
|
+
const nonce = await getUserNonce({
|
|
2664
3097
|
publicClient,
|
|
2665
|
-
|
|
2666
|
-
|
|
3098
|
+
userAddress: account.address,
|
|
3099
|
+
intentEntrypoint: intentEntrypoint as `0x${string}`,
|
|
3100
|
+
})
|
|
3101
|
+
logger.console.log("[trails-sdk] User nonce:", nonce.toString())
|
|
3102
|
+
|
|
3103
|
+
// 4. Get intent signature
|
|
3104
|
+
logger.console.log("[trails-sdk] Requesting intent signature via EIP-712")
|
|
3105
|
+
// Get fee collector address from fee options response, or use selected fee option's collector
|
|
3106
|
+
const feeCollectorAddress = (selectedFeeOption?.feeCollector ||
|
|
3107
|
+
feeOptions?.feeCollector) as `0x${string}` | undefined
|
|
3108
|
+
|
|
3109
|
+
// Validate that we have a valid fee collector address
|
|
3110
|
+
if (!feeCollectorAddress || feeCollectorAddress === zeroAddress) {
|
|
3111
|
+
throw new Error(
|
|
3112
|
+
"[trails-sdk] Fee collector address not provided by API. Cannot proceed with gasless deposit. " +
|
|
3113
|
+
"Please ensure the API is returning feeCollector in the fee options response.",
|
|
3114
|
+
)
|
|
3115
|
+
}
|
|
3116
|
+
|
|
3117
|
+
logger.console.log(
|
|
3118
|
+
"[trails-sdk] Using fee collector address:",
|
|
3119
|
+
feeCollectorAddress,
|
|
3120
|
+
)
|
|
3121
|
+
|
|
3122
|
+
const { signature: intentSignature } = await signIntent({
|
|
3123
|
+
client: walletClient,
|
|
3124
|
+
intentParams: {
|
|
3125
|
+
user: account.address,
|
|
3126
|
+
token: depositTokenAddress as `0x${string}`,
|
|
3127
|
+
amount: BigInt(depositTokenAmount),
|
|
3128
|
+
intentAddress: depositRecipient as `0x${string}`,
|
|
3129
|
+
deadline: BigInt(deadline),
|
|
3130
|
+
chainId: originChainId,
|
|
3131
|
+
contractAddress: intentEntrypoint as `0x${string}`,
|
|
3132
|
+
nonce,
|
|
3133
|
+
feeAmount: BigInt(selectedFeeOption?.amount || "0"),
|
|
3134
|
+
feeCollector: feeCollectorAddress,
|
|
3135
|
+
},
|
|
3136
|
+
})
|
|
3137
|
+
logger.console.log("[trails-sdk] Intent signature received")
|
|
3138
|
+
|
|
3139
|
+
// 5. Call the deposit endpoint with permit support and optional fee
|
|
3140
|
+
logger.console.log(
|
|
3141
|
+
"[trails-sdk] Calling getIntentEntrypointDeposit with permit enabled",
|
|
3142
|
+
{ usePermit: needsApproval, hasFee: !!feeOptions },
|
|
3143
|
+
)
|
|
3144
|
+
|
|
3145
|
+
// selectedFeeOption was already determined at the start of the try block
|
|
3146
|
+
|
|
3147
|
+
logger.console.log(
|
|
3148
|
+
"[trails-sdk] Calling getIntentEntrypointDeposit with params:",
|
|
3149
|
+
{
|
|
3150
|
+
userAddress: account.address,
|
|
3151
|
+
tokenAddress: depositTokenAddress,
|
|
3152
|
+
amount: depositTokenAmount,
|
|
3153
|
+
intentAddress: depositRecipient,
|
|
3154
|
+
chainID: originChainId,
|
|
3155
|
+
deadline,
|
|
3156
|
+
usePermit: needsApproval,
|
|
3157
|
+
hasPermitSignature: !!permitSignature,
|
|
3158
|
+
feeAmount: selectedFeeOption?.amount,
|
|
3159
|
+
feeTokenSymbol: selectedFeeOption?.tokenSymbol,
|
|
3160
|
+
selectedFeeToken,
|
|
3161
|
+
usingSelectedFeeToken: !!selectedFeeToken,
|
|
3162
|
+
},
|
|
3163
|
+
)
|
|
3164
|
+
|
|
3165
|
+
const depositDataResponse = await trailsClient.getIntentEntrypointDeposit({
|
|
3166
|
+
params: {
|
|
3167
|
+
userAddress: account.address,
|
|
3168
|
+
tokenAddress: depositTokenAddress,
|
|
3169
|
+
amount: depositTokenAmount,
|
|
3170
|
+
intentAddress: depositRecipient,
|
|
3171
|
+
chainID: originChainId,
|
|
3172
|
+
deadline,
|
|
3173
|
+
intentSignature,
|
|
3174
|
+
usePermit: needsApproval, // Use permit if approval needed
|
|
3175
|
+
permitAmount: needsApproval ? maxUint256.toString() : undefined, // Pass infinite approval amount
|
|
3176
|
+
permitSignature,
|
|
3177
|
+
permitDeadline,
|
|
3178
|
+
feeAmount: selectedFeeOption?.amount || undefined,
|
|
3179
|
+
},
|
|
3180
|
+
})
|
|
3181
|
+
|
|
3182
|
+
const depositData = depositDataResponse.result
|
|
3183
|
+
logger.console.log("[trails-sdk] Deposit data received:", {
|
|
3184
|
+
depositWalletAddress: depositData.depositWalletAddress,
|
|
3185
|
+
entrypointAddress: depositData.entrypointAddress,
|
|
3186
|
+
metaTxnId: depositData.metaTxn?.id,
|
|
3187
|
+
usedPermit2: needsApproval,
|
|
3188
|
+
})
|
|
3189
|
+
|
|
3190
|
+
// 5. Send meta transaction via relayer
|
|
3191
|
+
logger.console.log("[trails-sdk] Sending meta transaction to relayer")
|
|
3192
|
+
const feeQuoteObj = depositData.feeQuote
|
|
3193
|
+
? ({
|
|
3194
|
+
_tag: "FeeQuote",
|
|
3195
|
+
_quote: { toJSON: () => depositData.feeQuote },
|
|
3196
|
+
} as any)
|
|
3197
|
+
: undefined
|
|
3198
|
+
|
|
3199
|
+
const opHash = await relayerSendMetaTx(
|
|
3200
|
+
originRelayer,
|
|
3201
|
+
depositData.metaTxn as any,
|
|
3202
|
+
[], // No preconditions for gasless deposit
|
|
3203
|
+
feeQuoteObj,
|
|
2667
3204
|
)
|
|
2668
|
-
logger.console.log("[trails-sdk]
|
|
3205
|
+
logger.console.log("[trails-sdk] Meta transaction sent, opHash:", opHash)
|
|
3206
|
+
|
|
2669
3207
|
if (onOriginSend) {
|
|
2670
3208
|
onOriginSend()
|
|
2671
3209
|
}
|
|
2672
3210
|
|
|
2673
|
-
|
|
2674
|
-
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
3211
|
+
// 6. Wait for transaction receipt
|
|
3212
|
+
logger.console.log("[trails-sdk] Waiting for transaction receipt")
|
|
3213
|
+
// eslint-disable-next-line no-constant-condition
|
|
3214
|
+
while (true) {
|
|
3215
|
+
const receipt: any = await getMetaTxStatus(
|
|
3216
|
+
originRelayer,
|
|
3217
|
+
depositData.metaTxn.id,
|
|
3218
|
+
Number(depositData.metaTxn.chainId),
|
|
3219
|
+
)
|
|
3220
|
+
logger.console.log("[trails-sdk] Meta transaction status:", receipt)
|
|
3221
|
+
|
|
3222
|
+
if (receipt?.transactionHash) {
|
|
3223
|
+
const metaTxnReceipt = receipt.data?.receipt
|
|
3224
|
+
if (!metaTxnReceipt) {
|
|
3225
|
+
throw new Error("No meta txn receipt found")
|
|
3226
|
+
}
|
|
3227
|
+
|
|
3228
|
+
// Get the full transaction receipt
|
|
3229
|
+
const txReceipt = await publicClient.getTransactionReceipt({
|
|
3230
|
+
hash: receipt.transactionHash as `0x${string}`,
|
|
3231
|
+
})
|
|
3232
|
+
logger.console.log("[trails-sdk] Transaction receipt:", txReceipt)
|
|
3233
|
+
originUserTxReceipt = txReceipt
|
|
3234
|
+
break
|
|
3235
|
+
}
|
|
3236
|
+
|
|
3237
|
+
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
3238
|
+
}
|
|
3239
|
+
} catch (error) {
|
|
3240
|
+
logger.console.error(
|
|
3241
|
+
"[trails-sdk] Error in Intent Entrypoint gasless deposit with permit2:",
|
|
3242
|
+
error,
|
|
3243
|
+
)
|
|
3244
|
+
throw error
|
|
2678
3245
|
}
|
|
2679
3246
|
|
|
2680
3247
|
return originUserTxReceipt
|
|
@@ -2835,6 +3402,13 @@ export async function attemptNonGaslessUserDeposit({
|
|
|
2835
3402
|
: "0x0",
|
|
2836
3403
|
})
|
|
2837
3404
|
|
|
3405
|
+
// Update persistent toast before wallet interaction
|
|
3406
|
+
updatePersistentToast(
|
|
3407
|
+
"Waiting for Confirmation",
|
|
3408
|
+
"Please confirm the transaction in your wallet...",
|
|
3409
|
+
"info",
|
|
3410
|
+
)
|
|
3411
|
+
|
|
2838
3412
|
// Send the batched call via EIP-7702
|
|
2839
3413
|
const result = (await walletClient.request({
|
|
2840
3414
|
method: "wallet_sendCalls",
|
|
@@ -2881,6 +3455,14 @@ export async function attemptNonGaslessUserDeposit({
|
|
|
2881
3455
|
onOriginSend()
|
|
2882
3456
|
}
|
|
2883
3457
|
|
|
3458
|
+
// Update persistent toast after transaction sent
|
|
3459
|
+
const chainInfo = getChainInfo(originChainId)
|
|
3460
|
+
updatePersistentToast(
|
|
3461
|
+
"Transaction Submitted",
|
|
3462
|
+
`Waiting for confirmation on ${chainInfo?.name || "chain"}...`,
|
|
3463
|
+
"info",
|
|
3464
|
+
)
|
|
3465
|
+
|
|
2884
3466
|
const receipt = await publicClient.waitForTransactionReceipt({
|
|
2885
3467
|
hash: txHash as `0x${string}`,
|
|
2886
3468
|
})
|
|
@@ -2912,6 +3494,13 @@ export async function attemptNonGaslessUserDeposit({
|
|
|
2912
3494
|
logger.console.log("[trails-sdk] nativeFeeReceipt", feeReceipt)
|
|
2913
3495
|
}
|
|
2914
3496
|
|
|
3497
|
+
// Show persistent toast for checkout flow
|
|
3498
|
+
updatePersistentToast(
|
|
3499
|
+
"Payment Started",
|
|
3500
|
+
"Waiting for wallet confirmation...",
|
|
3501
|
+
"info",
|
|
3502
|
+
)
|
|
3503
|
+
|
|
2915
3504
|
const txHash = await sendOriginTransaction(
|
|
2916
3505
|
account,
|
|
2917
3506
|
walletClient,
|
|
@@ -2944,6 +3533,13 @@ export async function attemptNonGaslessUserDeposit({
|
|
|
2944
3533
|
hash: txHash,
|
|
2945
3534
|
})
|
|
2946
3535
|
|
|
3536
|
+
const chainInfo = getChainInfo(originChainId)
|
|
3537
|
+
updatePersistentToast(
|
|
3538
|
+
"Transfer Confirmed",
|
|
3539
|
+
`Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
|
|
3540
|
+
"info",
|
|
3541
|
+
)
|
|
3542
|
+
|
|
2947
3543
|
trackTransactionConfirmed({
|
|
2948
3544
|
transactionHash: txHash,
|
|
2949
3545
|
chainId: originChainId,
|
|
@@ -2989,7 +3585,8 @@ async function attemptUserDepositTx({
|
|
|
2989
3585
|
destinationTokenSymbol,
|
|
2990
3586
|
depositAmountUsd,
|
|
2991
3587
|
feeOptions,
|
|
2992
|
-
|
|
3588
|
+
trailsClient,
|
|
3589
|
+
selectedFeeToken,
|
|
2993
3590
|
}: {
|
|
2994
3591
|
originTokenAddress: string
|
|
2995
3592
|
gasless: boolean
|
|
@@ -3016,7 +3613,8 @@ async function attemptUserDepositTx({
|
|
|
3016
3613
|
destinationTokenSymbol: string
|
|
3017
3614
|
depositAmountUsd: number
|
|
3018
3615
|
feeOptions?: any
|
|
3019
|
-
|
|
3616
|
+
trailsClient: TrailsAPIClient
|
|
3617
|
+
selectedFeeToken?: any
|
|
3020
3618
|
}): Promise<TransactionReceipt | null> {
|
|
3021
3619
|
let originUserTxReceipt: TransactionReceipt | null = null
|
|
3022
3620
|
const originChainId = chain.id
|
|
@@ -3035,11 +3633,36 @@ async function attemptUserDepositTx({
|
|
|
3035
3633
|
return null
|
|
3036
3634
|
}
|
|
3037
3635
|
|
|
3038
|
-
const doGasless =
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3636
|
+
const doGasless = getDoGasless(
|
|
3637
|
+
originTokenAddress,
|
|
3638
|
+
gasless,
|
|
3639
|
+
feeOptions,
|
|
3640
|
+
selectedFeeToken,
|
|
3641
|
+
)
|
|
3642
|
+
logger.console.log(
|
|
3643
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] doGasless check results:",
|
|
3644
|
+
{
|
|
3645
|
+
doGasless,
|
|
3646
|
+
paymasterUrl,
|
|
3647
|
+
hasFeeOptions: !!feeOptions,
|
|
3648
|
+
feeOptionsCount: feeOptions?.feeOptions?.length || 0,
|
|
3649
|
+
selectedFeeToken,
|
|
3650
|
+
selectedFeeTokenSet:
|
|
3651
|
+
selectedFeeToken !== null && selectedFeeToken !== undefined,
|
|
3652
|
+
originTokenAddress,
|
|
3653
|
+
gasless,
|
|
3654
|
+
isNotNative: originTokenAddress !== zeroAddress,
|
|
3655
|
+
},
|
|
3656
|
+
)
|
|
3657
|
+
if (doGasless || paymasterUrl) {
|
|
3658
|
+
logger.console.log(
|
|
3659
|
+
"[trails-sdk] [GASLESS-FLOW] Entering gasless deposit flow",
|
|
3660
|
+
{
|
|
3661
|
+
doGasless,
|
|
3662
|
+
hasPaymasterUrl: !!paymasterUrl,
|
|
3663
|
+
paymasterUrl,
|
|
3664
|
+
},
|
|
3665
|
+
)
|
|
3043
3666
|
try {
|
|
3044
3667
|
originUserTxReceipt = await attemptGaslessDeposit({
|
|
3045
3668
|
paymasterUrl,
|
|
@@ -3050,12 +3673,14 @@ async function attemptUserDepositTx({
|
|
|
3050
3673
|
walletClient,
|
|
3051
3674
|
chain,
|
|
3052
3675
|
account,
|
|
3676
|
+
trailsClient,
|
|
3053
3677
|
originRelayer,
|
|
3054
|
-
feeOptions,
|
|
3055
|
-
|
|
3678
|
+
feeOptions: feeOptions,
|
|
3679
|
+
selectedFeeToken: selectedFeeToken,
|
|
3056
3680
|
})
|
|
3057
3681
|
} catch (error) {
|
|
3058
3682
|
logger.console.log("[trails-sdk] gassless attempt failed", error)
|
|
3683
|
+
throw error
|
|
3059
3684
|
}
|
|
3060
3685
|
}
|
|
3061
3686
|
|
|
@@ -3092,11 +3717,108 @@ async function attemptUserDepositTx({
|
|
|
3092
3717
|
export function getDoGasless(
|
|
3093
3718
|
originTokenAddress: string,
|
|
3094
3719
|
gasless: boolean,
|
|
3095
|
-
|
|
3720
|
+
feeOptions?: any,
|
|
3721
|
+
selectedFeeToken?: any,
|
|
3096
3722
|
): boolean {
|
|
3097
|
-
|
|
3098
|
-
|
|
3723
|
+
const hasFeeOptions = Boolean(feeOptions && feeOptions.feeOptions?.length > 0)
|
|
3724
|
+
|
|
3725
|
+
// Important: The UI passes selectedFeeToken in these states:
|
|
3726
|
+
// - null: User explicitly chose "Pay with native gas"
|
|
3727
|
+
// - {object}: User selected a fee token OR it was auto-selected
|
|
3728
|
+
// - undefined: Should not happen (initial state auto-selects if options exist)
|
|
3729
|
+
|
|
3730
|
+
// If selectedFeeToken is null, user explicitly chose native gas
|
|
3731
|
+
if (selectedFeeToken === null) {
|
|
3732
|
+
logger.console.log(
|
|
3733
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: User explicitly selected native gas (null)",
|
|
3734
|
+
)
|
|
3735
|
+
return false
|
|
3736
|
+
}
|
|
3737
|
+
|
|
3738
|
+
// If selectedFeeToken is undefined and no fee options, can't do gasless
|
|
3739
|
+
if (!selectedFeeToken && !hasFeeOptions) {
|
|
3740
|
+
logger.console.log(
|
|
3741
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: No fee token selected and no fee options available",
|
|
3742
|
+
)
|
|
3743
|
+
return false
|
|
3744
|
+
}
|
|
3745
|
+
|
|
3746
|
+
// If selectedFeeToken is undefined but fee options exist, use first ERC20 option
|
|
3747
|
+
let effectiveFeeToken = selectedFeeToken
|
|
3748
|
+
if (!effectiveFeeToken && hasFeeOptions) {
|
|
3749
|
+
const firstFeeOption = feeOptions.feeOptions[0]
|
|
3750
|
+
|
|
3751
|
+
// Check if first option is native gas
|
|
3752
|
+
const isFirstOptionNative =
|
|
3753
|
+
firstFeeOption?.tokenAddress === zeroAddress ||
|
|
3754
|
+
firstFeeOption?.tokenAddress?.toLowerCase() === zeroAddress ||
|
|
3755
|
+
firstFeeOption?.isNative === true
|
|
3756
|
+
|
|
3757
|
+
if (!isFirstOptionNative && firstFeeOption?.tokenAddress) {
|
|
3758
|
+
// First fee option is ERC20, use it
|
|
3759
|
+
effectiveFeeToken = firstFeeOption
|
|
3760
|
+
logger.console.log(
|
|
3761
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: No fee token selected, using first ERC20 fee option",
|
|
3762
|
+
{
|
|
3763
|
+
feeOption: effectiveFeeToken,
|
|
3764
|
+
},
|
|
3765
|
+
)
|
|
3766
|
+
} else {
|
|
3767
|
+
logger.console.log(
|
|
3768
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: First fee option is native gas, skipping gasless",
|
|
3769
|
+
)
|
|
3770
|
+
return false
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
// Check if the effective fee token is native gas
|
|
3775
|
+
const isNativeGasFee =
|
|
3776
|
+
!effectiveFeeToken ||
|
|
3777
|
+
effectiveFeeToken.tokenAddress === zeroAddress ||
|
|
3778
|
+
effectiveFeeToken.tokenAddress?.toLowerCase() === zeroAddress ||
|
|
3779
|
+
effectiveFeeToken.isNative === true
|
|
3780
|
+
|
|
3781
|
+
// Don't use gasless if origin token is native (sending ETH)
|
|
3782
|
+
if (originTokenAddress === zeroAddress) {
|
|
3783
|
+
logger.console.log(
|
|
3784
|
+
"[trails-sdk] [GASLESS-FLOW] getDoGasless: Origin token is native, skipping gasless",
|
|
3785
|
+
)
|
|
3786
|
+
return false
|
|
3787
|
+
}
|
|
3788
|
+
|
|
3789
|
+
// Don't use gasless if fee token is native
|
|
3790
|
+
if (isNativeGasFee) {
|
|
3791
|
+
logger.console.log(
|
|
3792
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: Fee token is native gas, skipping gasless",
|
|
3793
|
+
{
|
|
3794
|
+
effectiveFeeToken,
|
|
3795
|
+
},
|
|
3796
|
+
)
|
|
3797
|
+
return false
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3800
|
+
// Don't use gasless if disabled
|
|
3801
|
+
if (!gasless) {
|
|
3802
|
+
logger.console.log(
|
|
3803
|
+
"[trails-sdk] [GASLESS-FLOW] getDoGasless: Gasless disabled",
|
|
3804
|
+
)
|
|
3805
|
+
return false
|
|
3806
|
+
}
|
|
3807
|
+
|
|
3808
|
+
// All conditions met, use gasless with ERC20 fee token
|
|
3809
|
+
logger.console.log(
|
|
3810
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless decision: Using gasless",
|
|
3811
|
+
{
|
|
3812
|
+
originTokenAddress,
|
|
3813
|
+
gasless,
|
|
3814
|
+
hasFeeOptions,
|
|
3815
|
+
selectedFeeToken,
|
|
3816
|
+
effectiveFeeToken,
|
|
3817
|
+
feeOptionsCount: feeOptions?.feeOptions?.length || 0,
|
|
3818
|
+
},
|
|
3099
3819
|
)
|
|
3820
|
+
|
|
3821
|
+
return true
|
|
3100
3822
|
}
|
|
3101
3823
|
|
|
3102
3824
|
function getTransactionStateFromReceipt(
|
|
@@ -3270,6 +3992,9 @@ async function checkAccountBalance({
|
|
|
3270
3992
|
abi: erc20Abi,
|
|
3271
3993
|
functionName: "decimals",
|
|
3272
3994
|
})
|
|
3995
|
+
if (!decimals) {
|
|
3996
|
+
throw new Error("Decimals not found")
|
|
3997
|
+
}
|
|
3273
3998
|
balanceFormatted = formatUnits(balance, decimals)
|
|
3274
3999
|
requiredAmountFormatted = formatUnits(requiredAmount, decimals)
|
|
3275
4000
|
}
|
|
@@ -3335,7 +4060,7 @@ function getNeedsLifiNativeFee({
|
|
|
3335
4060
|
formatUnits(BigInt(destinationTokenAmount), destinationTokenDecimals),
|
|
3336
4061
|
)
|
|
3337
4062
|
const depositAmountFormatted = Number(
|
|
3338
|
-
formatUnits(BigInt(depositAmount),
|
|
4063
|
+
formatUnits(BigInt(depositAmount), sourceTokenDecimals),
|
|
3339
4064
|
)
|
|
3340
4065
|
logger.console.log("[trails-sdk] destinationAmount", destinationAmount)
|
|
3341
4066
|
logger.console.log(
|
|
@@ -3380,6 +4105,8 @@ export type UseQuoteProps = {
|
|
|
3380
4105
|
slippageTolerance?: string | number | null
|
|
3381
4106
|
onStatusUpdate?: ((transactionStates: TransactionState[]) => void) | null
|
|
3382
4107
|
quoteProvider?: string | null
|
|
4108
|
+
gasless?: boolean
|
|
4109
|
+
paymasterUrl?: string
|
|
3383
4110
|
}
|
|
3384
4111
|
|
|
3385
4112
|
export type SwapReturn = {
|
|
@@ -3421,6 +4148,8 @@ export type Quote = {
|
|
|
3421
4148
|
destinationTokenRate?: string
|
|
3422
4149
|
fromAmountUsdDisplay?: string
|
|
3423
4150
|
toAmountUsdDisplay?: string
|
|
4151
|
+
gasCostUsd?: number
|
|
4152
|
+
gasCostUsdDisplay?: string
|
|
3424
4153
|
}
|
|
3425
4154
|
|
|
3426
4155
|
export type UseQuoteReturn = {
|
|
@@ -3444,10 +4173,22 @@ export function useQuote({
|
|
|
3444
4173
|
slippageTolerance,
|
|
3445
4174
|
onStatusUpdate,
|
|
3446
4175
|
quoteProvider,
|
|
3447
|
-
|
|
4176
|
+
gasless,
|
|
4177
|
+
paymasterUrl,
|
|
4178
|
+
relayerEnv,
|
|
4179
|
+
nodeGatewayEnv,
|
|
4180
|
+
}: Partial<
|
|
4181
|
+
UseQuoteProps & { relayerEnv?: RelayerEnv; nodeGatewayEnv?: SequenceEnv }
|
|
4182
|
+
> = {}): UseQuoteReturn {
|
|
4183
|
+
// Set node gateway environment override for this quote session
|
|
4184
|
+
if (nodeGatewayEnv) {
|
|
4185
|
+
;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
|
|
4186
|
+
}
|
|
4187
|
+
|
|
3448
4188
|
const apiClient = useAPIClient()
|
|
4189
|
+
const trailsClient = useTrailsClient()
|
|
3449
4190
|
const { getRelayer } = useRelayers({
|
|
3450
|
-
env: getSequenceEnv() as RelayerEnv,
|
|
4191
|
+
env: relayerEnv || (getSequenceEnv() as RelayerEnv),
|
|
3451
4192
|
})
|
|
3452
4193
|
const indexerGatewayClient = useIndexerGatewayClient()
|
|
3453
4194
|
|
|
@@ -3471,6 +4212,7 @@ export function useQuote({
|
|
|
3471
4212
|
if (
|
|
3472
4213
|
!walletClient ||
|
|
3473
4214
|
!apiClient ||
|
|
4215
|
+
!trailsClient ||
|
|
3474
4216
|
!fromTokenAddress ||
|
|
3475
4217
|
!toTokenAddress ||
|
|
3476
4218
|
!swapAmount ||
|
|
@@ -3506,19 +4248,27 @@ export function useQuote({
|
|
|
3506
4248
|
// return null
|
|
3507
4249
|
// }
|
|
3508
4250
|
|
|
4251
|
+
// logger.console.log("supportedTokens", supportedTokens)
|
|
4252
|
+
|
|
3509
4253
|
const originToken = supportedTokens?.find(
|
|
3510
4254
|
(token) =>
|
|
3511
|
-
token.contractAddress ===
|
|
3512
|
-
|
|
4255
|
+
token.contractAddress?.toLowerCase() ===
|
|
4256
|
+
fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
|
|
3513
4257
|
)
|
|
3514
4258
|
const destinationToken = supportedTokens?.find(
|
|
3515
4259
|
(token) =>
|
|
3516
|
-
token.contractAddress ===
|
|
3517
|
-
|
|
4260
|
+
token.contractAddress?.toLowerCase() ===
|
|
4261
|
+
toTokenAddress?.toLowerCase() && token.chainId === toChainId,
|
|
3518
4262
|
)
|
|
3519
4263
|
|
|
3520
|
-
const sourceTokenDecimals = originToken?.decimals
|
|
3521
|
-
|
|
4264
|
+
const sourceTokenDecimals = originToken?.decimals
|
|
4265
|
+
if (!sourceTokenDecimals) {
|
|
4266
|
+
throw new Error("Source token decimals not found")
|
|
4267
|
+
}
|
|
4268
|
+
const destinationTokenDecimals = destinationToken?.decimals
|
|
4269
|
+
if (!destinationTokenDecimals) {
|
|
4270
|
+
throw new Error("Destination token decimals not found")
|
|
4271
|
+
}
|
|
3522
4272
|
const destinationTokenSymbol = destinationToken?.symbol ?? ""
|
|
3523
4273
|
const originTokenSymbol = originToken?.symbol ?? ""
|
|
3524
4274
|
|
|
@@ -3537,6 +4287,7 @@ export function useQuote({
|
|
|
3537
4287
|
destinationCalldata: toCalldata as string,
|
|
3538
4288
|
client: walletClient,
|
|
3539
4289
|
apiClient,
|
|
4290
|
+
trailsClient,
|
|
3540
4291
|
originRelayer,
|
|
3541
4292
|
destinationRelayer,
|
|
3542
4293
|
sourceTokenDecimals,
|
|
@@ -3546,6 +4297,8 @@ export function useQuote({
|
|
|
3546
4297
|
onTransactionStateChange: onStatusUpdate ?? (() => {}),
|
|
3547
4298
|
slippageTolerance: slippageTolerance?.toString(),
|
|
3548
4299
|
quoteProvider: quoteProvider,
|
|
4300
|
+
gasless: gasless ?? false,
|
|
4301
|
+
paymasterUrl: paymasterUrl,
|
|
3549
4302
|
}
|
|
3550
4303
|
|
|
3551
4304
|
logger.console.log("[trails-sdk] options", options)
|
|
@@ -3573,6 +4326,8 @@ export function useQuote({
|
|
|
3573
4326
|
prepareSendQuote.originAmountUsdDisplay ?? undefined,
|
|
3574
4327
|
toAmountUsdDisplay:
|
|
3575
4328
|
prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
|
|
4329
|
+
gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
|
|
4330
|
+
gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
|
|
3576
4331
|
}
|
|
3577
4332
|
|
|
3578
4333
|
const swap = async (): Promise<SwapReturn> => {
|
|
@@ -3645,7 +4400,7 @@ export function getFeesFromIntent(
|
|
|
3645
4400
|
toAmountUsd,
|
|
3646
4401
|
}: { tradeType: TradeType; fromAmountUsd: number; toAmountUsd: number },
|
|
3647
4402
|
): PrepareSendFees {
|
|
3648
|
-
const totalFeeAmountUsd = intent?.trailsFee?.totalFeeUSD ?? 0
|
|
4403
|
+
const totalFeeAmountUsd = intent.payloads?.trailsFee?.totalFeeUSD ?? 0
|
|
3649
4404
|
const totalFeeAmountUsdDisplay = formatUsdAmountDisplay(totalFeeAmountUsd)
|
|
3650
4405
|
|
|
3651
4406
|
logger.console.log("[trails-sdk] getFeesFromIntent", {
|
|
@@ -3657,8 +4412,8 @@ export function getFeesFromIntent(
|
|
|
3657
4412
|
})
|
|
3658
4413
|
|
|
3659
4414
|
return {
|
|
3660
|
-
feeTokenAddress: intent.trailsFee?.feeToken ?? zeroAddress,
|
|
3661
|
-
totalFeeAmount: intent.trailsFee?.totalFeeAmount ?? "0",
|
|
4415
|
+
feeTokenAddress: intent.payloads.trailsFee?.feeToken ?? zeroAddress,
|
|
4416
|
+
totalFeeAmount: intent.payloads.trailsFee?.totalFeeAmount ?? "0",
|
|
3662
4417
|
totalFeeAmountUsd: totalFeeAmountUsd.toString(),
|
|
3663
4418
|
totalFeeAmountUsdDisplay,
|
|
3664
4419
|
}
|
|
@@ -3667,20 +4422,20 @@ export function getFeesFromIntent(
|
|
|
3667
4422
|
export function getSlippageToleranceFromIntent(
|
|
3668
4423
|
intent: GetIntentCallsPayloadsReturn,
|
|
3669
4424
|
): string {
|
|
3670
|
-
return intent.quote?.maxSlippage?.toString() ?? "0"
|
|
4425
|
+
return intent.payloads.quote?.maxSlippage?.toString() ?? "0"
|
|
3671
4426
|
}
|
|
3672
4427
|
|
|
3673
4428
|
export function getPriceImpactFromIntent(
|
|
3674
4429
|
intent: GetIntentCallsPayloadsReturn,
|
|
3675
4430
|
): string {
|
|
3676
|
-
return intent?.quote?.priceImpact?.toString() ?? "0"
|
|
4431
|
+
return intent.payloads?.quote?.priceImpact?.toString() ?? "0"
|
|
3677
4432
|
}
|
|
3678
4433
|
|
|
3679
4434
|
export function getPriceImpactUsdFromIntent(
|
|
3680
4435
|
intent: GetIntentCallsPayloadsReturn,
|
|
3681
4436
|
): string {
|
|
3682
4437
|
// Temporary type assertion until API types are regenerated
|
|
3683
|
-
return (intent?.quote as any)?.priceImpactUsd?.toString() ?? "0"
|
|
4438
|
+
return (intent.payloads?.quote as any)?.priceImpactUsd?.toString() ?? "0"
|
|
3684
4439
|
}
|
|
3685
4440
|
|
|
3686
4441
|
export function getFeesFromRelaySdkQuote(quote: RelayQuote): PrepareSendFees {
|
|
@@ -3761,7 +4516,6 @@ export async function getNormalizedQuoteObject({
|
|
|
3761
4516
|
originNativeTokenPriceUsd,
|
|
3762
4517
|
quoteProvider,
|
|
3763
4518
|
noSufficientBalance,
|
|
3764
|
-
minimumNotMet,
|
|
3765
4519
|
}: {
|
|
3766
4520
|
originDepositAddress?: string
|
|
3767
4521
|
destinationDepositAddress?: string
|
|
@@ -3785,7 +4539,6 @@ export async function getNormalizedQuoteObject({
|
|
|
3785
4539
|
originNativeTokenPriceUsd?: number | null
|
|
3786
4540
|
quoteProvider?: string
|
|
3787
4541
|
noSufficientBalance?: boolean
|
|
3788
|
-
minimumNotMet?: boolean
|
|
3789
4542
|
}): Promise<PrepareSendQuote> {
|
|
3790
4543
|
if (!destinationChainId) {
|
|
3791
4544
|
throw new Error("Destination chain id is required")
|
|
@@ -3983,7 +4736,6 @@ export async function getNormalizedQuoteObject({
|
|
|
3983
4736
|
),
|
|
3984
4737
|
quoteProvider: quoteProviderInfo,
|
|
3985
4738
|
noSufficientBalance: noSufficientBalance || false,
|
|
3986
|
-
minimumNotMet: minimumNotMet || false,
|
|
3987
4739
|
}
|
|
3988
4740
|
}
|
|
3989
4741
|
|