0xtrails 0.2.4 → 0.2.6
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 +8 -0
- package/dist/aave.d.ts.map +1 -1
- package/dist/abortController.d.ts +8 -0
- package/dist/abortController.d.ts.map +1 -0
- package/dist/{ccip-BlV1Mry3.js → ccip-Xjh9d1gb.js} +7 -7
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/constants.d.ts +3 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/error.d.ts +1 -0
- package/dist/error.d.ts.map +1 -1
- package/dist/estimate.d.ts +52 -0
- package/dist/estimate.d.ts.map +1 -1
- package/dist/fees.d.ts +19 -0
- package/dist/fees.d.ts.map +1 -0
- package/dist/{index-BNWCIGfQ.js → index-BnhdZ8Ho.js} +76406 -75798
- package/dist/index.js +726 -520
- package/dist/intents.d.ts +40 -0
- package/dist/intents.d.ts.map +1 -1
- package/dist/metaTxnMonitor.d.ts +3 -3
- package/dist/metaTxnMonitor.d.ts.map +1 -1
- package/dist/metaTxns.d.ts +3 -3
- package/dist/metaTxns.d.ts.map +1 -1
- package/dist/morpho.d.ts +8 -0
- package/dist/morpho.d.ts.map +1 -1
- package/dist/prepareSend.d.ts +19 -75
- package/dist/prepareSend.d.ts.map +1 -1
- package/dist/queryParams.d.ts.map +1 -1
- package/dist/relayer.d.ts +6 -6
- package/dist/relayer.d.ts.map +1 -1
- package/dist/sequenceWallet.d.ts +2 -2
- package/dist/sequenceWallet.d.ts.map +1 -1
- package/dist/tokens.d.ts.map +1 -1
- package/dist/transactions.d.ts +4 -2
- 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/AccountIntentTransactionHistoryButton.d.ts +4 -0
- package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
- package/dist/widget/components/AccountSettings.d.ts.map +1 -1
- package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
- package/dist/widget/components/ClassicSwap.d.ts +4 -2
- package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
- package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
- package/dist/widget/components/ConnectedWallets.d.ts +4 -0
- package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
- package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
- package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
- package/dist/widget/components/Earn.d.ts +2 -2
- package/dist/widget/components/Earn.d.ts.map +1 -1
- package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
- package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
- package/dist/widget/components/FeeBreakdown.d.ts +9 -0
- package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
- package/dist/widget/components/Fund.d.ts +2 -2
- package/dist/widget/components/Fund.d.ts.map +1 -1
- package/dist/widget/components/FundMethods.d.ts.map +1 -1
- package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +13 -7
- package/dist/widget/components/FundSwap.d.ts.map +1 -0
- package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
- package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Identicon.d.ts.map +1 -1
- package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
- package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
- package/dist/widget/components/Modal.d.ts.map +1 -1
- package/dist/widget/components/Pay.d.ts +2 -2
- package/dist/widget/components/Pay.d.ts.map +1 -1
- package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
- package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
- package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +14 -36
- package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
- package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +19 -10
- package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
- package/dist/widget/components/QuoteDetails.d.ts +1 -0
- package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
- package/dist/widget/components/Receipt.d.ts.map +1 -1
- package/dist/widget/components/Receive.d.ts.map +1 -1
- package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
- package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Recipients.d.ts.map +1 -1
- package/dist/widget/components/RequiredPropsError.d.ts +8 -0
- package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
- package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
- package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
- package/dist/widget/components/Swap.d.ts +3 -2
- package/dist/widget/components/Swap.d.ts.map +1 -1
- package/dist/widget/components/SwapSettings.d.ts.map +1 -1
- package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
- package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
- package/dist/widget/components/TokenImage.d.ts +1 -0
- package/dist/widget/components/TokenImage.d.ts.map +1 -1
- package/dist/widget/components/TokenList.d.ts.map +1 -1
- package/dist/widget/components/TokenSelector.d.ts.map +1 -1
- package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
- package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
- package/dist/widget/components/Tooltip.d.ts +9 -0
- package/dist/widget/components/Tooltip.d.ts.map +1 -0
- package/dist/widget/components/UserPreferences.d.ts.map +1 -1
- package/dist/widget/components/WaasFeeOptions.d.ts +9 -0
- package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
- package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
- package/dist/widget/components/WalletConnect.d.ts.map +1 -1
- package/dist/widget/components/WalletList.d.ts.map +1 -1
- package/dist/widget/css/compiled.css +2 -0
- package/dist/widget/css/index.css +554 -0
- package/dist/widget/hooks/useBack.d.ts +1 -0
- package/dist/widget/hooks/useBack.d.ts.map +1 -1
- package/dist/widget/hooks/useCheckout.d.ts +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/useDefaultTokenSelection.d.ts +3 -3
- package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
- package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
- package/dist/widget/hooks/useQuote.d.ts +83 -0
- package/dist/widget/hooks/useQuote.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
- package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
- package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
- package/dist/widget/hooks/useSendForm.d.ts +2 -2
- package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
- package/dist/widget/index.js +2 -2
- package/dist/widget/widget.d.ts +9 -4
- package/dist/widget/widget.d.ts.map +1 -1
- package/package.json +18 -12
- package/src/aave.ts +32 -0
- package/src/abortController.ts +35 -0
- package/src/config.ts +12 -4
- package/src/constants.ts +5 -0
- package/src/error.ts +19 -1
- package/src/estimate.ts +416 -5
- package/src/fees.ts +199 -0
- package/src/intents.ts +161 -11
- package/src/metaTxnMonitor.ts +3 -3
- package/src/metaTxns.ts +3 -5
- package/src/morpho.ts +32 -0
- package/src/prepareSend.ts +714 -550
- package/src/queryParams.ts +2 -1
- package/src/relayer.ts +11 -11
- package/src/sequenceWallet.ts +2 -2
- package/src/tokens.ts +7 -1
- package/src/trails.ts +3 -3
- package/src/transactions.ts +62 -18
- package/src/wallets.ts +8 -0
- package/src/widget/compiled.css +2 -2
- package/src/widget/components/AccountActionsDropdown.tsx +3 -13
- package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
- package/src/widget/components/AccountSettings.tsx +48 -54
- package/src/widget/components/ChainFilterDropdown.tsx +24 -3
- package/src/widget/components/ClassicSwap.tsx +131 -213
- package/src/widget/components/ConnectWallet.tsx +8 -38
- package/src/widget/components/ConnectedWallets.tsx +132 -77
- package/src/widget/components/DynamicInputStyles.tsx +76 -0
- package/src/widget/components/Earn.tsx +82 -593
- package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
- package/src/widget/components/FeeBreakdown.tsx +155 -0
- package/src/widget/components/Fund.tsx +41 -108
- package/src/widget/components/FundMethods.tsx +82 -159
- package/src/widget/components/FundSwap.tsx +52 -0
- package/src/widget/components/FundingMethodSelectorButton.tsx +70 -0
- package/src/widget/components/Identicon.tsx +164 -95
- package/src/widget/components/MeshConnectExchanges.tsx +2 -15
- package/src/widget/components/Modal.tsx +0 -8
- package/src/widget/components/Pay.tsx +214 -237
- package/src/widget/components/PercentageMaxButtons.tsx +77 -0
- package/src/widget/components/PoolDeposit.tsx +569 -0
- package/src/widget/components/PoolWithdraw.tsx +884 -0
- package/src/widget/components/PriceImpactWarning.tsx +1 -1
- package/src/widget/components/QuoteDetails.tsx +43 -12
- package/src/widget/components/Receipt.tsx +16 -2
- package/src/widget/components/Receive.tsx +0 -2
- package/src/widget/components/RecipientSelectorButton.tsx +44 -0
- package/src/widget/components/Recipients.tsx +63 -157
- package/src/widget/components/RequiredPropsError.tsx +33 -0
- package/src/widget/components/ScreenHeader.tsx +62 -34
- package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
- package/src/widget/components/Swap.tsx +4 -45
- package/src/widget/components/SwapSettings.tsx +2 -14
- package/src/widget/components/ThemeProvider.tsx +2 -1
- package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
- package/src/widget/components/TokenImage.tsx +22 -5
- package/src/widget/components/TokenList.tsx +0 -1
- package/src/widget/components/TokenSelector.tsx +63 -53
- package/src/widget/components/TokenSelectorButton.tsx +98 -0
- package/src/widget/components/Tooltip.tsx +51 -0
- package/src/widget/components/TransferPendingVertical.tsx +1 -1
- package/src/widget/components/UserPreferences.tsx +6 -24
- package/src/widget/components/WaasFeeOptions.tsx +450 -0
- package/src/widget/components/WalletConfirmation.tsx +76 -14
- package/src/widget/components/WalletConnect.tsx +93 -29
- package/src/widget/components/WalletList.tsx +4 -2
- package/src/widget/hooks/useBack.tsx +2 -0
- package/src/widget/hooks/useCheckout.ts +36 -20
- package/src/widget/hooks/useCurrentScreen.tsx +1 -0
- package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
- package/src/widget/hooks/usePayMessage.tsx +86 -11
- package/src/widget/hooks/useQuote.ts +413 -0
- package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
- package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
- package/src/widget/hooks/useSendForm.ts +32 -6
- package/src/widget/index.css +27 -0
- package/src/widget/widget.tsx +326 -283
- package/dist/widget/components/FundSendForm.d.ts.map +0 -1
- package/dist/widget/components/PaySendForm.d.ts.map +0 -1
- package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
- package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
- package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
- package/src/widget/components/FundSendForm.tsx +0 -903
- package/src/widget/components/PaySendForm.tsx +0 -869
- package/src/widget/components/SimpleSwap.tsx +0 -983
- package/src/widget/hooks/useSwapSettings.tsx +0 -100
package/src/prepareSend.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
GetIntentCallsPayloadParams,
|
|
3
|
-
GetIntentCallsPayloadsReturn,
|
|
4
3
|
IntentPrecondition,
|
|
5
4
|
} from "@0xsequence/trails-api"
|
|
5
|
+
import type { GetIntentCallsPayloadsReturn } from "./intents.js"
|
|
6
6
|
import type { TrailsAPIClient } from "@0xsequence/trails-api"
|
|
7
|
-
import {
|
|
7
|
+
import type { Relayer } from "@0xsequence/relayer"
|
|
8
8
|
import type {
|
|
9
9
|
Account,
|
|
10
10
|
Chain,
|
|
@@ -12,6 +12,8 @@ import type {
|
|
|
12
12
|
TransactionReceipt,
|
|
13
13
|
WalletClient,
|
|
14
14
|
} from "viem"
|
|
15
|
+
import { abortControllerRegistry } from "./abortController.js"
|
|
16
|
+
import { extractTrailsFeeBreakdown, type TrailsFeeBreakdown } from "./fees.js"
|
|
15
17
|
import {
|
|
16
18
|
createPublicClient,
|
|
17
19
|
createWalletClient,
|
|
@@ -34,7 +36,6 @@ import {
|
|
|
34
36
|
trackRelayerCallStarted,
|
|
35
37
|
trackTransactionConfirmed,
|
|
36
38
|
} from "./analytics.js"
|
|
37
|
-
import { useAPIClient } from "./apiClient.js"
|
|
38
39
|
import type { Attestation } from "./cctp.js"
|
|
39
40
|
import {
|
|
40
41
|
approveERC20,
|
|
@@ -50,19 +51,20 @@ import {
|
|
|
50
51
|
import { queueCCTPTransfer } from "./cctpqueue.js"
|
|
51
52
|
import { getChainInfo, getTestnetChainInfo } from "./chains.js"
|
|
52
53
|
import { attemptSwitchChain } from "./chainSwitch.js"
|
|
53
|
-
import {
|
|
54
|
-
getSequenceEnv,
|
|
55
|
-
getSlippageTolerance,
|
|
56
|
-
type SequenceEnv,
|
|
57
|
-
} from "./config.js"
|
|
54
|
+
import { getSlippageTolerance } from "./config.js"
|
|
58
55
|
import { TRAILS_INTENT_ENTRYPOINT_ADDRESS } from "./constants.js"
|
|
59
56
|
import {
|
|
60
57
|
decodeGuestModuleEvents,
|
|
61
58
|
decodeTrailsTokenSweeperEvents,
|
|
62
59
|
} from "./decoders.js"
|
|
63
60
|
import { getERC20TransferData } from "./encoders.js"
|
|
64
|
-
import {
|
|
65
|
-
import {
|
|
61
|
+
import { InsufficientBalanceError } from "./error.js"
|
|
62
|
+
import {
|
|
63
|
+
estimateGasCost,
|
|
64
|
+
estimateGasCostUsd,
|
|
65
|
+
estimateGasLimit,
|
|
66
|
+
DEFAULT_MIN_GASLIMIT,
|
|
67
|
+
} from "./estimate.js"
|
|
66
68
|
import { getExplorerUrl } from "./explorer.js"
|
|
67
69
|
import {
|
|
68
70
|
getNeedsIntentEntrypointApproval,
|
|
@@ -71,8 +73,9 @@ import {
|
|
|
71
73
|
signIntent,
|
|
72
74
|
} from "./gasless.js"
|
|
73
75
|
import { getIntentEntrypointFeeOptions } from "./intentEntrypoint.js"
|
|
74
|
-
import { useIndexerGatewayClient } from "./indexerClient.js"
|
|
75
76
|
import {
|
|
77
|
+
buildSameChainTransactionParams,
|
|
78
|
+
buildCrossChainDepositParams,
|
|
76
79
|
commitIntentConfig,
|
|
77
80
|
getIntentCallsPayloads as getIntentCallsPayloadsFromIntents,
|
|
78
81
|
sendOriginTransaction,
|
|
@@ -84,8 +87,7 @@ import { relayerSendMetaTx } from "./metaTxns.js"
|
|
|
84
87
|
import { findFirstPreconditionForChainId } from "./preconditions.js"
|
|
85
88
|
import { calcAmountUsdPrice, getTokenPrice } from "./prices.js"
|
|
86
89
|
import { getQueryParam } from "./queryParams.js"
|
|
87
|
-
import type { MetaTxnReceipt
|
|
88
|
-
import { useRelayers } from "./relayer.js"
|
|
90
|
+
import type { MetaTxnReceipt } from "./relayer.js"
|
|
89
91
|
import {
|
|
90
92
|
executeSimpleRelayTransaction,
|
|
91
93
|
getRelaySDKQuote,
|
|
@@ -103,13 +105,8 @@ import {
|
|
|
103
105
|
formatAmountDisplay,
|
|
104
106
|
formatRawAmount,
|
|
105
107
|
formatUsdAmountDisplay,
|
|
106
|
-
getTokenBalancesWithPrices,
|
|
107
108
|
} from "./tokenBalances.js"
|
|
108
|
-
import {
|
|
109
|
-
getTokenInfo,
|
|
110
|
-
useSupportedTokens,
|
|
111
|
-
type SupportedToken,
|
|
112
|
-
} from "./tokens.js"
|
|
109
|
+
import { getTokenInfo, type SupportedToken } from "./tokens.js"
|
|
113
110
|
import {
|
|
114
111
|
TRAILS_ROUTER_PLACEHOLDER_AMOUNT,
|
|
115
112
|
wrapCalldataWithTrailsRouterIfNeeded,
|
|
@@ -122,16 +119,27 @@ import { getAccountTransactionHistory, getTxTimeDiff } from "./transactions.js"
|
|
|
122
119
|
import { requestWithTimeout } from "./utils.js"
|
|
123
120
|
import type { CheckoutOnHandlers } from "./widget/hooks/useCheckout.js"
|
|
124
121
|
import { logger } from "./logger.js"
|
|
122
|
+
|
|
123
|
+
// Polling intervals for different operations
|
|
124
|
+
const POLLING_INTERVALS = {
|
|
125
|
+
// Consistent interval for transaction history polling (3 seconds)
|
|
126
|
+
TRANSACTION_HISTORY: 3000,
|
|
127
|
+
// Consistent interval for meta transaction polling (2 seconds)
|
|
128
|
+
META_TRANSACTION: 2000,
|
|
129
|
+
// Consistent interval for CCTP queue polling (5 seconds)
|
|
130
|
+
CCTP_QUEUE: 5000,
|
|
131
|
+
// Consistent interval for failure polling (2 seconds)
|
|
132
|
+
FAILURE_POLLING: 2000,
|
|
133
|
+
} as const
|
|
134
|
+
|
|
125
135
|
import { getIsCustomCalldata } from "./contractUtils.js"
|
|
126
136
|
import type { SequenceAPIClient } from "@0xsequence/api"
|
|
127
|
-
import { useTrailsClient } from "./trailsClient.js"
|
|
128
137
|
import { updatePersistentToast } from "./toast.js"
|
|
129
138
|
import {
|
|
130
139
|
getDelegatorSmartAccount,
|
|
131
140
|
getPaymasterGaslessTransaction,
|
|
132
141
|
sendPaymasterGaslessTransaction,
|
|
133
142
|
} from "./paymasterSend.js"
|
|
134
|
-
import type { RpcRelayer } from "@0xsequence/relayer"
|
|
135
143
|
|
|
136
144
|
export enum TradeType {
|
|
137
145
|
EXACT_INPUT = "EXACT_INPUT",
|
|
@@ -156,8 +164,8 @@ export type PrepareSendOptions = {
|
|
|
156
164
|
dryMode: boolean
|
|
157
165
|
apiClient: SequenceAPIClient
|
|
158
166
|
trailsClient: TrailsAPIClient
|
|
159
|
-
originRelayer:
|
|
160
|
-
destinationRelayer:
|
|
167
|
+
originRelayer: Relayer.RpcRelayer
|
|
168
|
+
destinationRelayer: Relayer.RpcRelayer
|
|
161
169
|
destinationCalldata?: string
|
|
162
170
|
onTransactionStateChange: (transactionStates: TransactionState[]) => void
|
|
163
171
|
sourceTokenPriceUsd?: number | null
|
|
@@ -174,6 +182,8 @@ export type PrepareSendOptions = {
|
|
|
174
182
|
checkoutOnHandlers?: CheckoutOnHandlers
|
|
175
183
|
refundAddress?: string
|
|
176
184
|
selectedFeeToken?: any
|
|
185
|
+
walletId?: string
|
|
186
|
+
abortSignal?: AbortSignal
|
|
177
187
|
}
|
|
178
188
|
|
|
179
189
|
export type PrepareSendFees = {
|
|
@@ -218,10 +228,13 @@ export type PrepareSendQuote = {
|
|
|
218
228
|
transactionStates: TransactionState[]
|
|
219
229
|
gasCostUsd: number
|
|
220
230
|
gasCostUsdDisplay: string
|
|
231
|
+
gasCost: string
|
|
232
|
+
gasCostFormatted: string
|
|
221
233
|
originTokenRate: string
|
|
222
234
|
destinationTokenRate: string
|
|
223
235
|
quoteProvider: QuoteProviderInfo | null
|
|
224
236
|
noSufficientBalance: boolean
|
|
237
|
+
trailsFeeBreakdown?: TrailsFeeBreakdown | null
|
|
225
238
|
}
|
|
226
239
|
|
|
227
240
|
export type PrepareSendReturn = {
|
|
@@ -353,6 +366,12 @@ function getIntentArgs(
|
|
|
353
366
|
export async function prepareSend(
|
|
354
367
|
options: PrepareSendOptions,
|
|
355
368
|
): Promise<PrepareSendReturn> {
|
|
369
|
+
// Abort all existing operations when a new quote is generated
|
|
370
|
+
logger.console.log(
|
|
371
|
+
"[trails-sdk] New quote generated - aborting all existing operations",
|
|
372
|
+
)
|
|
373
|
+
abortControllerRegistry.abortAll()
|
|
374
|
+
|
|
356
375
|
const {
|
|
357
376
|
account,
|
|
358
377
|
originTokenAddress,
|
|
@@ -385,6 +404,8 @@ export async function prepareSend(
|
|
|
385
404
|
mode,
|
|
386
405
|
checkoutOnHandlers,
|
|
387
406
|
selectedFeeToken,
|
|
407
|
+
walletId,
|
|
408
|
+
abortSignal,
|
|
388
409
|
} = options
|
|
389
410
|
let { sourceTokenPriceUsd, destinationTokenPriceUsd } = options
|
|
390
411
|
|
|
@@ -568,18 +589,20 @@ export async function prepareSend(
|
|
|
568
589
|
label: isToSameChain && isToSameToken ? "Execute" : "Transfer",
|
|
569
590
|
})
|
|
570
591
|
|
|
571
|
-
// swap (+ bridge tx)
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
592
|
+
// swap (+ bridge tx) - skip for same-chain-same-token as there's no second transaction
|
|
593
|
+
if (!(isToSameChain && isToSameToken)) {
|
|
594
|
+
transactionStates.push({
|
|
595
|
+
transactionHash: "",
|
|
596
|
+
explorerUrl: "",
|
|
597
|
+
chainId: originChainId,
|
|
598
|
+
state: "pending",
|
|
599
|
+
label: isToSameToken
|
|
600
|
+
? "Bridge"
|
|
601
|
+
: isToSameChain && !isToSameToken
|
|
602
|
+
? "Swap"
|
|
603
|
+
: "Swap & Bridge",
|
|
604
|
+
})
|
|
605
|
+
}
|
|
583
606
|
|
|
584
607
|
// additional execute tx on same chain if needed
|
|
585
608
|
if (isToSameChain && hasCustomCalldata && !isToSameToken) {
|
|
@@ -652,6 +675,14 @@ export async function prepareSend(
|
|
|
652
675
|
originNativeTokenPriceUsd,
|
|
653
676
|
slippageTolerance,
|
|
654
677
|
checkoutOnHandlers,
|
|
678
|
+
gasless,
|
|
679
|
+
paymasterUrl,
|
|
680
|
+
selectedFeeToken,
|
|
681
|
+
trailsClient,
|
|
682
|
+
originRelayer,
|
|
683
|
+
mode,
|
|
684
|
+
fundMethod,
|
|
685
|
+
abortSignal,
|
|
655
686
|
})
|
|
656
687
|
}
|
|
657
688
|
|
|
@@ -693,6 +724,8 @@ export async function prepareSend(
|
|
|
693
724
|
mode,
|
|
694
725
|
checkoutOnHandlers,
|
|
695
726
|
selectedFeeToken,
|
|
727
|
+
walletId,
|
|
728
|
+
abortSignal,
|
|
696
729
|
})
|
|
697
730
|
}
|
|
698
731
|
|
|
@@ -733,6 +766,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
733
766
|
mode,
|
|
734
767
|
checkoutOnHandlers,
|
|
735
768
|
selectedFeeToken,
|
|
769
|
+
walletId,
|
|
770
|
+
abortSignal,
|
|
736
771
|
}: {
|
|
737
772
|
mainSignerAddress: string
|
|
738
773
|
originChainId: number
|
|
@@ -753,8 +788,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
753
788
|
destinationTokenDecimals: number
|
|
754
789
|
gasless: boolean
|
|
755
790
|
paymasterUrl?: string
|
|
756
|
-
originRelayer:
|
|
757
|
-
destinationRelayer:
|
|
791
|
+
originRelayer: Relayer.RpcRelayer
|
|
792
|
+
destinationRelayer: Relayer.RpcRelayer
|
|
758
793
|
walletClient: WalletClient
|
|
759
794
|
publicClient: PublicClient
|
|
760
795
|
chain: Chain
|
|
@@ -771,6 +806,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
771
806
|
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
772
807
|
checkoutOnHandlers?: CheckoutOnHandlers
|
|
773
808
|
selectedFeeToken?: any
|
|
809
|
+
walletId?: string
|
|
810
|
+
abortSignal?: AbortSignal
|
|
774
811
|
}): Promise<PrepareSendReturn> {
|
|
775
812
|
const testnet = isTestnetDebugMode()
|
|
776
813
|
const useCctp = getUseCctp(
|
|
@@ -1120,6 +1157,42 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1120
1157
|
}
|
|
1121
1158
|
}
|
|
1122
1159
|
|
|
1160
|
+
// Estimate gas limit for the quote (same logic as in attemptNonGaslessUserDeposit)
|
|
1161
|
+
const originCallParamsForEstimate = buildCrossChainDepositParams({
|
|
1162
|
+
originTokenAddress,
|
|
1163
|
+
originIntentAddress,
|
|
1164
|
+
depositAmount: firstPreconditionMin,
|
|
1165
|
+
fee,
|
|
1166
|
+
originChainId,
|
|
1167
|
+
chain,
|
|
1168
|
+
})
|
|
1169
|
+
|
|
1170
|
+
logger.console.log(
|
|
1171
|
+
"[trails-sdk][gas-estimation] About to estimate gas limit for cross-chain quote with params:",
|
|
1172
|
+
{
|
|
1173
|
+
account: account.address,
|
|
1174
|
+
to: originCallParamsForEstimate.to,
|
|
1175
|
+
data: originCallParamsForEstimate.data,
|
|
1176
|
+
value: originCallParamsForEstimate.value,
|
|
1177
|
+
},
|
|
1178
|
+
)
|
|
1179
|
+
|
|
1180
|
+
const estimatedGasLimitForQuote = await estimateGasLimit(
|
|
1181
|
+
publicClient,
|
|
1182
|
+
{
|
|
1183
|
+
account: account.address,
|
|
1184
|
+
to: originCallParamsForEstimate.to,
|
|
1185
|
+
data: originCallParamsForEstimate.data,
|
|
1186
|
+
value: BigInt(originCallParamsForEstimate.value),
|
|
1187
|
+
},
|
|
1188
|
+
"quote",
|
|
1189
|
+
)
|
|
1190
|
+
|
|
1191
|
+
logger.console.log(
|
|
1192
|
+
"[trails-sdk][gas-estimation] Estimated gas limit for cross-chain quote:",
|
|
1193
|
+
estimatedGasLimitForQuote,
|
|
1194
|
+
)
|
|
1195
|
+
|
|
1123
1196
|
const quote = await getNormalizedQuoteObject({
|
|
1124
1197
|
originDepositAddress: originIntentAddress,
|
|
1125
1198
|
destinationDepositAddress: intent.payloads.destinationIntentAddress,
|
|
@@ -1147,6 +1220,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1147
1220
|
originNativeTokenPriceUsd,
|
|
1148
1221
|
quoteProvider: intent.payloads?.quote?.quoteProvider,
|
|
1149
1222
|
noSufficientBalance,
|
|
1223
|
+
estimatedGasLimit: estimatedGasLimitForQuote,
|
|
1224
|
+
intent,
|
|
1150
1225
|
})
|
|
1151
1226
|
|
|
1152
1227
|
// Call onCheckoutQuote callback if provided
|
|
@@ -1154,12 +1229,15 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1154
1229
|
checkoutOnHandlers.triggerCheckoutQuote(quote)
|
|
1155
1230
|
}
|
|
1156
1231
|
|
|
1157
|
-
// Get intent entrypoint fee options if supported
|
|
1232
|
+
// Get intent entrypoint fee options if supported and gasless is enabled
|
|
1233
|
+
// Note: We fetch fee options whenever gasless is true, regardless of fundMethod,
|
|
1234
|
+
// because gasless scenarios (including tests) need fee collector information
|
|
1235
|
+
// Skip gasless fee options for sequence-waas wallet
|
|
1158
1236
|
let intentEntrypointFeeOptions: any = null
|
|
1159
|
-
if (gasless &&
|
|
1237
|
+
if (gasless && walletId !== "sequence-waas") {
|
|
1160
1238
|
try {
|
|
1161
1239
|
logger.console.log(
|
|
1162
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled
|
|
1240
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled, checking intent entrypoint support for chain:",
|
|
1163
1241
|
originChainId,
|
|
1164
1242
|
)
|
|
1165
1243
|
|
|
@@ -1181,9 +1259,13 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1181
1259
|
error,
|
|
1182
1260
|
)
|
|
1183
1261
|
}
|
|
1262
|
+
} else if (walletId === "sequence-waas") {
|
|
1263
|
+
logger.console.log(
|
|
1264
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Skipping gasless fee options for sequence-waas wallet",
|
|
1265
|
+
)
|
|
1184
1266
|
} else {
|
|
1185
1267
|
logger.console.log(
|
|
1186
|
-
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled
|
|
1268
|
+
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled, skipping intent entrypoint fee options fetch",
|
|
1187
1269
|
)
|
|
1188
1270
|
}
|
|
1189
1271
|
|
|
@@ -1349,6 +1431,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1349
1431
|
feeOptions: intentEntrypointFeeOptions,
|
|
1350
1432
|
trailsClient,
|
|
1351
1433
|
selectedFeeToken: effectiveSelectedFeeToken,
|
|
1434
|
+
walletId,
|
|
1435
|
+
abortSignal,
|
|
1352
1436
|
})
|
|
1353
1437
|
|
|
1354
1438
|
if (!originUserTxReceipt) {
|
|
@@ -1429,10 +1513,19 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1429
1513
|
|
|
1430
1514
|
const checkForDepositTx = async () => {
|
|
1431
1515
|
while (true) {
|
|
1516
|
+
// Check if we should abort
|
|
1517
|
+
if (abortSignal?.aborted) {
|
|
1518
|
+
logger.console.log(
|
|
1519
|
+
"[trails-sdk] Aborting deposit tx check due to abort signal",
|
|
1520
|
+
)
|
|
1521
|
+
return null
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1432
1524
|
try {
|
|
1433
1525
|
const response = await getAccountTransactionHistory({
|
|
1434
1526
|
chainId: originChainId,
|
|
1435
1527
|
accountAddress: originIntentAddress,
|
|
1528
|
+
abortSignal,
|
|
1436
1529
|
})
|
|
1437
1530
|
logger.console.log(
|
|
1438
1531
|
"[trails-sdk] getAccountTransactionHistory response",
|
|
@@ -1441,14 +1534,16 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1441
1534
|
if (response.transactions.length > 0) {
|
|
1442
1535
|
const tx = response.transactions[0]
|
|
1443
1536
|
if (!tx?.txnHash) {
|
|
1444
|
-
await new Promise((resolve) =>
|
|
1537
|
+
await new Promise((resolve) =>
|
|
1538
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
1539
|
+
)
|
|
1445
1540
|
continue
|
|
1446
1541
|
}
|
|
1447
1542
|
// const isReceive = tx.transfers.some(
|
|
1448
1543
|
// (transfer) => transfer.transferType === "RECEIVE",
|
|
1449
1544
|
// )
|
|
1450
1545
|
// if (!isReceive) {
|
|
1451
|
-
// await new Promise((resolve) => setTimeout(resolve,
|
|
1546
|
+
// await new Promise((resolve) => setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY))
|
|
1452
1547
|
// continue
|
|
1453
1548
|
// }
|
|
1454
1549
|
const originDepositTxReceipt =
|
|
@@ -1473,18 +1568,29 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1473
1568
|
} catch (error) {
|
|
1474
1569
|
logger.console.error("Error checking for deposit tx", error)
|
|
1475
1570
|
}
|
|
1476
|
-
await new Promise((resolve) =>
|
|
1571
|
+
await new Promise((resolve) =>
|
|
1572
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
1573
|
+
)
|
|
1477
1574
|
}
|
|
1478
1575
|
}
|
|
1479
1576
|
|
|
1480
1577
|
const _checkForDestinationDepositTx: () => Promise<TransactionReceipt | null> =
|
|
1481
1578
|
async () => {
|
|
1482
1579
|
while (true) {
|
|
1580
|
+
// Check if we should abort
|
|
1581
|
+
if (abortSignal?.aborted) {
|
|
1582
|
+
logger.console.log(
|
|
1583
|
+
"[trails-sdk] Aborting destination deposit tx check due to abort signal",
|
|
1584
|
+
)
|
|
1585
|
+
return null
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1483
1588
|
try {
|
|
1484
1589
|
const response = await getAccountTransactionHistory({
|
|
1485
1590
|
chainId: destinationChainId,
|
|
1486
1591
|
accountAddress: intent.payloads
|
|
1487
1592
|
.destinationIntentAddress as `0x${string}`,
|
|
1593
|
+
abortSignal,
|
|
1488
1594
|
})
|
|
1489
1595
|
logger.console.log(
|
|
1490
1596
|
"[trails-sdk] getAccountTransactionHistory response",
|
|
@@ -1493,7 +1599,12 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1493
1599
|
if (response.transactions.length > 0) {
|
|
1494
1600
|
const tx = response.transactions[0]
|
|
1495
1601
|
if (!tx?.txnHash) {
|
|
1496
|
-
await new Promise((resolve) =>
|
|
1602
|
+
await new Promise((resolve) =>
|
|
1603
|
+
setTimeout(
|
|
1604
|
+
resolve,
|
|
1605
|
+
POLLING_INTERVALS.TRANSACTION_HISTORY,
|
|
1606
|
+
),
|
|
1607
|
+
)
|
|
1497
1608
|
continue
|
|
1498
1609
|
}
|
|
1499
1610
|
// const isReceive = tx.transfers.some(
|
|
@@ -1520,13 +1631,15 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1520
1631
|
} catch (error) {
|
|
1521
1632
|
logger.console.error("Error checking for deposit tx", error)
|
|
1522
1633
|
}
|
|
1523
|
-
await new Promise((resolve) =>
|
|
1634
|
+
await new Promise((resolve) =>
|
|
1635
|
+
setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
|
|
1636
|
+
)
|
|
1524
1637
|
}
|
|
1525
1638
|
}
|
|
1526
1639
|
|
|
1527
1640
|
// Variables to store the waitForReceipt functions
|
|
1528
1641
|
let originMetaTxnReceiptPromise:
|
|
1529
|
-
| (() => Promise<MetaTxnReceipt | null>)
|
|
1642
|
+
| ((abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>)
|
|
1530
1643
|
| null = null
|
|
1531
1644
|
let destinationMetaTxnReceiptPromise:
|
|
1532
1645
|
| ((abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>)
|
|
@@ -1783,6 +1896,14 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1783
1896
|
if (isCctp) {
|
|
1784
1897
|
queueCctpPromise = async () => {
|
|
1785
1898
|
while (true) {
|
|
1899
|
+
// Check if we should abort
|
|
1900
|
+
if (abortSignal?.aborted) {
|
|
1901
|
+
logger.console.log(
|
|
1902
|
+
"[trails-sdk] Aborting CCTP queue due to abort signal",
|
|
1903
|
+
)
|
|
1904
|
+
return
|
|
1905
|
+
}
|
|
1906
|
+
|
|
1786
1907
|
const originMetaTxnHash = originMetaTxnReceipt?.txnHash
|
|
1787
1908
|
if (originMetaTxnHash) {
|
|
1788
1909
|
await queueCCTPTransfer({
|
|
@@ -1793,16 +1914,38 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1793
1914
|
})
|
|
1794
1915
|
break
|
|
1795
1916
|
}
|
|
1796
|
-
await new Promise((resolve) =>
|
|
1917
|
+
await new Promise((resolve) =>
|
|
1918
|
+
setTimeout(resolve, POLLING_INTERVALS.CCTP_QUEUE),
|
|
1919
|
+
)
|
|
1797
1920
|
}
|
|
1798
1921
|
}
|
|
1799
1922
|
} else {
|
|
1800
1923
|
queueCctpPromise = () => Promise.resolve()
|
|
1801
1924
|
}
|
|
1802
1925
|
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1926
|
+
// Only start polling for external deposits if needed:
|
|
1927
|
+
// - QR code or exchange funding (external deposits)
|
|
1928
|
+
// - Gasless flow (relayer-executed deposits)
|
|
1929
|
+
const needsDepositPolling =
|
|
1930
|
+
fundMethod === "qr-code" ||
|
|
1931
|
+
fundMethod === "exchange" ||
|
|
1932
|
+
gasless === true
|
|
1933
|
+
|
|
1934
|
+
if (needsDepositPolling) {
|
|
1935
|
+
logger.console.log(
|
|
1936
|
+
"[trails-sdk] Starting deposit polling for fundMethod:",
|
|
1937
|
+
fundMethod,
|
|
1938
|
+
"gasless:",
|
|
1939
|
+
gasless,
|
|
1940
|
+
)
|
|
1941
|
+
checkForDepositTx().catch((error) => {
|
|
1942
|
+
logger.console.error("Error checking for deposit tx", error)
|
|
1943
|
+
})
|
|
1944
|
+
} else {
|
|
1945
|
+
logger.console.log(
|
|
1946
|
+
"[trails-sdk] Skipping deposit polling for wallet funding with gas fees",
|
|
1947
|
+
)
|
|
1948
|
+
}
|
|
1806
1949
|
|
|
1807
1950
|
// Phase 1: Send meta transactions and queue CCTP
|
|
1808
1951
|
logger.console.log(
|
|
@@ -1827,7 +1970,8 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1827
1970
|
)
|
|
1828
1971
|
if (originMetaTxnReceiptPromise) {
|
|
1829
1972
|
try {
|
|
1830
|
-
originMetaTxnReceipt =
|
|
1973
|
+
originMetaTxnReceipt =
|
|
1974
|
+
await originMetaTxnReceiptPromise(abortSignal)
|
|
1831
1975
|
|
|
1832
1976
|
if (originMetaTxnReceipt && transactionStates[1]) {
|
|
1833
1977
|
transactionStates[1] = getTransactionStateFromReceipt(
|
|
@@ -1907,12 +2051,9 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1907
2051
|
)
|
|
1908
2052
|
if (destinationMetaTxnReceiptPromise) {
|
|
1909
2053
|
try {
|
|
1910
|
-
// Create abort controller for cancelling destination polling
|
|
1911
|
-
const abortController = new AbortController()
|
|
1912
|
-
|
|
1913
2054
|
// Race between destination receipt and failure polling
|
|
1914
2055
|
const destinationReceiptPromise = destinationMetaTxnReceiptPromise
|
|
1915
|
-
? destinationMetaTxnReceiptPromise(
|
|
2056
|
+
? destinationMetaTxnReceiptPromise(abortSignal)
|
|
1916
2057
|
: Promise.resolve(null)
|
|
1917
2058
|
|
|
1918
2059
|
const failurePollingPromise = new Promise<null>((resolve) => {
|
|
@@ -1929,11 +2070,13 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
1929
2070
|
logger.console.log(
|
|
1930
2071
|
"[trails-sdk] Aborting destination meta transaction due to previous transaction failure",
|
|
1931
2072
|
)
|
|
1932
|
-
abortController.abort()
|
|
1933
2073
|
resolve(null)
|
|
1934
2074
|
} else {
|
|
1935
|
-
// Continue polling
|
|
1936
|
-
setTimeout(
|
|
2075
|
+
// Continue polling with consistent interval
|
|
2076
|
+
setTimeout(
|
|
2077
|
+
pollForFailures,
|
|
2078
|
+
POLLING_INTERVALS.FAILURE_POLLING,
|
|
2079
|
+
)
|
|
1937
2080
|
}
|
|
1938
2081
|
}
|
|
1939
2082
|
pollForFailures()
|
|
@@ -2019,11 +2162,14 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
2019
2162
|
|
|
2020
2163
|
onTransactionStateChange(transactionStates)
|
|
2021
2164
|
} catch (error) {
|
|
2022
|
-
console.error(
|
|
2165
|
+
logger.console.error(
|
|
2166
|
+
"Error decoding destination tx events",
|
|
2167
|
+
error,
|
|
2168
|
+
)
|
|
2023
2169
|
}
|
|
2024
2170
|
}
|
|
2025
2171
|
} catch (error) {
|
|
2026
|
-
console.error(
|
|
2172
|
+
logger.console.error(
|
|
2027
2173
|
"[trails-sdk] Error waiting for destination receipt:",
|
|
2028
2174
|
error,
|
|
2029
2175
|
)
|
|
@@ -2059,6 +2205,17 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
2059
2205
|
|
|
2060
2206
|
// Track payment completion for different chain and different token
|
|
2061
2207
|
if (originUserTxReceipt && destinationMetaTxnReceipt) {
|
|
2208
|
+
// Check if any transaction failed or was refunded
|
|
2209
|
+
const hasAnyCallFailed = transactionStates.some((tx) =>
|
|
2210
|
+
tx?.decodedGuestModuleEvents?.some(
|
|
2211
|
+
(e: any) => e?.type === "CallFailed",
|
|
2212
|
+
),
|
|
2213
|
+
)
|
|
2214
|
+
const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
|
|
2215
|
+
const txStatus =
|
|
2216
|
+
!hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
|
|
2217
|
+
|
|
2218
|
+
// Always track payment completion regardless of success/failure
|
|
2062
2219
|
trackPaymentCompleted({
|
|
2063
2220
|
userAddress: account.address,
|
|
2064
2221
|
originIntentAddress,
|
|
@@ -2079,9 +2236,9 @@ async function sendHandlerForDifferentChainDifferentToken({
|
|
|
2079
2236
|
effectiveDestinationTokenAmountUsd?.toString(),
|
|
2080
2237
|
})
|
|
2081
2238
|
|
|
2082
|
-
// Call onCheckoutComplete callback
|
|
2239
|
+
// Call onCheckoutComplete callback with transaction status
|
|
2083
2240
|
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
2084
|
-
checkoutOnHandlers.triggerCheckoutComplete()
|
|
2241
|
+
checkoutOnHandlers.triggerCheckoutComplete(txStatus)
|
|
2085
2242
|
}
|
|
2086
2243
|
} else {
|
|
2087
2244
|
if (
|
|
@@ -2190,6 +2347,12 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2190
2347
|
checkoutOnHandlers,
|
|
2191
2348
|
mode,
|
|
2192
2349
|
fundMethod,
|
|
2350
|
+
gasless,
|
|
2351
|
+
paymasterUrl,
|
|
2352
|
+
selectedFeeToken,
|
|
2353
|
+
trailsClient,
|
|
2354
|
+
originRelayer,
|
|
2355
|
+
abortSignal,
|
|
2193
2356
|
}: {
|
|
2194
2357
|
originTokenAddress: string
|
|
2195
2358
|
originTokenDecimals: number
|
|
@@ -2211,6 +2374,12 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2211
2374
|
checkoutOnHandlers?: CheckoutOnHandlers
|
|
2212
2375
|
mode?: "pay" | "fund" | "earn" | "swap" | "receive"
|
|
2213
2376
|
fundMethod?: string
|
|
2377
|
+
gasless?: boolean
|
|
2378
|
+
paymasterUrl?: string
|
|
2379
|
+
selectedFeeToken?: any
|
|
2380
|
+
trailsClient: TrailsAPIClient
|
|
2381
|
+
originRelayer: Relayer.RpcRelayer
|
|
2382
|
+
abortSignal?: AbortSignal
|
|
2214
2383
|
}): Promise<PrepareSendReturn> {
|
|
2215
2384
|
logger.console.log("[trails-sdk] isToSameToken && isToSameChain")
|
|
2216
2385
|
const testnet = isTestnetDebugMode()
|
|
@@ -2237,6 +2406,44 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2237
2406
|
noSufficientBalance = true
|
|
2238
2407
|
}
|
|
2239
2408
|
|
|
2409
|
+
// Build origin call params and estimate gas limit for the quote
|
|
2410
|
+
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
2411
|
+
const originCallParamsBase = buildSameChainTransactionParams({
|
|
2412
|
+
hasCustomCalldata,
|
|
2413
|
+
recipient,
|
|
2414
|
+
effectiveOriginTokenAddress,
|
|
2415
|
+
destinationCalldata,
|
|
2416
|
+
swapAmount,
|
|
2417
|
+
effectiveOriginChainId,
|
|
2418
|
+
effectiveOriginChain,
|
|
2419
|
+
})
|
|
2420
|
+
|
|
2421
|
+
logger.console.log(
|
|
2422
|
+
"[trails-sdk][gas-estimation] About to estimate gas limit for quote with params:",
|
|
2423
|
+
{
|
|
2424
|
+
account: account.address,
|
|
2425
|
+
to: originCallParamsBase.to,
|
|
2426
|
+
data: originCallParamsBase.data,
|
|
2427
|
+
value: originCallParamsBase.value,
|
|
2428
|
+
},
|
|
2429
|
+
)
|
|
2430
|
+
|
|
2431
|
+
const estimatedGasLimitForQuote = await estimateGasLimit(
|
|
2432
|
+
effectivePublicClient,
|
|
2433
|
+
{
|
|
2434
|
+
account: account.address,
|
|
2435
|
+
to: originCallParamsBase.to,
|
|
2436
|
+
data: originCallParamsBase.data,
|
|
2437
|
+
value: BigInt(originCallParamsBase.value),
|
|
2438
|
+
},
|
|
2439
|
+
"quote",
|
|
2440
|
+
)
|
|
2441
|
+
|
|
2442
|
+
logger.console.log(
|
|
2443
|
+
"[trails-sdk][gas-estimation] Estimated gas limit for quote:",
|
|
2444
|
+
estimatedGasLimitForQuote,
|
|
2445
|
+
)
|
|
2446
|
+
|
|
2240
2447
|
const quote = await getNormalizedQuoteObject({
|
|
2241
2448
|
originDepositAddress: recipient,
|
|
2242
2449
|
destinationDepositAddress: recipient,
|
|
@@ -2255,6 +2462,8 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2255
2462
|
slippageTolerance,
|
|
2256
2463
|
quoteProvider: "",
|
|
2257
2464
|
noSufficientBalance,
|
|
2465
|
+
estimatedGasLimit: estimatedGasLimitForQuote,
|
|
2466
|
+
intent: undefined,
|
|
2258
2467
|
})
|
|
2259
2468
|
|
|
2260
2469
|
// Call onCheckoutQuote callback if provided
|
|
@@ -2290,26 +2499,33 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2290
2499
|
})
|
|
2291
2500
|
|
|
2292
2501
|
const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
|
|
2502
|
+
|
|
2503
|
+
// Build origin call params (reusing the same logic as quote estimation)
|
|
2504
|
+
const originCallParamsBase = buildSameChainTransactionParams({
|
|
2505
|
+
hasCustomCalldata,
|
|
2506
|
+
recipient,
|
|
2507
|
+
effectiveOriginTokenAddress,
|
|
2508
|
+
destinationCalldata,
|
|
2509
|
+
swapAmount,
|
|
2510
|
+
effectiveOriginChainId,
|
|
2511
|
+
effectiveOriginChain,
|
|
2512
|
+
})
|
|
2513
|
+
|
|
2514
|
+
// Estimate gas limit for consistency with actual transaction
|
|
2515
|
+
const gasLimit = await estimateGasLimit(
|
|
2516
|
+
effectivePublicClient,
|
|
2517
|
+
{
|
|
2518
|
+
account: account.address,
|
|
2519
|
+
to: originCallParamsBase.to,
|
|
2520
|
+
data: originCallParamsBase.data,
|
|
2521
|
+
value: BigInt(originCallParamsBase.value),
|
|
2522
|
+
},
|
|
2523
|
+
"send",
|
|
2524
|
+
)
|
|
2525
|
+
|
|
2293
2526
|
const originCallParams = {
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
: effectiveOriginTokenAddress === zeroAddress
|
|
2297
|
-
? recipient
|
|
2298
|
-
: effectiveOriginTokenAddress,
|
|
2299
|
-
data: hasCustomCalldata
|
|
2300
|
-
? destinationCalldata
|
|
2301
|
-
: effectiveOriginTokenAddress === zeroAddress
|
|
2302
|
-
? "0x"
|
|
2303
|
-
: getERC20TransferData({
|
|
2304
|
-
recipient,
|
|
2305
|
-
amount: BigInt(swapAmount),
|
|
2306
|
-
}),
|
|
2307
|
-
value:
|
|
2308
|
-
effectiveOriginTokenAddress === zeroAddress
|
|
2309
|
-
? BigInt(swapAmount)
|
|
2310
|
-
: "0",
|
|
2311
|
-
chainId: effectiveOriginChainId,
|
|
2312
|
-
chain: effectiveOriginChain,
|
|
2527
|
+
...originCallParamsBase,
|
|
2528
|
+
gasLimit,
|
|
2313
2529
|
}
|
|
2314
2530
|
|
|
2315
2531
|
logger.console.log("[trails-sdk] origin call params", originCallParams)
|
|
@@ -2340,105 +2556,208 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2340
2556
|
)
|
|
2341
2557
|
}
|
|
2342
2558
|
|
|
2343
|
-
|
|
2559
|
+
// For gasless transactions with custom calldata, use the gasless deposit flow
|
|
2560
|
+
if (gasless && hasCustomCalldata) {
|
|
2561
|
+
logger.console.log(
|
|
2562
|
+
"[trails-sdk] Using gasless intent entrypoint flow for same-chain transaction with custom calldata",
|
|
2563
|
+
)
|
|
2564
|
+
|
|
2565
|
+
// Fetch fee options for gasless deposit
|
|
2566
|
+
let feeOptions: any = null
|
|
2344
2567
|
try {
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2348
|
-
|
|
2349
|
-
|
|
2350
|
-
|
|
2568
|
+
feeOptions = await getIntentEntrypointFeeOptions({
|
|
2569
|
+
trailsClient,
|
|
2570
|
+
userAddress: account.address as `0x${string}`,
|
|
2571
|
+
tokenAddress: effectiveOriginTokenAddress as `0x${string}`,
|
|
2572
|
+
amount: swapAmount,
|
|
2573
|
+
intentAddress: recipient as `0x${string}`,
|
|
2574
|
+
chainId: effectiveOriginChainId,
|
|
2575
|
+
})
|
|
2576
|
+
logger.console.log(
|
|
2577
|
+
"[trails-sdk] [GASLESS-FLOW] Fetched intent entrypoint fee options for same-chain transaction:",
|
|
2578
|
+
feeOptions,
|
|
2579
|
+
)
|
|
2580
|
+
} catch (error) {
|
|
2581
|
+
logger.console.error(
|
|
2582
|
+
"[trails-sdk] [GASLESS-FLOW] Error fetching fee options for same-chain gasless transaction:",
|
|
2583
|
+
error,
|
|
2584
|
+
)
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
const receipt = await attemptGaslessDeposit({
|
|
2588
|
+
account,
|
|
2589
|
+
walletClient,
|
|
2590
|
+
chain: effectiveOriginChain,
|
|
2591
|
+
depositTokenAddress: effectiveOriginTokenAddress,
|
|
2592
|
+
depositTokenAmount: swapAmount,
|
|
2593
|
+
depositRecipient: recipient,
|
|
2594
|
+
onOriginSend: () => {}, // No-op callback
|
|
2595
|
+
feeOptions,
|
|
2596
|
+
paymasterUrl,
|
|
2597
|
+
selectedFeeToken,
|
|
2598
|
+
trailsClient,
|
|
2599
|
+
originRelayer,
|
|
2600
|
+
abortSignal,
|
|
2601
|
+
})
|
|
2602
|
+
|
|
2603
|
+
if (receipt) {
|
|
2604
|
+
originUserTxReceipt = receipt
|
|
2605
|
+
|
|
2606
|
+
// Track the confirmed transaction
|
|
2607
|
+
trackTransactionConfirmed({
|
|
2608
|
+
transactionHash: receipt.transactionHash,
|
|
2609
|
+
chainId: effectiveOriginChainId,
|
|
2610
|
+
userAddress: account.address,
|
|
2611
|
+
blockNumber: Number(receipt.blockNumber),
|
|
2612
|
+
originTokenAddress,
|
|
2613
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2351
2614
|
})
|
|
2352
2615
|
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2616
|
+
// Toast will be shown after transaction state analysis
|
|
2617
|
+
|
|
2618
|
+
// Update transaction state
|
|
2619
|
+
try {
|
|
2620
|
+
onTransactionStateChange([
|
|
2621
|
+
getTransactionStateFromReceipt(
|
|
2622
|
+
receipt,
|
|
2623
|
+
effectiveOriginChainId,
|
|
2624
|
+
transactionStates[0]?.label,
|
|
2625
|
+
),
|
|
2626
|
+
])
|
|
2627
|
+
} catch (error) {
|
|
2628
|
+
logger.console.error(
|
|
2629
|
+
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2630
|
+
error,
|
|
2631
|
+
)
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
} else {
|
|
2635
|
+
// Non-gasless flow: handle approval if needed
|
|
2636
|
+
if (hasCustomCalldata) {
|
|
2637
|
+
try {
|
|
2638
|
+
const needsApproval = await getNeedsApproval({
|
|
2639
|
+
publicClient: effectivePublicClient,
|
|
2640
|
+
token: effectiveOriginTokenAddress,
|
|
2641
|
+
account: account.address,
|
|
2357
2642
|
spender: recipient,
|
|
2358
2643
|
amount: maxUint256,
|
|
2359
|
-
chain: effectiveOriginChain,
|
|
2360
2644
|
})
|
|
2361
2645
|
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2646
|
+
if (needsApproval) {
|
|
2647
|
+
const txHash = await approveERC20({
|
|
2648
|
+
walletClient,
|
|
2649
|
+
tokenAddress: effectiveOriginTokenAddress,
|
|
2650
|
+
spender: recipient,
|
|
2651
|
+
amount: maxUint256,
|
|
2652
|
+
chain: effectiveOriginChain,
|
|
2653
|
+
})
|
|
2654
|
+
|
|
2655
|
+
logger.console.log("waiting for approve", txHash)
|
|
2656
|
+
await effectivePublicClient.waitForTransactionReceipt({
|
|
2657
|
+
hash: txHash,
|
|
2658
|
+
})
|
|
2659
|
+
logger.console.log("approve done")
|
|
2660
|
+
}
|
|
2661
|
+
} catch (error) {
|
|
2662
|
+
logger.console.error(
|
|
2663
|
+
"[trails-sdk] Error approving ERC20",
|
|
2664
|
+
error,
|
|
2665
|
+
)
|
|
2367
2666
|
}
|
|
2368
|
-
} catch (error) {
|
|
2369
|
-
logger.console.error("[trails-sdk] Error approving ERC20", error)
|
|
2370
2667
|
}
|
|
2371
|
-
}
|
|
2372
2668
|
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
|
|
2378
|
-
|
|
2669
|
+
// Show persistent toast for checkout flow
|
|
2670
|
+
updatePersistentToast(
|
|
2671
|
+
"Payment Started",
|
|
2672
|
+
"Waiting for wallet confirmation...",
|
|
2673
|
+
"info",
|
|
2674
|
+
)
|
|
2379
2675
|
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2676
|
+
logger.console.log(
|
|
2677
|
+
"[trails-sdk] origin call params",
|
|
2678
|
+
originCallParams,
|
|
2679
|
+
)
|
|
2680
|
+
const txHash = await sendOriginTransaction(
|
|
2681
|
+
account,
|
|
2682
|
+
walletClient,
|
|
2683
|
+
originCallParams as any,
|
|
2684
|
+
{
|
|
2685
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2686
|
+
},
|
|
2687
|
+
) // TODO: Add proper type
|
|
2392
2688
|
|
|
2393
|
-
|
|
2689
|
+
logger.console.log("[trails-sdk] origin tx", txHash)
|
|
2394
2690
|
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2691
|
+
if (onOriginSend) {
|
|
2692
|
+
onOriginSend()
|
|
2693
|
+
}
|
|
2398
2694
|
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
originUserTxReceipt = receipt
|
|
2695
|
+
// Wait for transaction receipt
|
|
2696
|
+
const receipt =
|
|
2697
|
+
await effectivePublicClient.waitForTransactionReceipt({
|
|
2698
|
+
hash: txHash,
|
|
2699
|
+
})
|
|
2700
|
+
logger.console.log("[trails-sdk] receipt", receipt)
|
|
2701
|
+
originUserTxReceipt = receipt
|
|
2407
2702
|
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2703
|
+
trackTransactionConfirmed({
|
|
2704
|
+
transactionHash: txHash,
|
|
2705
|
+
chainId: effectiveOriginChainId,
|
|
2706
|
+
userAddress: account.address,
|
|
2707
|
+
blockNumber: Number(receipt.blockNumber),
|
|
2708
|
+
originTokenAddress,
|
|
2709
|
+
depositTokenAmountUsd: depositAmountUsd?.toString(),
|
|
2710
|
+
})
|
|
2416
2711
|
|
|
2417
|
-
|
|
2418
|
-
const chainInfo = getChainInfo(effectiveOriginChainId)
|
|
2419
|
-
updatePersistentToast(
|
|
2420
|
-
"Transfer Confirmed",
|
|
2421
|
-
`Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
|
|
2422
|
-
"info",
|
|
2423
|
-
)
|
|
2712
|
+
// Toast will be shown after transaction state analysis
|
|
2424
2713
|
|
|
2425
|
-
|
|
2426
|
-
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
2714
|
+
try {
|
|
2715
|
+
onTransactionStateChange([
|
|
2716
|
+
getTransactionStateFromReceipt(
|
|
2717
|
+
originUserTxReceipt,
|
|
2718
|
+
effectiveOriginChainId,
|
|
2719
|
+
transactionStates[0]?.label,
|
|
2720
|
+
),
|
|
2721
|
+
])
|
|
2722
|
+
} catch (error) {
|
|
2723
|
+
logger.console.error(
|
|
2724
|
+
"[trails-sdk] Error calling onTransactionStateChange:",
|
|
2725
|
+
error,
|
|
2726
|
+
)
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
|
|
2730
|
+
// Show conditional toast based on transaction status
|
|
2731
|
+
const chainInfo = getChainInfo(effectiveOriginChainId)
|
|
2732
|
+
if (originUserTxReceipt) {
|
|
2733
|
+
if (originUserTxReceipt?.status === "success") {
|
|
2734
|
+
updatePersistentToast(
|
|
2735
|
+
"Transfer Confirmed",
|
|
2736
|
+
`Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
|
|
2737
|
+
"info",
|
|
2738
|
+
)
|
|
2739
|
+
} else {
|
|
2740
|
+
updatePersistentToast(
|
|
2741
|
+
"Transfer Failed",
|
|
2742
|
+
`Your transaction on ${chainInfo?.name || "chain"} failed`,
|
|
2743
|
+
"error",
|
|
2744
|
+
)
|
|
2745
|
+
}
|
|
2438
2746
|
}
|
|
2439
2747
|
|
|
2440
2748
|
// Track payment completion for same-chain same-token transaction
|
|
2441
2749
|
if (originUserTxReceipt && originUserTxReceipt.status === "success") {
|
|
2750
|
+
// Check if any transaction failed or was refunded
|
|
2751
|
+
const hasAnyCallFailed = transactionStates.some((tx) =>
|
|
2752
|
+
tx?.decodedGuestModuleEvents?.some(
|
|
2753
|
+
(e: any) => e?.type === "CallFailed",
|
|
2754
|
+
),
|
|
2755
|
+
)
|
|
2756
|
+
const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
|
|
2757
|
+
const txStatus =
|
|
2758
|
+
!hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
|
|
2759
|
+
|
|
2760
|
+
// Always track payment completion regardless of success/failure
|
|
2442
2761
|
trackPaymentCompleted({
|
|
2443
2762
|
userAddress: account.address,
|
|
2444
2763
|
originTxHash: originUserTxReceipt.transactionHash,
|
|
@@ -2450,9 +2769,9 @@ async function sendHandlerForSameChainSameToken({
|
|
|
2450
2769
|
destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
|
|
2451
2770
|
})
|
|
2452
2771
|
|
|
2453
|
-
// Call onCheckoutComplete callback
|
|
2772
|
+
// Call onCheckoutComplete callback with transaction status
|
|
2454
2773
|
if (checkoutOnHandlers?.triggerCheckoutComplete) {
|
|
2455
|
-
checkoutOnHandlers.triggerCheckoutComplete()
|
|
2774
|
+
checkoutOnHandlers.triggerCheckoutComplete(txStatus)
|
|
2456
2775
|
}
|
|
2457
2776
|
} else if (originUserTxReceipt) {
|
|
2458
2777
|
trackPaymentError({
|
|
@@ -2607,7 +2926,7 @@ async function _sendHandlerForSameChainDifferentToken({
|
|
|
2607
2926
|
}
|
|
2608
2927
|
}
|
|
2609
2928
|
} catch (error) {
|
|
2610
|
-
console.error("[trails-sdk] Error decoding function data:", error)
|
|
2929
|
+
logger.console.error("[trails-sdk] Error decoding function data:", error)
|
|
2611
2930
|
}
|
|
2612
2931
|
}
|
|
2613
2932
|
|
|
@@ -2660,6 +2979,7 @@ async function _sendHandlerForSameChainDifferentToken({
|
|
|
2660
2979
|
destinationChainId: originChainId,
|
|
2661
2980
|
originNativeTokenPriceUsd,
|
|
2662
2981
|
quoteProvider: "relay",
|
|
2982
|
+
intent: undefined,
|
|
2663
2983
|
})
|
|
2664
2984
|
|
|
2665
2985
|
return {
|
|
@@ -2783,6 +3103,7 @@ async function attemptGaslessDeposit({
|
|
|
2783
3103
|
originRelayer,
|
|
2784
3104
|
feeOptions,
|
|
2785
3105
|
selectedFeeToken,
|
|
3106
|
+
abortSignal,
|
|
2786
3107
|
}: {
|
|
2787
3108
|
paymasterUrl?: string
|
|
2788
3109
|
depositTokenAddress: string
|
|
@@ -2793,9 +3114,10 @@ async function attemptGaslessDeposit({
|
|
|
2793
3114
|
chain: Chain
|
|
2794
3115
|
account: Account
|
|
2795
3116
|
trailsClient: TrailsAPIClient
|
|
2796
|
-
originRelayer:
|
|
3117
|
+
originRelayer: Relayer.RpcRelayer
|
|
2797
3118
|
feeOptions: any
|
|
2798
3119
|
selectedFeeToken?: any
|
|
3120
|
+
abortSignal?: AbortSignal
|
|
2799
3121
|
}): Promise<TransactionReceipt | null> {
|
|
2800
3122
|
let originUserTxReceipt: TransactionReceipt | null = null
|
|
2801
3123
|
const originChainId = chain.id
|
|
@@ -2900,7 +3222,7 @@ async function attemptGaslessDeposit({
|
|
|
2900
3222
|
feeOptions && feeOptions.feeOptions?.length > 0,
|
|
2901
3223
|
)
|
|
2902
3224
|
|
|
2903
|
-
// 1. Check if we need approval - check if we have enough allowance for this transaction (deposit + fee)
|
|
3225
|
+
// 1. Check if we need approval - check if we have enough allowance for this transaction (deposit + fee if same token)
|
|
2904
3226
|
let requiredAmount = BigInt(depositTokenAmount)
|
|
2905
3227
|
|
|
2906
3228
|
// Match selectedFeeToken by tokenAddress to get the latest fee amount from feeOptions
|
|
@@ -2930,14 +3252,36 @@ async function attemptGaslessDeposit({
|
|
|
2930
3252
|
)
|
|
2931
3253
|
}
|
|
2932
3254
|
|
|
2933
|
-
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
3255
|
+
// Only include fee in required amount if the fee token is the same as the deposit token
|
|
3256
|
+
if (selectedFeeOption?.amount && selectedFeeOption?.tokenAddress) {
|
|
3257
|
+
const feeTokenIsSameAsDepositToken =
|
|
3258
|
+
selectedFeeOption.tokenAddress.toLowerCase() ===
|
|
3259
|
+
depositTokenAddress.toLowerCase()
|
|
3260
|
+
|
|
3261
|
+
if (feeTokenIsSameAsDepositToken) {
|
|
3262
|
+
requiredAmount = requiredAmount + BigInt(selectedFeeOption.amount)
|
|
3263
|
+
logger.console.log(
|
|
3264
|
+
"[trails-sdk] Fee token matches deposit token, including fee in required approval amount:",
|
|
3265
|
+
{
|
|
3266
|
+
depositAmount: depositTokenAmount,
|
|
3267
|
+
feeAmount: selectedFeeOption.amount,
|
|
3268
|
+
feeTokenAddress: selectedFeeOption.tokenAddress,
|
|
3269
|
+
depositTokenAddress,
|
|
3270
|
+
totalRequired: requiredAmount.toString(),
|
|
3271
|
+
},
|
|
3272
|
+
)
|
|
3273
|
+
} else {
|
|
3274
|
+
logger.console.log(
|
|
3275
|
+
"[trails-sdk] Fee token differs from deposit token, separate approval will be needed:",
|
|
3276
|
+
{
|
|
3277
|
+
depositAmount: depositTokenAmount,
|
|
3278
|
+
depositTokenAddress,
|
|
3279
|
+
feeAmount: selectedFeeOption.amount,
|
|
3280
|
+
feeTokenAddress: selectedFeeOption.tokenAddress,
|
|
3281
|
+
depositTokenRequired: requiredAmount.toString(),
|
|
3282
|
+
},
|
|
3283
|
+
)
|
|
3284
|
+
}
|
|
2941
3285
|
}
|
|
2942
3286
|
|
|
2943
3287
|
const needsApproval = await getNeedsIntentEntrypointApproval({
|
|
@@ -2962,13 +3306,14 @@ async function attemptGaslessDeposit({
|
|
|
2962
3306
|
},
|
|
2963
3307
|
)
|
|
2964
3308
|
|
|
2965
|
-
// 2. Get permit signature if approval needed
|
|
3309
|
+
// 2. Get permit signature if approval needed for deposit token
|
|
3310
|
+
// Note: Fee payment is handled by the backend/relayer, we only need permit for the deposit token
|
|
2966
3311
|
let permitSignature: string | undefined
|
|
2967
3312
|
let permitDeadline: number | undefined
|
|
2968
3313
|
|
|
2969
3314
|
if (needsApproval) {
|
|
2970
3315
|
logger.console.log(
|
|
2971
|
-
"[trails-sdk] Getting permit signature for infinite approval",
|
|
3316
|
+
"[trails-sdk] Getting permit signature for deposit token infinite approval",
|
|
2972
3317
|
)
|
|
2973
3318
|
|
|
2974
3319
|
// Use infinite approval (maxUint256) so user doesn't need to approve again
|
|
@@ -2977,6 +3322,7 @@ async function attemptGaslessDeposit({
|
|
|
2977
3322
|
"[trails-sdk] Using infinite approval for gasless deposits",
|
|
2978
3323
|
{
|
|
2979
3324
|
depositAmount: depositTokenAmount,
|
|
3325
|
+
depositTokenAddress,
|
|
2980
3326
|
permitAmount: permitAmount.toString(),
|
|
2981
3327
|
},
|
|
2982
3328
|
)
|
|
@@ -2994,7 +3340,7 @@ async function attemptGaslessDeposit({
|
|
|
2994
3340
|
permitSignature = permitSig.signature
|
|
2995
3341
|
permitDeadline = Number(permitSig.deadline)
|
|
2996
3342
|
logger.console.log(
|
|
2997
|
-
"[trails-sdk]
|
|
3343
|
+
"[trails-sdk] Deposit token permit signature obtained for infinite approval",
|
|
2998
3344
|
)
|
|
2999
3345
|
}
|
|
3000
3346
|
|
|
@@ -3119,6 +3465,14 @@ async function attemptGaslessDeposit({
|
|
|
3119
3465
|
logger.console.log("[trails-sdk] Waiting for transaction receipt")
|
|
3120
3466
|
// eslint-disable-next-line no-constant-condition
|
|
3121
3467
|
while (true) {
|
|
3468
|
+
// Check if we should abort
|
|
3469
|
+
if (abortSignal?.aborted) {
|
|
3470
|
+
logger.console.log(
|
|
3471
|
+
"[trails-sdk] Aborting gasless deposit polling due to abort signal",
|
|
3472
|
+
)
|
|
3473
|
+
return null
|
|
3474
|
+
}
|
|
3475
|
+
|
|
3122
3476
|
const receipt: any = await getMetaTxStatus(
|
|
3123
3477
|
originRelayer,
|
|
3124
3478
|
depositData.metaTxn.id,
|
|
@@ -3141,7 +3495,9 @@ async function attemptGaslessDeposit({
|
|
|
3141
3495
|
break
|
|
3142
3496
|
}
|
|
3143
3497
|
|
|
3144
|
-
await new Promise((resolve) =>
|
|
3498
|
+
await new Promise((resolve) =>
|
|
3499
|
+
setTimeout(resolve, POLLING_INTERVALS.META_TRANSACTION),
|
|
3500
|
+
)
|
|
3145
3501
|
}
|
|
3146
3502
|
} catch (error) {
|
|
3147
3503
|
logger.console.error(
|
|
@@ -3221,24 +3577,31 @@ export async function attemptNonGaslessUserDeposit({
|
|
|
3221
3577
|
|
|
3222
3578
|
logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
|
|
3223
3579
|
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
? "0x"
|
|
3232
|
-
: getERC20TransferData({
|
|
3233
|
-
recipient: originIntentAddress,
|
|
3234
|
-
amount: BigInt(firstPreconditionMin) + BigInt(fee),
|
|
3235
|
-
}),
|
|
3236
|
-
value:
|
|
3237
|
-
originTokenAddress === zeroAddress
|
|
3238
|
-
? BigInt(firstPreconditionMin) + BigInt(fee)
|
|
3239
|
-
: "0",
|
|
3240
|
-
chainId: originChainId,
|
|
3580
|
+
// Build origin call params
|
|
3581
|
+
const originCallParamsBase = buildCrossChainDepositParams({
|
|
3582
|
+
originTokenAddress,
|
|
3583
|
+
originIntentAddress,
|
|
3584
|
+
depositAmount: firstPreconditionMin,
|
|
3585
|
+
fee,
|
|
3586
|
+
originChainId,
|
|
3241
3587
|
chain,
|
|
3588
|
+
})
|
|
3589
|
+
|
|
3590
|
+
// Estimate gas limit for consistency with actual transaction
|
|
3591
|
+
const gasLimit = await estimateGasLimit(
|
|
3592
|
+
publicClient,
|
|
3593
|
+
{
|
|
3594
|
+
account: account.address,
|
|
3595
|
+
to: originCallParamsBase.to,
|
|
3596
|
+
data: originCallParamsBase.data,
|
|
3597
|
+
value: BigInt(originCallParamsBase.value),
|
|
3598
|
+
},
|
|
3599
|
+
"deposit",
|
|
3600
|
+
)
|
|
3601
|
+
|
|
3602
|
+
const originCallParams = {
|
|
3603
|
+
...originCallParamsBase,
|
|
3604
|
+
gasLimit,
|
|
3242
3605
|
}
|
|
3243
3606
|
|
|
3244
3607
|
await attemptSwitchChain({
|
|
@@ -3494,13 +3857,15 @@ async function attemptUserDepositTx({
|
|
|
3494
3857
|
feeOptions,
|
|
3495
3858
|
trailsClient,
|
|
3496
3859
|
selectedFeeToken,
|
|
3860
|
+
walletId,
|
|
3861
|
+
abortSignal,
|
|
3497
3862
|
}: {
|
|
3498
3863
|
originTokenAddress: string
|
|
3499
3864
|
gasless: boolean
|
|
3500
3865
|
paymasterUrl?: string
|
|
3501
3866
|
chain: Chain
|
|
3502
3867
|
account: Account
|
|
3503
|
-
originRelayer:
|
|
3868
|
+
originRelayer: Relayer.RpcRelayer
|
|
3504
3869
|
firstPreconditionMin: string
|
|
3505
3870
|
originIntentAddress: string
|
|
3506
3871
|
onOriginSend?: () => void
|
|
@@ -3522,6 +3887,8 @@ async function attemptUserDepositTx({
|
|
|
3522
3887
|
feeOptions?: any
|
|
3523
3888
|
trailsClient: TrailsAPIClient
|
|
3524
3889
|
selectedFeeToken?: any
|
|
3890
|
+
walletId?: string
|
|
3891
|
+
abortSignal?: AbortSignal
|
|
3525
3892
|
}): Promise<TransactionReceipt | null> {
|
|
3526
3893
|
let originUserTxReceipt: TransactionReceipt | null = null
|
|
3527
3894
|
const originChainId = chain.id
|
|
@@ -3545,6 +3912,7 @@ async function attemptUserDepositTx({
|
|
|
3545
3912
|
gasless,
|
|
3546
3913
|
feeOptions,
|
|
3547
3914
|
selectedFeeToken,
|
|
3915
|
+
walletId,
|
|
3548
3916
|
)
|
|
3549
3917
|
logger.console.log(
|
|
3550
3918
|
"[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] doGasless check results:",
|
|
@@ -3584,15 +3952,19 @@ async function attemptUserDepositTx({
|
|
|
3584
3952
|
originRelayer,
|
|
3585
3953
|
feeOptions: feeOptions,
|
|
3586
3954
|
selectedFeeToken: selectedFeeToken,
|
|
3955
|
+
abortSignal,
|
|
3587
3956
|
})
|
|
3588
3957
|
} catch (error) {
|
|
3589
3958
|
logger.console.log("[trails-sdk] gassless attempt failed", error)
|
|
3590
|
-
throw error
|
|
3959
|
+
// In strict gasless mode, re-throw error instead of falling back
|
|
3960
|
+
if (gasless) {
|
|
3961
|
+
throw error
|
|
3962
|
+
}
|
|
3591
3963
|
}
|
|
3592
3964
|
}
|
|
3593
3965
|
|
|
3594
3966
|
// If gasless attempt failed, try to send a regular transaction
|
|
3595
|
-
if (!originUserTxReceipt) {
|
|
3967
|
+
if (!originUserTxReceipt && !gasless) {
|
|
3596
3968
|
originUserTxReceipt = await attemptNonGaslessUserDeposit({
|
|
3597
3969
|
originTokenAddress,
|
|
3598
3970
|
firstPreconditionMin,
|
|
@@ -3626,7 +3998,16 @@ export function getDoGasless(
|
|
|
3626
3998
|
gasless: boolean,
|
|
3627
3999
|
feeOptions?: any,
|
|
3628
4000
|
selectedFeeToken?: any,
|
|
4001
|
+
walletId?: string,
|
|
3629
4002
|
): boolean {
|
|
4003
|
+
// Don't use gasless flow for sequence-waas wallet
|
|
4004
|
+
if (walletId === "sequence-waas") {
|
|
4005
|
+
logger.console.log(
|
|
4006
|
+
"[trails-sdk] [GASLESS-FLOW] getDoGasless: Skipping gasless flow for sequence-waas wallet",
|
|
4007
|
+
)
|
|
4008
|
+
return false
|
|
4009
|
+
}
|
|
4010
|
+
|
|
3630
4011
|
const hasFeeOptions = Boolean(feeOptions && feeOptions.feeOptions?.length > 0)
|
|
3631
4012
|
|
|
3632
4013
|
// Important: The UI passes selectedFeeToken in these states:
|
|
@@ -3763,7 +4144,7 @@ async function sendMetaTxAndWaitForReceipt({
|
|
|
3763
4144
|
feeQuote,
|
|
3764
4145
|
}: {
|
|
3765
4146
|
metaTx: MetaTxn
|
|
3766
|
-
relayer:
|
|
4147
|
+
relayer: Relayer.RpcRelayer
|
|
3767
4148
|
precondition: IntentPrecondition | null
|
|
3768
4149
|
feeQuote?: string
|
|
3769
4150
|
}): Promise<{
|
|
@@ -3807,38 +4188,84 @@ async function sendMetaTxAndWaitForReceipt({
|
|
|
3807
4188
|
waitForReceipt: async (abortSignal?: AbortSignal) => {
|
|
3808
4189
|
let originMetaTxnReceipt: MetaTxnReceipt | null = null
|
|
3809
4190
|
|
|
3810
|
-
//
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
4191
|
+
// Create a unique ID for this polling operation
|
|
4192
|
+
const pollingId = `meta-txn-${metaTx.id}-${metaTx.chainId}-${Date.now()}`
|
|
4193
|
+
|
|
4194
|
+
// Create an abort controller for this specific polling operation
|
|
4195
|
+
const pollingAbortController = new AbortController()
|
|
4196
|
+
|
|
4197
|
+
// Register this polling operation with the global registry
|
|
4198
|
+
abortControllerRegistry.register(pollingId, pollingAbortController)
|
|
4199
|
+
|
|
4200
|
+
try {
|
|
4201
|
+
// eslint-disable-next-line no-constant-condition
|
|
4202
|
+
while (true) {
|
|
4203
|
+
// Check if we should abort (either from external signal or global registry)
|
|
4204
|
+
if (abortSignal?.aborted || pollingAbortController.signal.aborted) {
|
|
4205
|
+
logger.console.log(
|
|
4206
|
+
"[trails-sdk] Aborting meta transaction polling due to abort signal",
|
|
4207
|
+
)
|
|
4208
|
+
return null
|
|
4209
|
+
}
|
|
4210
|
+
|
|
3814
4211
|
logger.console.log(
|
|
3815
|
-
"[trails-sdk]
|
|
4212
|
+
"[trails-sdk] polling status",
|
|
4213
|
+
metaTx.id as `0x${string}`,
|
|
4214
|
+
metaTx.chainId.toString(),
|
|
3816
4215
|
)
|
|
3817
|
-
|
|
3818
|
-
|
|
4216
|
+
const receipt: any = await getMetaTxStatus(
|
|
4217
|
+
relayer,
|
|
4218
|
+
metaTx.id,
|
|
4219
|
+
Number(metaTx.chainId),
|
|
4220
|
+
)
|
|
4221
|
+
logger.console.log("[trails-sdk] status", receipt)
|
|
4222
|
+
if (receipt?.transactionHash) {
|
|
4223
|
+
originMetaTxnReceipt = receipt.data?.receipt
|
|
4224
|
+
if (!originMetaTxnReceipt) {
|
|
4225
|
+
throw new Error("No meta txn receipt found")
|
|
4226
|
+
}
|
|
4227
|
+
break
|
|
4228
|
+
}
|
|
3819
4229
|
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
relayer,
|
|
3827
|
-
metaTx.id,
|
|
3828
|
-
Number(metaTx.chainId),
|
|
3829
|
-
)
|
|
3830
|
-
logger.console.log("[trails-sdk] status", receipt)
|
|
3831
|
-
if (receipt?.transactionHash) {
|
|
3832
|
-
originMetaTxnReceipt = receipt.data?.receipt
|
|
3833
|
-
if (!originMetaTxnReceipt) {
|
|
3834
|
-
throw new Error("No meta txn receipt found")
|
|
4230
|
+
// Check abort signal before waiting
|
|
4231
|
+
if (abortSignal?.aborted || pollingAbortController.signal.aborted) {
|
|
4232
|
+
logger.console.log(
|
|
4233
|
+
"[trails-sdk] Aborting meta transaction polling before delay",
|
|
4234
|
+
)
|
|
4235
|
+
return null
|
|
3835
4236
|
}
|
|
3836
|
-
|
|
4237
|
+
|
|
4238
|
+
// Use consistent delay and check abort signal during the delay
|
|
4239
|
+
await new Promise((resolve) => {
|
|
4240
|
+
const timeoutId = setTimeout(
|
|
4241
|
+
resolve,
|
|
4242
|
+
POLLING_INTERVALS.META_TRANSACTION,
|
|
4243
|
+
)
|
|
4244
|
+
|
|
4245
|
+
// Listen for abort signal during the delay
|
|
4246
|
+
const abortHandler = () => {
|
|
4247
|
+
clearTimeout(timeoutId)
|
|
4248
|
+
resolve(undefined)
|
|
4249
|
+
}
|
|
4250
|
+
|
|
4251
|
+
if (abortSignal) {
|
|
4252
|
+
abortSignal.addEventListener("abort", abortHandler, {
|
|
4253
|
+
once: true,
|
|
4254
|
+
})
|
|
4255
|
+
}
|
|
4256
|
+
pollingAbortController.signal.addEventListener(
|
|
4257
|
+
"abort",
|
|
4258
|
+
abortHandler,
|
|
4259
|
+
{ once: true },
|
|
4260
|
+
)
|
|
4261
|
+
})
|
|
3837
4262
|
}
|
|
3838
|
-
await new Promise((resolve) => setTimeout(resolve, 1000))
|
|
3839
|
-
}
|
|
3840
4263
|
|
|
3841
|
-
|
|
4264
|
+
return originMetaTxnReceipt
|
|
4265
|
+
} finally {
|
|
4266
|
+
// Always unregister when polling completes or is aborted
|
|
4267
|
+
abortControllerRegistry.unregister(pollingId)
|
|
4268
|
+
}
|
|
3842
4269
|
},
|
|
3843
4270
|
}
|
|
3844
4271
|
}
|
|
@@ -3922,7 +4349,7 @@ async function checkAccountBalance({
|
|
|
3922
4349
|
balanceError,
|
|
3923
4350
|
}
|
|
3924
4351
|
} catch (error) {
|
|
3925
|
-
console.error("[trails-sdk] Error checking account balance:", error)
|
|
4352
|
+
logger.console.error("[trails-sdk] Error checking account balance:", error)
|
|
3926
4353
|
return {
|
|
3927
4354
|
hasEnoughBalance: false,
|
|
3928
4355
|
balance: BigInt(0),
|
|
@@ -3999,329 +4426,18 @@ function getNeedsLifiNativeFee({
|
|
|
3999
4426
|
return needsNativeFee
|
|
4000
4427
|
}
|
|
4001
4428
|
|
|
4002
|
-
export
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
|
|
4006
|
-
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
|
|
4011
|
-
tradeType?: TradeType | null
|
|
4012
|
-
slippageTolerance?: string | number | null
|
|
4013
|
-
onStatusUpdate?: ((transactionStates: TransactionState[]) => void) | null
|
|
4014
|
-
quoteProvider?: string | null
|
|
4015
|
-
gasless?: boolean
|
|
4016
|
-
paymasterUrl?: string
|
|
4017
|
-
}
|
|
4018
|
-
|
|
4019
|
-
export type SwapReturn = {
|
|
4020
|
-
originTransaction: {
|
|
4021
|
-
transactionHash?: string | null
|
|
4022
|
-
explorerUrl?: string | null
|
|
4023
|
-
receipt: TransactionReceipt | MetaTxnReceipt | null
|
|
4024
|
-
}
|
|
4025
|
-
destinationTransaction: {
|
|
4026
|
-
transactionHash?: string | null
|
|
4027
|
-
explorerUrl?: string | null
|
|
4028
|
-
receipt: MetaTxnReceipt | null
|
|
4029
|
-
}
|
|
4030
|
-
totalCompletionSeconds?: number
|
|
4031
|
-
}
|
|
4032
|
-
|
|
4033
|
-
export type QuoteProviderInfo = {
|
|
4034
|
-
id: string
|
|
4035
|
-
name: string
|
|
4036
|
-
url: string
|
|
4037
|
-
}
|
|
4038
|
-
|
|
4039
|
-
export type Quote = {
|
|
4040
|
-
fromAmount: string
|
|
4041
|
-
fromAmountMin: string
|
|
4042
|
-
toAmount: string
|
|
4043
|
-
toAmountMin: string
|
|
4044
|
-
originToken: SupportedToken
|
|
4045
|
-
destinationToken: SupportedToken
|
|
4046
|
-
originChain: Chain
|
|
4047
|
-
destinationChain: Chain
|
|
4048
|
-
fees: PrepareSendFees
|
|
4049
|
-
slippageTolerance: string
|
|
4050
|
-
priceImpact: string
|
|
4051
|
-
completionEstimateSeconds: number
|
|
4052
|
-
transactionStates?: TransactionState[]
|
|
4053
|
-
originTokenRate?: string
|
|
4054
|
-
quoteProvider?: QuoteProviderInfo | null
|
|
4055
|
-
destinationTokenRate?: string
|
|
4056
|
-
fromAmountUsdDisplay?: string
|
|
4057
|
-
toAmountUsdDisplay?: string
|
|
4058
|
-
gasCostUsd?: number
|
|
4059
|
-
gasCostUsdDisplay?: string
|
|
4060
|
-
}
|
|
4061
|
-
|
|
4062
|
-
export type UseQuoteReturn = {
|
|
4063
|
-
quote: Quote | null
|
|
4064
|
-
swap: (() => Promise<SwapReturn | null>) | null
|
|
4065
|
-
isLoadingQuote: boolean
|
|
4066
|
-
quoteError: unknown
|
|
4067
|
-
refetchQuote: () => void
|
|
4068
|
-
}
|
|
4069
|
-
|
|
4070
|
-
export function useQuote({
|
|
4071
|
-
walletClient,
|
|
4072
|
-
fromTokenAddress,
|
|
4073
|
-
fromChainId,
|
|
4074
|
-
toTokenAddress,
|
|
4075
|
-
toChainId,
|
|
4076
|
-
swapAmount,
|
|
4077
|
-
tradeType,
|
|
4078
|
-
toRecipient,
|
|
4079
|
-
toCalldata,
|
|
4080
|
-
slippageTolerance,
|
|
4081
|
-
onStatusUpdate,
|
|
4082
|
-
quoteProvider,
|
|
4083
|
-
gasless,
|
|
4084
|
-
paymasterUrl,
|
|
4085
|
-
relayerEnv,
|
|
4086
|
-
nodeGatewayEnv,
|
|
4087
|
-
}: Partial<
|
|
4088
|
-
UseQuoteProps & { relayerEnv?: RelayerEnv; nodeGatewayEnv?: SequenceEnv }
|
|
4089
|
-
> = {}): UseQuoteReturn {
|
|
4090
|
-
// Set node gateway environment override for this quote session
|
|
4091
|
-
if (nodeGatewayEnv) {
|
|
4092
|
-
;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
|
|
4093
|
-
}
|
|
4094
|
-
|
|
4095
|
-
const apiClient = useAPIClient()
|
|
4096
|
-
const trailsClient = useTrailsClient()
|
|
4097
|
-
const { getRelayer } = useRelayers({
|
|
4098
|
-
env: relayerEnv || (getSequenceEnv() as RelayerEnv),
|
|
4099
|
-
})
|
|
4100
|
-
const indexerGatewayClient = useIndexerGatewayClient()
|
|
4101
|
-
|
|
4102
|
-
const { supportedTokens } = useSupportedTokens()
|
|
4103
|
-
|
|
4104
|
-
const { data, isLoading, error, refetch } = useQuery({
|
|
4105
|
-
queryKey: [
|
|
4106
|
-
"quote",
|
|
4107
|
-
fromTokenAddress,
|
|
4108
|
-
fromChainId,
|
|
4109
|
-
toTokenAddress,
|
|
4110
|
-
toChainId,
|
|
4111
|
-
swapAmount?.toString(),
|
|
4112
|
-
toRecipient,
|
|
4113
|
-
toCalldata,
|
|
4114
|
-
tradeType,
|
|
4115
|
-
slippageTolerance,
|
|
4116
|
-
quoteProvider,
|
|
4117
|
-
],
|
|
4118
|
-
queryFn: async () => {
|
|
4119
|
-
try {
|
|
4120
|
-
if (
|
|
4121
|
-
!walletClient ||
|
|
4122
|
-
!apiClient ||
|
|
4123
|
-
!trailsClient ||
|
|
4124
|
-
!fromTokenAddress ||
|
|
4125
|
-
!toTokenAddress ||
|
|
4126
|
-
!swapAmount ||
|
|
4127
|
-
!toRecipient ||
|
|
4128
|
-
!fromChainId ||
|
|
4129
|
-
!toChainId ||
|
|
4130
|
-
!indexerGatewayClient
|
|
4131
|
-
) {
|
|
4132
|
-
return null
|
|
4133
|
-
}
|
|
4134
|
-
|
|
4135
|
-
// Get token balance using async method
|
|
4136
|
-
const { balances } = await getTokenBalancesWithPrices({
|
|
4137
|
-
account: walletClient.account!.address,
|
|
4138
|
-
indexerGatewayClient,
|
|
4139
|
-
apiClient,
|
|
4140
|
-
})
|
|
4141
|
-
|
|
4142
|
-
const originTokenBalance = balances.find(
|
|
4143
|
-
(b) =>
|
|
4144
|
-
b.chainId === fromChainId &&
|
|
4145
|
-
(b.contractAddress?.toLowerCase() ===
|
|
4146
|
-
fromTokenAddress.toLowerCase() ||
|
|
4147
|
-
(!b.contractAddress && fromTokenAddress === zeroAddress)),
|
|
4148
|
-
)
|
|
4149
|
-
|
|
4150
|
-
const originTokenBalanceAmount = originTokenBalance?.balance ?? "0"
|
|
4151
|
-
const destinationRelayer = getRelayer(toChainId)
|
|
4152
|
-
const originRelayer = getRelayer(fromChainId)
|
|
4153
|
-
|
|
4154
|
-
// Note: Disable this check for now to allow fetching a quote even when the origin balance is zero
|
|
4155
|
-
// if (originTokenBalanceAmount === "0") {
|
|
4156
|
-
// return null
|
|
4157
|
-
// }
|
|
4158
|
-
|
|
4159
|
-
// logger.console.log("supportedTokens", supportedTokens)
|
|
4160
|
-
|
|
4161
|
-
const originToken = supportedTokens?.find(
|
|
4162
|
-
(token) =>
|
|
4163
|
-
token.contractAddress?.toLowerCase() ===
|
|
4164
|
-
fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
|
|
4165
|
-
)
|
|
4166
|
-
const destinationToken = supportedTokens?.find(
|
|
4167
|
-
(token) =>
|
|
4168
|
-
token.contractAddress?.toLowerCase() ===
|
|
4169
|
-
toTokenAddress?.toLowerCase() && token.chainId === toChainId,
|
|
4170
|
-
)
|
|
4171
|
-
|
|
4172
|
-
const sourceTokenDecimals = originToken?.decimals
|
|
4173
|
-
if (!sourceTokenDecimals) {
|
|
4174
|
-
logger.console.error(
|
|
4175
|
-
"[trails-sdk] [useQuote] Missing source token decimals:",
|
|
4176
|
-
{
|
|
4177
|
-
originToken,
|
|
4178
|
-
fromTokenAddress,
|
|
4179
|
-
fromChainId,
|
|
4180
|
-
},
|
|
4181
|
-
)
|
|
4182
|
-
throw new Error("Source token decimals not found")
|
|
4183
|
-
}
|
|
4184
|
-
const destinationTokenDecimals = destinationToken?.decimals
|
|
4185
|
-
if (!destinationTokenDecimals) {
|
|
4186
|
-
logger.console.error(
|
|
4187
|
-
"[trails-sdk] Missing destination token decimals:",
|
|
4188
|
-
{
|
|
4189
|
-
destinationToken,
|
|
4190
|
-
toTokenAddress,
|
|
4191
|
-
toChainId,
|
|
4192
|
-
},
|
|
4193
|
-
)
|
|
4194
|
-
throw new Error("Destination token decimals not found")
|
|
4195
|
-
}
|
|
4196
|
-
const destinationTokenSymbol = destinationToken?.symbol ?? ""
|
|
4197
|
-
const originTokenSymbol = originToken?.symbol ?? ""
|
|
4198
|
-
|
|
4199
|
-
const options = {
|
|
4200
|
-
account: walletClient.account!,
|
|
4201
|
-
originTokenAddress: fromTokenAddress,
|
|
4202
|
-
originChainId: fromChainId,
|
|
4203
|
-
originTokenBalance: originTokenBalanceAmount,
|
|
4204
|
-
destinationChainId: toChainId,
|
|
4205
|
-
recipient: toRecipient,
|
|
4206
|
-
destinationTokenAddress: toTokenAddress,
|
|
4207
|
-
swapAmount: swapAmount.toString(),
|
|
4208
|
-
tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
|
|
4209
|
-
originTokenSymbol: originTokenSymbol,
|
|
4210
|
-
destinationTokenSymbol: destinationTokenSymbol,
|
|
4211
|
-
destinationCalldata: toCalldata as string,
|
|
4212
|
-
client: walletClient,
|
|
4213
|
-
apiClient,
|
|
4214
|
-
trailsClient,
|
|
4215
|
-
originRelayer,
|
|
4216
|
-
destinationRelayer,
|
|
4217
|
-
sourceTokenDecimals,
|
|
4218
|
-
destinationTokenDecimals,
|
|
4219
|
-
fee: "0",
|
|
4220
|
-
dryMode: false,
|
|
4221
|
-
onTransactionStateChange: onStatusUpdate ?? (() => {}),
|
|
4222
|
-
slippageTolerance: slippageTolerance?.toString(),
|
|
4223
|
-
quoteProvider: quoteProvider,
|
|
4224
|
-
gasless: gasless ?? false,
|
|
4225
|
-
paymasterUrl: paymasterUrl,
|
|
4226
|
-
}
|
|
4227
|
-
|
|
4228
|
-
logger.console.log("[trails-sdk] options", options)
|
|
4229
|
-
|
|
4230
|
-
const { quote: prepareSendQuote, send } = await prepareSend(options)
|
|
4231
|
-
|
|
4232
|
-
const quote = {
|
|
4233
|
-
fromAmount: prepareSendQuote.originAmount,
|
|
4234
|
-
toAmount: prepareSendQuote.destinationAmount,
|
|
4235
|
-
fromAmountMin: prepareSendQuote.originAmountMin,
|
|
4236
|
-
toAmountMin: prepareSendQuote.destinationAmountMin,
|
|
4237
|
-
originToken: prepareSendQuote.originToken,
|
|
4238
|
-
destinationToken: prepareSendQuote.destinationToken,
|
|
4239
|
-
originChain: prepareSendQuote.originChain,
|
|
4240
|
-
destinationChain: prepareSendQuote.destinationChain,
|
|
4241
|
-
fees: prepareSendQuote.fees,
|
|
4242
|
-
priceImpact: prepareSendQuote.priceImpact,
|
|
4243
|
-
completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
|
|
4244
|
-
slippageTolerance: prepareSendQuote.slippageTolerance,
|
|
4245
|
-
transactionStates: prepareSendQuote.transactionStates,
|
|
4246
|
-
originTokenRate: prepareSendQuote.originTokenRate,
|
|
4247
|
-
destinationTokenRate: prepareSendQuote.destinationTokenRate,
|
|
4248
|
-
quoteProvider: prepareSendQuote.quoteProvider,
|
|
4249
|
-
fromAmountUsdDisplay:
|
|
4250
|
-
prepareSendQuote.originAmountUsdDisplay ?? undefined,
|
|
4251
|
-
toAmountUsdDisplay:
|
|
4252
|
-
prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
|
|
4253
|
-
gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
|
|
4254
|
-
gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
|
|
4255
|
-
}
|
|
4256
|
-
|
|
4257
|
-
const swap = async (): Promise<SwapReturn> => {
|
|
4258
|
-
const {
|
|
4259
|
-
originUserTxReceipt,
|
|
4260
|
-
destinationMetaTxnReceipt,
|
|
4261
|
-
totalCompletionSeconds,
|
|
4262
|
-
} = await send({})
|
|
4263
|
-
|
|
4264
|
-
return {
|
|
4265
|
-
originTransaction: {
|
|
4266
|
-
transactionHash: originUserTxReceipt?.transactionHash,
|
|
4267
|
-
explorerUrl: getExplorerUrl({
|
|
4268
|
-
txHash: originUserTxReceipt?.transactionHash as string,
|
|
4269
|
-
chainId: fromChainId,
|
|
4270
|
-
}),
|
|
4271
|
-
receipt: originUserTxReceipt,
|
|
4272
|
-
},
|
|
4273
|
-
destinationTransaction: {
|
|
4274
|
-
transactionHash: destinationMetaTxnReceipt?.txnHash,
|
|
4275
|
-
explorerUrl: getExplorerUrl({
|
|
4276
|
-
txHash: destinationMetaTxnReceipt?.txnHash as string,
|
|
4277
|
-
chainId: toChainId,
|
|
4278
|
-
}),
|
|
4279
|
-
receipt: destinationMetaTxnReceipt,
|
|
4280
|
-
},
|
|
4281
|
-
totalCompletionSeconds,
|
|
4282
|
-
}
|
|
4283
|
-
}
|
|
4284
|
-
|
|
4285
|
-
return {
|
|
4286
|
-
quote,
|
|
4287
|
-
swap,
|
|
4288
|
-
}
|
|
4289
|
-
} catch (error) {
|
|
4290
|
-
logger.console.error(
|
|
4291
|
-
"[trails-sdk] [useQuote] Error getting quote:",
|
|
4292
|
-
error,
|
|
4293
|
-
)
|
|
4294
|
-
throw getFullErrorMessage(error)
|
|
4295
|
-
}
|
|
4296
|
-
},
|
|
4297
|
-
// Prevent unnecessary refetching
|
|
4298
|
-
enabled: Boolean(
|
|
4299
|
-
walletClient &&
|
|
4300
|
-
apiClient &&
|
|
4301
|
-
fromTokenAddress &&
|
|
4302
|
-
toTokenAddress &&
|
|
4303
|
-
swapAmount &&
|
|
4304
|
-
toRecipient &&
|
|
4305
|
-
fromChainId &&
|
|
4306
|
-
toChainId &&
|
|
4307
|
-
indexerGatewayClient,
|
|
4308
|
-
),
|
|
4309
|
-
staleTime: 30 * 1000, // Consider data fresh for 30 seconds
|
|
4310
|
-
refetchOnWindowFocus: false, // Don't refetch when window regains focus
|
|
4311
|
-
refetchOnMount: false, // Don't refetch on component remount if data exists
|
|
4312
|
-
refetchInterval: false, // Disable automatic polling
|
|
4313
|
-
retry: 2, // Limit retry attempts
|
|
4314
|
-
refetchOnReconnect: true, // Refetch when network reconnects
|
|
4315
|
-
})
|
|
4429
|
+
// Re-export useQuote types and hook from the new location
|
|
4430
|
+
export type {
|
|
4431
|
+
UseQuoteProps,
|
|
4432
|
+
Quote,
|
|
4433
|
+
UseQuoteReturn,
|
|
4434
|
+
SwapReturn,
|
|
4435
|
+
QuoteProviderInfo,
|
|
4436
|
+
} from "./widget/hooks/useQuote.js"
|
|
4437
|
+
export { useQuote } from "./widget/hooks/useQuote.js"
|
|
4316
4438
|
|
|
4317
|
-
|
|
4318
|
-
|
|
4319
|
-
swap: data?.swap || null,
|
|
4320
|
-
isLoadingQuote: isLoading,
|
|
4321
|
-
quoteError: error,
|
|
4322
|
-
refetchQuote: () => refetch(),
|
|
4323
|
-
}
|
|
4324
|
-
}
|
|
4439
|
+
// Import QuoteProviderInfo for use in this file
|
|
4440
|
+
import type { QuoteProviderInfo } from "./widget/hooks/useQuote.js"
|
|
4325
4441
|
|
|
4326
4442
|
export function getFeesFromIntent(
|
|
4327
4443
|
intent: GetIntentCallsPayloadsReturn,
|
|
@@ -4447,6 +4563,8 @@ export async function getNormalizedQuoteObject({
|
|
|
4447
4563
|
originNativeTokenPriceUsd,
|
|
4448
4564
|
quoteProvider,
|
|
4449
4565
|
noSufficientBalance,
|
|
4566
|
+
estimatedGasLimit,
|
|
4567
|
+
intent,
|
|
4450
4568
|
}: {
|
|
4451
4569
|
originDepositAddress?: string
|
|
4452
4570
|
destinationDepositAddress?: string
|
|
@@ -4470,6 +4588,8 @@ export async function getNormalizedQuoteObject({
|
|
|
4470
4588
|
originNativeTokenPriceUsd?: number | null
|
|
4471
4589
|
quoteProvider?: string
|
|
4472
4590
|
noSufficientBalance?: boolean
|
|
4591
|
+
estimatedGasLimit?: bigint
|
|
4592
|
+
intent?: GetIntentCallsPayloadsReturn
|
|
4473
4593
|
}): Promise<PrepareSendQuote> {
|
|
4474
4594
|
if (!destinationChainId) {
|
|
4475
4595
|
throw new Error("Destination chain id is required")
|
|
@@ -4497,7 +4617,7 @@ export async function getNormalizedQuoteObject({
|
|
|
4497
4617
|
const destinationChain = getChainInfo(destinationChainId)
|
|
4498
4618
|
|
|
4499
4619
|
if (!originToken || !destinationToken || !originChain || !destinationChain) {
|
|
4500
|
-
console.error("[trails-sdk] Token or chain not found", {
|
|
4620
|
+
logger.console.error("[trails-sdk] Token or chain not found", {
|
|
4501
4621
|
originToken,
|
|
4502
4622
|
destinationToken,
|
|
4503
4623
|
originChain,
|
|
@@ -4571,17 +4691,52 @@ export async function getNormalizedQuoteObject({
|
|
|
4571
4691
|
|
|
4572
4692
|
let gasCostUsd: number = 0
|
|
4573
4693
|
let gasCostUsdDisplay: string = "0"
|
|
4694
|
+
let gasCost: string = "0"
|
|
4695
|
+
let gasCostFormatted: string = "0"
|
|
4574
4696
|
try {
|
|
4575
4697
|
if (originNativeTokenPriceUsd) {
|
|
4698
|
+
// Use the actual estimated gas limit if provided, otherwise use default
|
|
4699
|
+
// This ensures the quote gas cost matches what will be used in the actual transaction
|
|
4700
|
+
const gasLimitForCost = estimatedGasLimit || DEFAULT_MIN_GASLIMIT
|
|
4701
|
+
|
|
4702
|
+
logger.console.log(
|
|
4703
|
+
"[trails-sdk][gas-estimation] Calculating gas cost for quote with gasLimit:",
|
|
4704
|
+
gasLimitForCost,
|
|
4705
|
+
"(estimated:",
|
|
4706
|
+
estimatedGasLimit,
|
|
4707
|
+
"default:",
|
|
4708
|
+
DEFAULT_MIN_GASLIMIT,
|
|
4709
|
+
")",
|
|
4710
|
+
)
|
|
4711
|
+
|
|
4712
|
+
const gasCostWei = await estimateGasCost(publicClient, gasLimitForCost)
|
|
4713
|
+
gasCost = gasCostWei.toString()
|
|
4714
|
+
gasCostFormatted = formatUnits(gasCostWei, 18) // Native tokens always use 18 decimals
|
|
4715
|
+
|
|
4716
|
+
// Calculate USD value
|
|
4576
4717
|
gasCostUsd = await estimateGasCostUsd(
|
|
4577
4718
|
publicClient,
|
|
4578
4719
|
originNativeTokenPriceUsd,
|
|
4579
|
-
|
|
4720
|
+
gasLimitForCost,
|
|
4580
4721
|
)
|
|
4581
4722
|
gasCostUsdDisplay = formatUsdAmountDisplay(gasCostUsd)
|
|
4723
|
+
|
|
4724
|
+
logger.console.log(
|
|
4725
|
+
"[trails-sdk][gas-estimation] Quote gas cost calculated:",
|
|
4726
|
+
{
|
|
4727
|
+
gasCostWei,
|
|
4728
|
+
gasCost,
|
|
4729
|
+
gasCostFormatted,
|
|
4730
|
+
gasCostUsd,
|
|
4731
|
+
gasCostUsdDisplay,
|
|
4732
|
+
},
|
|
4733
|
+
)
|
|
4582
4734
|
}
|
|
4583
4735
|
} catch (error) {
|
|
4584
|
-
console.error(
|
|
4736
|
+
logger.console.error(
|
|
4737
|
+
"[trails-sdk][gas-estimation] Error estimating gas cost for quote display",
|
|
4738
|
+
error,
|
|
4739
|
+
)
|
|
4585
4740
|
}
|
|
4586
4741
|
|
|
4587
4742
|
// Calculate exchange rates
|
|
@@ -4623,6 +4778,12 @@ export async function getNormalizedQuoteObject({
|
|
|
4623
4778
|
|
|
4624
4779
|
const priceImpactUsdDisplay = formatUsdAmountDisplay(priceImpactUsd)
|
|
4625
4780
|
|
|
4781
|
+
// Extract fee breakdown from intent if available
|
|
4782
|
+
let trailsFeeBreakdown = null
|
|
4783
|
+
if (intent) {
|
|
4784
|
+
trailsFeeBreakdown = await extractTrailsFeeBreakdown(intent)
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4626
4787
|
return {
|
|
4627
4788
|
originDepositAddress: originDepositAddress || "",
|
|
4628
4789
|
destinationDepositAddress: destinationDepositAddress || "",
|
|
@@ -4657,6 +4818,8 @@ export async function getNormalizedQuoteObject({
|
|
|
4657
4818
|
transactionStates: transactionStates || [],
|
|
4658
4819
|
gasCostUsd,
|
|
4659
4820
|
gasCostUsdDisplay,
|
|
4821
|
+
gasCost,
|
|
4822
|
+
gasCostFormatted,
|
|
4660
4823
|
originTokenRate: exchangeRates.originTokenRate,
|
|
4661
4824
|
destinationTokenRate: exchangeRates.destinationTokenRate,
|
|
4662
4825
|
originAmountDisplay: formatAmountDisplay(originAmountFormatted),
|
|
@@ -4667,6 +4830,7 @@ export async function getNormalizedQuoteObject({
|
|
|
4667
4830
|
),
|
|
4668
4831
|
quoteProvider: quoteProviderInfo,
|
|
4669
4832
|
noSufficientBalance: noSufficientBalance || false,
|
|
4833
|
+
trailsFeeBreakdown,
|
|
4670
4834
|
}
|
|
4671
4835
|
}
|
|
4672
4836
|
|