0xtrails 0.2.4 → 0.2.5

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.
Files changed (161) hide show
  1. package/dist/aave.d.ts +8 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/{ccip-BlV1Mry3.js → ccip-CXlshvBY.js} +1 -1
  4. package/dist/config.d.ts +1 -1
  5. package/dist/config.d.ts.map +1 -1
  6. package/dist/constants.d.ts +1 -0
  7. package/dist/constants.d.ts.map +1 -1
  8. package/dist/error.d.ts +1 -0
  9. package/dist/error.d.ts.map +1 -1
  10. package/dist/estimate.d.ts +52 -0
  11. package/dist/estimate.d.ts.map +1 -1
  12. package/dist/{index-BNWCIGfQ.js → index-_QuyGrjU.js} +72332 -72246
  13. package/dist/index.js +2 -2
  14. package/dist/intents.d.ts +40 -0
  15. package/dist/intents.d.ts.map +1 -1
  16. package/dist/metaTxnMonitor.d.ts +3 -3
  17. package/dist/metaTxnMonitor.d.ts.map +1 -1
  18. package/dist/metaTxns.d.ts +3 -3
  19. package/dist/metaTxns.d.ts.map +1 -1
  20. package/dist/morpho.d.ts +8 -0
  21. package/dist/morpho.d.ts.map +1 -1
  22. package/dist/prepareSend.d.ts +16 -6
  23. package/dist/prepareSend.d.ts.map +1 -1
  24. package/dist/queryParams.d.ts.map +1 -1
  25. package/dist/relayer.d.ts +6 -6
  26. package/dist/relayer.d.ts.map +1 -1
  27. package/dist/sequenceWallet.d.ts +2 -2
  28. package/dist/sequenceWallet.d.ts.map +1 -1
  29. package/dist/tokens.d.ts.map +1 -1
  30. package/dist/wallets.d.ts.map +1 -1
  31. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  32. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  33. package/dist/widget/components/ClassicSwap.d.ts +2 -0
  34. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  35. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  36. package/dist/widget/components/ConnectedWallets.d.ts +4 -0
  37. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  38. package/dist/widget/components/Earn.d.ts.map +1 -1
  39. package/dist/widget/components/Fund.d.ts.map +1 -1
  40. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  41. package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +11 -5
  42. package/dist/widget/components/FundSwap.d.ts.map +1 -0
  43. package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
  44. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
  45. package/dist/widget/components/Modal.d.ts.map +1 -1
  46. package/dist/widget/components/Pay.d.ts.map +1 -1
  47. package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
  48. package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
  49. package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +11 -34
  50. package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
  51. package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +16 -8
  52. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
  53. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  54. package/dist/widget/components/Receive.d.ts.map +1 -1
  55. package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
  56. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
  57. package/dist/widget/components/Recipients.d.ts.map +1 -1
  58. package/dist/widget/components/RequiredPropsError.d.ts +8 -0
  59. package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
  60. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  61. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  62. package/dist/widget/components/Swap.d.ts +1 -0
  63. package/dist/widget/components/Swap.d.ts.map +1 -1
  64. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  65. package/dist/widget/components/TokenImage.d.ts +1 -0
  66. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  67. package/dist/widget/components/TokenList.d.ts.map +1 -1
  68. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  69. package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
  70. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
  71. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  72. package/dist/widget/components/WaasFeeOptions.d.ts +8 -0
  73. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
  74. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  75. package/dist/widget/components/WalletList.d.ts.map +1 -1
  76. package/dist/widget/css/compiled.css +2 -0
  77. package/dist/widget/css/index.css +554 -0
  78. package/dist/widget/hooks/useBack.d.ts +1 -0
  79. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  80. package/dist/widget/hooks/useCheckout.d.ts +1 -1
  81. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  82. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  83. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  84. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
  85. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  86. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
  87. package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
  88. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
  89. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
  90. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  91. package/dist/widget/index.js +1 -1
  92. package/dist/widget/widget.d.ts +4 -4
  93. package/dist/widget/widget.d.ts.map +1 -1
  94. package/package.json +18 -12
  95. package/src/aave.ts +32 -0
  96. package/src/config.ts +12 -4
  97. package/src/constants.ts +2 -0
  98. package/src/error.ts +19 -1
  99. package/src/estimate.ts +416 -5
  100. package/src/intents.ts +161 -11
  101. package/src/metaTxnMonitor.ts +3 -3
  102. package/src/metaTxns.ts +3 -5
  103. package/src/morpho.ts +32 -0
  104. package/src/prepareSend.ts +503 -166
  105. package/src/queryParams.ts +2 -1
  106. package/src/relayer.ts +11 -11
  107. package/src/sequenceWallet.ts +2 -2
  108. package/src/tokens.ts +7 -1
  109. package/src/wallets.ts +8 -0
  110. package/src/widget/compiled.css +2 -2
  111. package/src/widget/components/AccountActionsDropdown.tsx +3 -13
  112. package/src/widget/components/AccountSettings.tsx +6 -24
  113. package/src/widget/components/ClassicSwap.tsx +111 -155
  114. package/src/widget/components/ConnectWallet.tsx +4 -37
  115. package/src/widget/components/ConnectedWallets.tsx +113 -58
  116. package/src/widget/components/Earn.tsx +73 -589
  117. package/src/widget/components/Fund.tsx +31 -82
  118. package/src/widget/components/FundMethods.tsx +82 -159
  119. package/src/widget/components/FundSwap.tsx +52 -0
  120. package/src/widget/components/FundingMethodSelectorButton.tsx +60 -0
  121. package/src/widget/components/Modal.tsx +6 -2
  122. package/src/widget/components/Pay.tsx +183 -208
  123. package/src/widget/components/PercentageMaxButtons.tsx +77 -0
  124. package/src/widget/components/PoolDeposit.tsx +593 -0
  125. package/src/widget/components/PoolWithdraw.tsx +903 -0
  126. package/src/widget/components/QuoteDetails.tsx +22 -8
  127. package/src/widget/components/Receive.tsx +0 -2
  128. package/src/widget/components/RecipientSelectorButton.tsx +42 -0
  129. package/src/widget/components/Recipients.tsx +62 -156
  130. package/src/widget/components/RequiredPropsError.tsx +33 -0
  131. package/src/widget/components/ScreenHeader.tsx +5 -1
  132. package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
  133. package/src/widget/components/Swap.tsx +2 -43
  134. package/src/widget/components/SwapSettings.tsx +2 -14
  135. package/src/widget/components/TokenImage.tsx +21 -4
  136. package/src/widget/components/TokenList.tsx +0 -1
  137. package/src/widget/components/TokenSelector.tsx +1 -0
  138. package/src/widget/components/TokenSelectorButton.tsx +75 -0
  139. package/src/widget/components/UserPreferences.tsx +6 -24
  140. package/src/widget/components/WaasFeeOptions.tsx +331 -0
  141. package/src/widget/components/WalletConfirmation.tsx +55 -3
  142. package/src/widget/components/WalletList.tsx +4 -2
  143. package/src/widget/hooks/useBack.tsx +2 -0
  144. package/src/widget/hooks/useCheckout.ts +36 -20
  145. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  146. package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
  147. package/src/widget/hooks/usePayMessage.tsx +86 -11
  148. package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
  149. package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
  150. package/src/widget/hooks/useSendForm.ts +24 -2
  151. package/src/widget/index.css +27 -0
  152. package/src/widget/widget.tsx +169 -111
  153. package/dist/widget/components/FundSendForm.d.ts.map +0 -1
  154. package/dist/widget/components/PaySendForm.d.ts.map +0 -1
  155. package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
  156. package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
  157. package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
  158. package/src/widget/components/FundSendForm.tsx +0 -903
  159. package/src/widget/components/PaySendForm.tsx +0 -869
  160. package/src/widget/components/SimpleSwap.tsx +0 -983
  161. package/src/widget/hooks/useSwapSettings.tsx +0 -100
@@ -4,6 +4,7 @@ import type {
4
4
  IntentPrecondition,
5
5
  } from "@0xsequence/trails-api"
6
6
  import type { TrailsAPIClient } from "@0xsequence/trails-api"
7
+ import type { Relayer } from "@0xsequence/relayer"
7
8
  import { useQuery } from "@tanstack/react-query"
8
9
  import type {
9
10
  Account,
@@ -62,7 +63,12 @@ import {
62
63
  } from "./decoders.js"
63
64
  import { getERC20TransferData } from "./encoders.js"
64
65
  import { getFullErrorMessage, InsufficientBalanceError } from "./error.js"
65
- import { estimateGasCostUsd } from "./estimate.js"
66
+ import {
67
+ estimateGasCost,
68
+ estimateGasCostUsd,
69
+ estimateGasLimit,
70
+ DEFAULT_MIN_GASLIMIT,
71
+ } from "./estimate.js"
66
72
  import { getExplorerUrl } from "./explorer.js"
67
73
  import {
68
74
  getNeedsIntentEntrypointApproval,
@@ -73,6 +79,8 @@ import {
73
79
  import { getIntentEntrypointFeeOptions } from "./intentEntrypoint.js"
74
80
  import { useIndexerGatewayClient } from "./indexerClient.js"
75
81
  import {
82
+ buildSameChainTransactionParams,
83
+ buildCrossChainDepositParams,
76
84
  commitIntentConfig,
77
85
  getIntentCallsPayloads as getIntentCallsPayloadsFromIntents,
78
86
  sendOriginTransaction,
@@ -131,7 +139,6 @@ import {
131
139
  getPaymasterGaslessTransaction,
132
140
  sendPaymasterGaslessTransaction,
133
141
  } from "./paymasterSend.js"
134
- import type { RpcRelayer } from "@0xsequence/relayer"
135
142
 
136
143
  export enum TradeType {
137
144
  EXACT_INPUT = "EXACT_INPUT",
@@ -156,8 +163,8 @@ export type PrepareSendOptions = {
156
163
  dryMode: boolean
157
164
  apiClient: SequenceAPIClient
158
165
  trailsClient: TrailsAPIClient
159
- originRelayer: RpcRelayer.RpcRelayer
160
- destinationRelayer: RpcRelayer.RpcRelayer
166
+ originRelayer: Relayer.RpcRelayer
167
+ destinationRelayer: Relayer.RpcRelayer
161
168
  destinationCalldata?: string
162
169
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
163
170
  sourceTokenPriceUsd?: number | null
@@ -174,6 +181,7 @@ export type PrepareSendOptions = {
174
181
  checkoutOnHandlers?: CheckoutOnHandlers
175
182
  refundAddress?: string
176
183
  selectedFeeToken?: any
184
+ walletId?: string
177
185
  }
178
186
 
179
187
  export type PrepareSendFees = {
@@ -218,6 +226,8 @@ export type PrepareSendQuote = {
218
226
  transactionStates: TransactionState[]
219
227
  gasCostUsd: number
220
228
  gasCostUsdDisplay: string
229
+ gasCost: string
230
+ gasCostFormatted: string
221
231
  originTokenRate: string
222
232
  destinationTokenRate: string
223
233
  quoteProvider: QuoteProviderInfo | null
@@ -385,6 +395,7 @@ export async function prepareSend(
385
395
  mode,
386
396
  checkoutOnHandlers,
387
397
  selectedFeeToken,
398
+ walletId,
388
399
  } = options
389
400
  let { sourceTokenPriceUsd, destinationTokenPriceUsd } = options
390
401
 
@@ -568,18 +579,20 @@ export async function prepareSend(
568
579
  label: isToSameChain && isToSameToken ? "Execute" : "Transfer",
569
580
  })
570
581
 
571
- // swap (+ bridge tx)
572
- transactionStates.push({
573
- transactionHash: "",
574
- explorerUrl: "",
575
- chainId: originChainId,
576
- state: "pending",
577
- label: isToSameToken
578
- ? "Bridge"
579
- : isToSameChain && !isToSameToken
580
- ? "Swap"
581
- : "Swap & Bridge",
582
- })
582
+ // swap (+ bridge tx) - skip for same-chain-same-token as there's no second transaction
583
+ if (!(isToSameChain && isToSameToken)) {
584
+ transactionStates.push({
585
+ transactionHash: "",
586
+ explorerUrl: "",
587
+ chainId: originChainId,
588
+ state: "pending",
589
+ label: isToSameToken
590
+ ? "Bridge"
591
+ : isToSameChain && !isToSameToken
592
+ ? "Swap"
593
+ : "Swap & Bridge",
594
+ })
595
+ }
583
596
 
584
597
  // additional execute tx on same chain if needed
585
598
  if (isToSameChain && hasCustomCalldata && !isToSameToken) {
@@ -652,6 +665,13 @@ export async function prepareSend(
652
665
  originNativeTokenPriceUsd,
653
666
  slippageTolerance,
654
667
  checkoutOnHandlers,
668
+ gasless,
669
+ paymasterUrl,
670
+ selectedFeeToken,
671
+ trailsClient,
672
+ originRelayer,
673
+ mode,
674
+ fundMethod,
655
675
  })
656
676
  }
657
677
 
@@ -693,6 +713,7 @@ export async function prepareSend(
693
713
  mode,
694
714
  checkoutOnHandlers,
695
715
  selectedFeeToken,
716
+ walletId,
696
717
  })
697
718
  }
698
719
 
@@ -733,6 +754,7 @@ async function sendHandlerForDifferentChainDifferentToken({
733
754
  mode,
734
755
  checkoutOnHandlers,
735
756
  selectedFeeToken,
757
+ walletId,
736
758
  }: {
737
759
  mainSignerAddress: string
738
760
  originChainId: number
@@ -753,8 +775,8 @@ async function sendHandlerForDifferentChainDifferentToken({
753
775
  destinationTokenDecimals: number
754
776
  gasless: boolean
755
777
  paymasterUrl?: string
756
- originRelayer: RpcRelayer.RpcRelayer
757
- destinationRelayer: RpcRelayer.RpcRelayer
778
+ originRelayer: Relayer.RpcRelayer
779
+ destinationRelayer: Relayer.RpcRelayer
758
780
  walletClient: WalletClient
759
781
  publicClient: PublicClient
760
782
  chain: Chain
@@ -771,6 +793,7 @@ async function sendHandlerForDifferentChainDifferentToken({
771
793
  mode?: "pay" | "fund" | "earn" | "swap" | "receive"
772
794
  checkoutOnHandlers?: CheckoutOnHandlers
773
795
  selectedFeeToken?: any
796
+ walletId?: string
774
797
  }): Promise<PrepareSendReturn> {
775
798
  const testnet = isTestnetDebugMode()
776
799
  const useCctp = getUseCctp(
@@ -1120,6 +1143,42 @@ async function sendHandlerForDifferentChainDifferentToken({
1120
1143
  }
1121
1144
  }
1122
1145
 
1146
+ // Estimate gas limit for the quote (same logic as in attemptNonGaslessUserDeposit)
1147
+ const originCallParamsForEstimate = buildCrossChainDepositParams({
1148
+ originTokenAddress,
1149
+ originIntentAddress,
1150
+ depositAmount: firstPreconditionMin,
1151
+ fee,
1152
+ originChainId,
1153
+ chain,
1154
+ })
1155
+
1156
+ logger.console.log(
1157
+ "[trails-sdk][gas-estimation] About to estimate gas limit for cross-chain quote with params:",
1158
+ {
1159
+ account: account.address,
1160
+ to: originCallParamsForEstimate.to,
1161
+ data: originCallParamsForEstimate.data,
1162
+ value: originCallParamsForEstimate.value,
1163
+ },
1164
+ )
1165
+
1166
+ const estimatedGasLimitForQuote = await estimateGasLimit(
1167
+ publicClient,
1168
+ {
1169
+ account: account.address,
1170
+ to: originCallParamsForEstimate.to,
1171
+ data: originCallParamsForEstimate.data,
1172
+ value: BigInt(originCallParamsForEstimate.value),
1173
+ },
1174
+ "quote",
1175
+ )
1176
+
1177
+ logger.console.log(
1178
+ "[trails-sdk][gas-estimation] Estimated gas limit for cross-chain quote:",
1179
+ estimatedGasLimitForQuote,
1180
+ )
1181
+
1123
1182
  const quote = await getNormalizedQuoteObject({
1124
1183
  originDepositAddress: originIntentAddress,
1125
1184
  destinationDepositAddress: intent.payloads.destinationIntentAddress,
@@ -1147,6 +1206,7 @@ async function sendHandlerForDifferentChainDifferentToken({
1147
1206
  originNativeTokenPriceUsd,
1148
1207
  quoteProvider: intent.payloads?.quote?.quoteProvider,
1149
1208
  noSufficientBalance,
1209
+ estimatedGasLimit: estimatedGasLimitForQuote,
1150
1210
  })
1151
1211
 
1152
1212
  // Call onCheckoutQuote callback if provided
@@ -1154,12 +1214,15 @@ async function sendHandlerForDifferentChainDifferentToken({
1154
1214
  checkoutOnHandlers.triggerCheckoutQuote(quote)
1155
1215
  }
1156
1216
 
1157
- // Get intent entrypoint fee options if supported, gasless is enabled, and fundMethod is wallet
1217
+ // Get intent entrypoint fee options if supported and gasless is enabled
1218
+ // Note: We fetch fee options whenever gasless is true, regardless of fundMethod,
1219
+ // because gasless scenarios (including tests) need fee collector information
1220
+ // Skip gasless fee options for sequence-waas wallet
1158
1221
  let intentEntrypointFeeOptions: any = null
1159
- if (gasless && fundMethod === "wallet") {
1222
+ if (gasless && walletId !== "sequence-waas") {
1160
1223
  try {
1161
1224
  logger.console.log(
1162
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled and fundMethod is wallet, checking intent entrypoint support for chain:",
1225
+ "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled, checking intent entrypoint support for chain:",
1163
1226
  originChainId,
1164
1227
  )
1165
1228
 
@@ -1181,9 +1244,13 @@ async function sendHandlerForDifferentChainDifferentToken({
1181
1244
  error,
1182
1245
  )
1183
1246
  }
1247
+ } else if (walletId === "sequence-waas") {
1248
+ logger.console.log(
1249
+ "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Skipping gasless fee options for sequence-waas wallet",
1250
+ )
1184
1251
  } else {
1185
1252
  logger.console.log(
1186
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled or fundMethod is not wallet, skipping intent entrypoint fee options fetch",
1253
+ "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled, skipping intent entrypoint fee options fetch",
1187
1254
  )
1188
1255
  }
1189
1256
 
@@ -1349,6 +1416,7 @@ async function sendHandlerForDifferentChainDifferentToken({
1349
1416
  feeOptions: intentEntrypointFeeOptions,
1350
1417
  trailsClient,
1351
1418
  selectedFeeToken: effectiveSelectedFeeToken,
1419
+ walletId,
1352
1420
  })
1353
1421
 
1354
1422
  if (!originUserTxReceipt) {
@@ -1801,7 +1869,7 @@ async function sendHandlerForDifferentChainDifferentToken({
1801
1869
  }
1802
1870
 
1803
1871
  checkForDepositTx().catch((error) => {
1804
- console.error("Error checking for deposit tx", error)
1872
+ logger.console.error("Error checking for deposit tx", error)
1805
1873
  })
1806
1874
 
1807
1875
  // Phase 1: Send meta transactions and queue CCTP
@@ -2019,11 +2087,14 @@ async function sendHandlerForDifferentChainDifferentToken({
2019
2087
 
2020
2088
  onTransactionStateChange(transactionStates)
2021
2089
  } catch (error) {
2022
- console.error("Error decoding destination tx events", error)
2090
+ logger.console.error(
2091
+ "Error decoding destination tx events",
2092
+ error,
2093
+ )
2023
2094
  }
2024
2095
  }
2025
2096
  } catch (error) {
2026
- console.error(
2097
+ logger.console.error(
2027
2098
  "[trails-sdk] Error waiting for destination receipt:",
2028
2099
  error,
2029
2100
  )
@@ -2059,6 +2130,17 @@ async function sendHandlerForDifferentChainDifferentToken({
2059
2130
 
2060
2131
  // Track payment completion for different chain and different token
2061
2132
  if (originUserTxReceipt && destinationMetaTxnReceipt) {
2133
+ // Check if any transaction failed or was refunded
2134
+ const hasAnyCallFailed = transactionStates.some((tx) =>
2135
+ tx?.decodedGuestModuleEvents?.some(
2136
+ (e: any) => e?.type === "CallFailed",
2137
+ ),
2138
+ )
2139
+ const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
2140
+ const txStatus =
2141
+ !hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
2142
+
2143
+ // Always track payment completion regardless of success/failure
2062
2144
  trackPaymentCompleted({
2063
2145
  userAddress: account.address,
2064
2146
  originIntentAddress,
@@ -2079,9 +2161,9 @@ async function sendHandlerForDifferentChainDifferentToken({
2079
2161
  effectiveDestinationTokenAmountUsd?.toString(),
2080
2162
  })
2081
2163
 
2082
- // Call onCheckoutComplete callback if provided
2164
+ // Call onCheckoutComplete callback with transaction status
2083
2165
  if (checkoutOnHandlers?.triggerCheckoutComplete) {
2084
- checkoutOnHandlers.triggerCheckoutComplete()
2166
+ checkoutOnHandlers.triggerCheckoutComplete(txStatus)
2085
2167
  }
2086
2168
  } else {
2087
2169
  if (
@@ -2190,6 +2272,11 @@ async function sendHandlerForSameChainSameToken({
2190
2272
  checkoutOnHandlers,
2191
2273
  mode,
2192
2274
  fundMethod,
2275
+ gasless,
2276
+ paymasterUrl,
2277
+ selectedFeeToken,
2278
+ trailsClient,
2279
+ originRelayer,
2193
2280
  }: {
2194
2281
  originTokenAddress: string
2195
2282
  originTokenDecimals: number
@@ -2211,6 +2298,11 @@ async function sendHandlerForSameChainSameToken({
2211
2298
  checkoutOnHandlers?: CheckoutOnHandlers
2212
2299
  mode?: "pay" | "fund" | "earn" | "swap" | "receive"
2213
2300
  fundMethod?: string
2301
+ gasless?: boolean
2302
+ paymasterUrl?: string
2303
+ selectedFeeToken?: any
2304
+ trailsClient: TrailsAPIClient
2305
+ originRelayer: Relayer.RpcRelayer
2214
2306
  }): Promise<PrepareSendReturn> {
2215
2307
  logger.console.log("[trails-sdk] isToSameToken && isToSameChain")
2216
2308
  const testnet = isTestnetDebugMode()
@@ -2237,6 +2329,44 @@ async function sendHandlerForSameChainSameToken({
2237
2329
  noSufficientBalance = true
2238
2330
  }
2239
2331
 
2332
+ // Build origin call params and estimate gas limit for the quote
2333
+ const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
2334
+ const originCallParamsBase = buildSameChainTransactionParams({
2335
+ hasCustomCalldata,
2336
+ recipient,
2337
+ effectiveOriginTokenAddress,
2338
+ destinationCalldata,
2339
+ swapAmount,
2340
+ effectiveOriginChainId,
2341
+ effectiveOriginChain,
2342
+ })
2343
+
2344
+ logger.console.log(
2345
+ "[trails-sdk][gas-estimation] About to estimate gas limit for quote with params:",
2346
+ {
2347
+ account: account.address,
2348
+ to: originCallParamsBase.to,
2349
+ data: originCallParamsBase.data,
2350
+ value: originCallParamsBase.value,
2351
+ },
2352
+ )
2353
+
2354
+ const estimatedGasLimitForQuote = await estimateGasLimit(
2355
+ effectivePublicClient,
2356
+ {
2357
+ account: account.address,
2358
+ to: originCallParamsBase.to,
2359
+ data: originCallParamsBase.data,
2360
+ value: BigInt(originCallParamsBase.value),
2361
+ },
2362
+ "quote",
2363
+ )
2364
+
2365
+ logger.console.log(
2366
+ "[trails-sdk][gas-estimation] Estimated gas limit for quote:",
2367
+ estimatedGasLimitForQuote,
2368
+ )
2369
+
2240
2370
  const quote = await getNormalizedQuoteObject({
2241
2371
  originDepositAddress: recipient,
2242
2372
  destinationDepositAddress: recipient,
@@ -2255,6 +2385,7 @@ async function sendHandlerForSameChainSameToken({
2255
2385
  slippageTolerance,
2256
2386
  quoteProvider: "",
2257
2387
  noSufficientBalance,
2388
+ estimatedGasLimit: estimatedGasLimitForQuote,
2258
2389
  })
2259
2390
 
2260
2391
  // Call onCheckoutQuote callback if provided
@@ -2290,26 +2421,33 @@ async function sendHandlerForSameChainSameToken({
2290
2421
  })
2291
2422
 
2292
2423
  const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
2424
+
2425
+ // Build origin call params (reusing the same logic as quote estimation)
2426
+ const originCallParamsBase = buildSameChainTransactionParams({
2427
+ hasCustomCalldata,
2428
+ recipient,
2429
+ effectiveOriginTokenAddress,
2430
+ destinationCalldata,
2431
+ swapAmount,
2432
+ effectiveOriginChainId,
2433
+ effectiveOriginChain,
2434
+ })
2435
+
2436
+ // Estimate gas limit for consistency with actual transaction
2437
+ const gasLimit = await estimateGasLimit(
2438
+ effectivePublicClient,
2439
+ {
2440
+ account: account.address,
2441
+ to: originCallParamsBase.to,
2442
+ data: originCallParamsBase.data,
2443
+ value: BigInt(originCallParamsBase.value),
2444
+ },
2445
+ "send",
2446
+ )
2447
+
2293
2448
  const originCallParams = {
2294
- to: hasCustomCalldata
2295
- ? recipient
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,
2449
+ ...originCallParamsBase,
2450
+ gasLimit,
2313
2451
  }
2314
2452
 
2315
2453
  logger.console.log("[trails-sdk] origin call params", originCallParams)
@@ -2340,105 +2478,207 @@ async function sendHandlerForSameChainSameToken({
2340
2478
  )
2341
2479
  }
2342
2480
 
2343
- if (hasCustomCalldata) {
2481
+ // For gasless transactions with custom calldata, use the gasless deposit flow
2482
+ if (gasless && hasCustomCalldata) {
2483
+ logger.console.log(
2484
+ "[trails-sdk] Using gasless intent entrypoint flow for same-chain transaction with custom calldata",
2485
+ )
2486
+
2487
+ // Fetch fee options for gasless deposit
2488
+ let feeOptions: any = null
2344
2489
  try {
2345
- const needsApproval = await getNeedsApproval({
2346
- publicClient: effectivePublicClient,
2347
- token: effectiveOriginTokenAddress,
2348
- account: account.address,
2349
- spender: recipient,
2350
- amount: maxUint256,
2490
+ feeOptions = await getIntentEntrypointFeeOptions({
2491
+ trailsClient,
2492
+ userAddress: account.address as `0x${string}`,
2493
+ tokenAddress: effectiveOriginTokenAddress as `0x${string}`,
2494
+ amount: swapAmount,
2495
+ intentAddress: recipient as `0x${string}`,
2496
+ chainId: effectiveOriginChainId,
2497
+ })
2498
+ logger.console.log(
2499
+ "[trails-sdk] [GASLESS-FLOW] Fetched intent entrypoint fee options for same-chain transaction:",
2500
+ feeOptions,
2501
+ )
2502
+ } catch (error) {
2503
+ logger.console.error(
2504
+ "[trails-sdk] [GASLESS-FLOW] Error fetching fee options for same-chain gasless transaction:",
2505
+ error,
2506
+ )
2507
+ }
2508
+
2509
+ const receipt = await attemptGaslessDeposit({
2510
+ account,
2511
+ walletClient,
2512
+ chain: effectiveOriginChain,
2513
+ depositTokenAddress: effectiveOriginTokenAddress,
2514
+ depositTokenAmount: swapAmount,
2515
+ depositRecipient: recipient,
2516
+ onOriginSend: () => {}, // No-op callback
2517
+ feeOptions,
2518
+ paymasterUrl,
2519
+ selectedFeeToken,
2520
+ trailsClient,
2521
+ originRelayer,
2522
+ })
2523
+
2524
+ if (receipt) {
2525
+ originUserTxReceipt = receipt
2526
+
2527
+ // Track the confirmed transaction
2528
+ trackTransactionConfirmed({
2529
+ transactionHash: receipt.transactionHash,
2530
+ chainId: effectiveOriginChainId,
2531
+ userAddress: account.address,
2532
+ blockNumber: Number(receipt.blockNumber),
2533
+ originTokenAddress,
2534
+ depositTokenAmountUsd: depositAmountUsd?.toString(),
2351
2535
  })
2352
2536
 
2353
- if (needsApproval) {
2354
- const txHash = await approveERC20({
2355
- walletClient,
2356
- tokenAddress: effectiveOriginTokenAddress,
2537
+ // Toast will be shown after transaction state analysis
2538
+
2539
+ // Update transaction state
2540
+ try {
2541
+ onTransactionStateChange([
2542
+ getTransactionStateFromReceipt(
2543
+ receipt,
2544
+ effectiveOriginChainId,
2545
+ transactionStates[0]?.label,
2546
+ ),
2547
+ ])
2548
+ } catch (error) {
2549
+ logger.console.error(
2550
+ "[trails-sdk] Error calling onTransactionStateChange:",
2551
+ error,
2552
+ )
2553
+ }
2554
+ }
2555
+ } else {
2556
+ // Non-gasless flow: handle approval if needed
2557
+ if (hasCustomCalldata) {
2558
+ try {
2559
+ const needsApproval = await getNeedsApproval({
2560
+ publicClient: effectivePublicClient,
2561
+ token: effectiveOriginTokenAddress,
2562
+ account: account.address,
2357
2563
  spender: recipient,
2358
2564
  amount: maxUint256,
2359
- chain: effectiveOriginChain,
2360
2565
  })
2361
2566
 
2362
- logger.console.log("waiting for approve", txHash)
2363
- await effectivePublicClient.waitForTransactionReceipt({
2364
- hash: txHash,
2365
- })
2366
- logger.console.log("approve done")
2567
+ if (needsApproval) {
2568
+ const txHash = await approveERC20({
2569
+ walletClient,
2570
+ tokenAddress: effectiveOriginTokenAddress,
2571
+ spender: recipient,
2572
+ amount: maxUint256,
2573
+ chain: effectiveOriginChain,
2574
+ })
2575
+
2576
+ logger.console.log("waiting for approve", txHash)
2577
+ await effectivePublicClient.waitForTransactionReceipt({
2578
+ hash: txHash,
2579
+ })
2580
+ logger.console.log("approve done")
2581
+ }
2582
+ } catch (error) {
2583
+ logger.console.error(
2584
+ "[trails-sdk] Error approving ERC20",
2585
+ error,
2586
+ )
2367
2587
  }
2368
- } catch (error) {
2369
- logger.console.error("[trails-sdk] Error approving ERC20", error)
2370
2588
  }
2371
- }
2372
2589
 
2373
- // Show persistent toast for checkout flow
2374
- updatePersistentToast(
2375
- "Payment Started",
2376
- "Waiting for wallet confirmation...",
2377
- "info",
2378
- )
2590
+ // Show persistent toast for checkout flow
2591
+ updatePersistentToast(
2592
+ "Payment Started",
2593
+ "Waiting for wallet confirmation...",
2594
+ "info",
2595
+ )
2379
2596
 
2380
- logger.console.log(
2381
- "[trails-sdk] origin call params",
2382
- originCallParams,
2383
- )
2384
- const txHash = await sendOriginTransaction(
2385
- account,
2386
- walletClient,
2387
- originCallParams as any,
2388
- {
2389
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2390
- },
2391
- ) // TODO: Add proper type
2597
+ logger.console.log(
2598
+ "[trails-sdk] origin call params",
2599
+ originCallParams,
2600
+ )
2601
+ const txHash = await sendOriginTransaction(
2602
+ account,
2603
+ walletClient,
2604
+ originCallParams as any,
2605
+ {
2606
+ depositTokenAmountUsd: depositAmountUsd?.toString(),
2607
+ },
2608
+ ) // TODO: Add proper type
2392
2609
 
2393
- logger.console.log("[trails-sdk] origin tx", txHash)
2610
+ logger.console.log("[trails-sdk] origin tx", txHash)
2394
2611
 
2395
- if (onOriginSend) {
2396
- onOriginSend()
2397
- }
2612
+ if (onOriginSend) {
2613
+ onOriginSend()
2614
+ }
2398
2615
 
2399
- // Wait for transaction receipt
2400
- const receipt = await effectivePublicClient.waitForTransactionReceipt(
2401
- {
2402
- hash: txHash,
2403
- },
2404
- )
2405
- logger.console.log("[trails-sdk] receipt", receipt)
2406
- originUserTxReceipt = receipt
2616
+ // Wait for transaction receipt
2617
+ const receipt =
2618
+ await effectivePublicClient.waitForTransactionReceipt({
2619
+ hash: txHash,
2620
+ })
2621
+ logger.console.log("[trails-sdk] receipt", receipt)
2622
+ originUserTxReceipt = receipt
2407
2623
 
2408
- trackTransactionConfirmed({
2409
- transactionHash: txHash,
2410
- chainId: effectiveOriginChainId,
2411
- userAddress: account.address,
2412
- blockNumber: Number(receipt.blockNumber),
2413
- originTokenAddress,
2414
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2415
- })
2624
+ trackTransactionConfirmed({
2625
+ transactionHash: txHash,
2626
+ chainId: effectiveOriginChainId,
2627
+ userAddress: account.address,
2628
+ blockNumber: Number(receipt.blockNumber),
2629
+ originTokenAddress,
2630
+ depositTokenAmountUsd: depositAmountUsd?.toString(),
2631
+ })
2416
2632
 
2417
- // Remove persistent toast and show success
2418
- const chainInfo = getChainInfo(effectiveOriginChainId)
2419
- updatePersistentToast(
2420
- "Transfer Confirmed",
2421
- `Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
2422
- "info",
2423
- )
2633
+ // Toast will be shown after transaction state analysis
2424
2634
 
2425
- try {
2426
- onTransactionStateChange([
2427
- getTransactionStateFromReceipt(
2428
- originUserTxReceipt,
2429
- effectiveOriginChainId,
2430
- transactionStates[0]?.label,
2431
- ),
2432
- ])
2433
- } catch (error) {
2434
- logger.console.error(
2435
- "[trails-sdk] Error calling onTransactionStateChange:",
2436
- error,
2437
- )
2635
+ try {
2636
+ onTransactionStateChange([
2637
+ getTransactionStateFromReceipt(
2638
+ originUserTxReceipt,
2639
+ effectiveOriginChainId,
2640
+ transactionStates[0]?.label,
2641
+ ),
2642
+ ])
2643
+ } catch (error) {
2644
+ logger.console.error(
2645
+ "[trails-sdk] Error calling onTransactionStateChange:",
2646
+ error,
2647
+ )
2648
+ }
2649
+ }
2650
+
2651
+ // Show conditional toast based on transaction status
2652
+ const chainInfo = getChainInfo(effectiveOriginChainId)
2653
+ if (originUserTxReceipt) {
2654
+ if (originUserTxReceipt?.status === "success") {
2655
+ updatePersistentToast(
2656
+ "Transfer Confirmed",
2657
+ `Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
2658
+ "info",
2659
+ )
2660
+ } else {
2661
+ updatePersistentToast(
2662
+ "Transfer Failed",
2663
+ `Your transaction on ${chainInfo?.name || "chain"} failed`,
2664
+ "error",
2665
+ )
2666
+ }
2438
2667
  }
2439
2668
 
2440
2669
  // Track payment completion for same-chain same-token transaction
2441
2670
  if (originUserTxReceipt && originUserTxReceipt.status === "success") {
2671
+ // Check if any transaction failed or was refunded
2672
+ const hasAnyCallFailed = transactionStates.some((tx) =>
2673
+ tx?.decodedGuestModuleEvents?.some(
2674
+ (e: any) => e?.type === "CallFailed",
2675
+ ),
2676
+ )
2677
+ const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
2678
+ const txStatus =
2679
+ !hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
2680
+
2681
+ // Always track payment completion regardless of success/failure
2442
2682
  trackPaymentCompleted({
2443
2683
  userAddress: account.address,
2444
2684
  originTxHash: originUserTxReceipt.transactionHash,
@@ -2450,9 +2690,9 @@ async function sendHandlerForSameChainSameToken({
2450
2690
  destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
2451
2691
  })
2452
2692
 
2453
- // Call onCheckoutComplete callback if provided
2693
+ // Call onCheckoutComplete callback with transaction status
2454
2694
  if (checkoutOnHandlers?.triggerCheckoutComplete) {
2455
- checkoutOnHandlers.triggerCheckoutComplete()
2695
+ checkoutOnHandlers.triggerCheckoutComplete(txStatus)
2456
2696
  }
2457
2697
  } else if (originUserTxReceipt) {
2458
2698
  trackPaymentError({
@@ -2607,7 +2847,7 @@ async function _sendHandlerForSameChainDifferentToken({
2607
2847
  }
2608
2848
  }
2609
2849
  } catch (error) {
2610
- console.error("[trails-sdk] Error decoding function data:", error)
2850
+ logger.console.error("[trails-sdk] Error decoding function data:", error)
2611
2851
  }
2612
2852
  }
2613
2853
 
@@ -2793,7 +3033,7 @@ async function attemptGaslessDeposit({
2793
3033
  chain: Chain
2794
3034
  account: Account
2795
3035
  trailsClient: TrailsAPIClient
2796
- originRelayer: RpcRelayer.RpcRelayer
3036
+ originRelayer: Relayer.RpcRelayer
2797
3037
  feeOptions: any
2798
3038
  selectedFeeToken?: any
2799
3039
  }): Promise<TransactionReceipt | null> {
@@ -2900,7 +3140,7 @@ async function attemptGaslessDeposit({
2900
3140
  feeOptions && feeOptions.feeOptions?.length > 0,
2901
3141
  )
2902
3142
 
2903
- // 1. Check if we need approval - check if we have enough allowance for this transaction (deposit + fee)
3143
+ // 1. Check if we need approval - check if we have enough allowance for this transaction (deposit + fee if same token)
2904
3144
  let requiredAmount = BigInt(depositTokenAmount)
2905
3145
 
2906
3146
  // Match selectedFeeToken by tokenAddress to get the latest fee amount from feeOptions
@@ -2930,14 +3170,36 @@ async function attemptGaslessDeposit({
2930
3170
  )
2931
3171
  }
2932
3172
 
2933
- if (selectedFeeOption?.amount) {
2934
- requiredAmount = requiredAmount + BigInt(selectedFeeOption.amount)
2935
- logger.console.log("[trails-sdk] Including fee in required amount:", {
2936
- depositAmount: depositTokenAmount,
2937
- feeAmount: selectedFeeOption.amount,
2938
- feeTokenAddress: selectedFeeOption.tokenAddress,
2939
- totalRequired: requiredAmount.toString(),
2940
- })
3173
+ // Only include fee in required amount if the fee token is the same as the deposit token
3174
+ if (selectedFeeOption?.amount && selectedFeeOption?.tokenAddress) {
3175
+ const feeTokenIsSameAsDepositToken =
3176
+ selectedFeeOption.tokenAddress.toLowerCase() ===
3177
+ depositTokenAddress.toLowerCase()
3178
+
3179
+ if (feeTokenIsSameAsDepositToken) {
3180
+ requiredAmount = requiredAmount + BigInt(selectedFeeOption.amount)
3181
+ logger.console.log(
3182
+ "[trails-sdk] Fee token matches deposit token, including fee in required approval amount:",
3183
+ {
3184
+ depositAmount: depositTokenAmount,
3185
+ feeAmount: selectedFeeOption.amount,
3186
+ feeTokenAddress: selectedFeeOption.tokenAddress,
3187
+ depositTokenAddress,
3188
+ totalRequired: requiredAmount.toString(),
3189
+ },
3190
+ )
3191
+ } else {
3192
+ logger.console.log(
3193
+ "[trails-sdk] Fee token differs from deposit token, separate approval will be needed:",
3194
+ {
3195
+ depositAmount: depositTokenAmount,
3196
+ depositTokenAddress,
3197
+ feeAmount: selectedFeeOption.amount,
3198
+ feeTokenAddress: selectedFeeOption.tokenAddress,
3199
+ depositTokenRequired: requiredAmount.toString(),
3200
+ },
3201
+ )
3202
+ }
2941
3203
  }
2942
3204
 
2943
3205
  const needsApproval = await getNeedsIntentEntrypointApproval({
@@ -2962,13 +3224,14 @@ async function attemptGaslessDeposit({
2962
3224
  },
2963
3225
  )
2964
3226
 
2965
- // 2. Get permit signature if approval needed
3227
+ // 2. Get permit signature if approval needed for deposit token
3228
+ // Note: Fee payment is handled by the backend/relayer, we only need permit for the deposit token
2966
3229
  let permitSignature: string | undefined
2967
3230
  let permitDeadline: number | undefined
2968
3231
 
2969
3232
  if (needsApproval) {
2970
3233
  logger.console.log(
2971
- "[trails-sdk] Getting permit signature for infinite approval",
3234
+ "[trails-sdk] Getting permit signature for deposit token infinite approval",
2972
3235
  )
2973
3236
 
2974
3237
  // Use infinite approval (maxUint256) so user doesn't need to approve again
@@ -2977,6 +3240,7 @@ async function attemptGaslessDeposit({
2977
3240
  "[trails-sdk] Using infinite approval for gasless deposits",
2978
3241
  {
2979
3242
  depositAmount: depositTokenAmount,
3243
+ depositTokenAddress,
2980
3244
  permitAmount: permitAmount.toString(),
2981
3245
  },
2982
3246
  )
@@ -2994,7 +3258,7 @@ async function attemptGaslessDeposit({
2994
3258
  permitSignature = permitSig.signature
2995
3259
  permitDeadline = Number(permitSig.deadline)
2996
3260
  logger.console.log(
2997
- "[trails-sdk] Permit signature obtained for infinite approval",
3261
+ "[trails-sdk] Deposit token permit signature obtained for infinite approval",
2998
3262
  )
2999
3263
  }
3000
3264
 
@@ -3221,24 +3485,31 @@ export async function attemptNonGaslessUserDeposit({
3221
3485
 
3222
3486
  logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
3223
3487
 
3224
- const originCallParams = {
3225
- to:
3226
- originTokenAddress === zeroAddress
3227
- ? originIntentAddress
3228
- : originTokenAddress,
3229
- data:
3230
- originTokenAddress === zeroAddress
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,
3488
+ // Build origin call params
3489
+ const originCallParamsBase = buildCrossChainDepositParams({
3490
+ originTokenAddress,
3491
+ originIntentAddress,
3492
+ depositAmount: firstPreconditionMin,
3493
+ fee,
3494
+ originChainId,
3241
3495
  chain,
3496
+ })
3497
+
3498
+ // Estimate gas limit for consistency with actual transaction
3499
+ const gasLimit = await estimateGasLimit(
3500
+ publicClient,
3501
+ {
3502
+ account: account.address,
3503
+ to: originCallParamsBase.to,
3504
+ data: originCallParamsBase.data,
3505
+ value: BigInt(originCallParamsBase.value),
3506
+ },
3507
+ "deposit",
3508
+ )
3509
+
3510
+ const originCallParams = {
3511
+ ...originCallParamsBase,
3512
+ gasLimit,
3242
3513
  }
3243
3514
 
3244
3515
  await attemptSwitchChain({
@@ -3494,13 +3765,14 @@ async function attemptUserDepositTx({
3494
3765
  feeOptions,
3495
3766
  trailsClient,
3496
3767
  selectedFeeToken,
3768
+ walletId,
3497
3769
  }: {
3498
3770
  originTokenAddress: string
3499
3771
  gasless: boolean
3500
3772
  paymasterUrl?: string
3501
3773
  chain: Chain
3502
3774
  account: Account
3503
- originRelayer: RpcRelayer.RpcRelayer
3775
+ originRelayer: Relayer.RpcRelayer
3504
3776
  firstPreconditionMin: string
3505
3777
  originIntentAddress: string
3506
3778
  onOriginSend?: () => void
@@ -3522,6 +3794,7 @@ async function attemptUserDepositTx({
3522
3794
  feeOptions?: any
3523
3795
  trailsClient: TrailsAPIClient
3524
3796
  selectedFeeToken?: any
3797
+ walletId?: string
3525
3798
  }): Promise<TransactionReceipt | null> {
3526
3799
  let originUserTxReceipt: TransactionReceipt | null = null
3527
3800
  const originChainId = chain.id
@@ -3545,6 +3818,7 @@ async function attemptUserDepositTx({
3545
3818
  gasless,
3546
3819
  feeOptions,
3547
3820
  selectedFeeToken,
3821
+ walletId,
3548
3822
  )
3549
3823
  logger.console.log(
3550
3824
  "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] doGasless check results:",
@@ -3587,12 +3861,15 @@ async function attemptUserDepositTx({
3587
3861
  })
3588
3862
  } catch (error) {
3589
3863
  logger.console.log("[trails-sdk] gassless attempt failed", error)
3590
- throw error
3864
+ // In strict gasless mode, re-throw error instead of falling back
3865
+ if (gasless) {
3866
+ throw error
3867
+ }
3591
3868
  }
3592
3869
  }
3593
3870
 
3594
3871
  // If gasless attempt failed, try to send a regular transaction
3595
- if (!originUserTxReceipt) {
3872
+ if (!originUserTxReceipt && !gasless) {
3596
3873
  originUserTxReceipt = await attemptNonGaslessUserDeposit({
3597
3874
  originTokenAddress,
3598
3875
  firstPreconditionMin,
@@ -3626,7 +3903,16 @@ export function getDoGasless(
3626
3903
  gasless: boolean,
3627
3904
  feeOptions?: any,
3628
3905
  selectedFeeToken?: any,
3906
+ walletId?: string,
3629
3907
  ): boolean {
3908
+ // Don't use gasless flow for sequence-waas wallet
3909
+ if (walletId === "sequence-waas") {
3910
+ logger.console.log(
3911
+ "[trails-sdk] [GASLESS-FLOW] getDoGasless: Skipping gasless flow for sequence-waas wallet",
3912
+ )
3913
+ return false
3914
+ }
3915
+
3630
3916
  const hasFeeOptions = Boolean(feeOptions && feeOptions.feeOptions?.length > 0)
3631
3917
 
3632
3918
  // Important: The UI passes selectedFeeToken in these states:
@@ -3763,7 +4049,7 @@ async function sendMetaTxAndWaitForReceipt({
3763
4049
  feeQuote,
3764
4050
  }: {
3765
4051
  metaTx: MetaTxn
3766
- relayer: RpcRelayer.RpcRelayer
4052
+ relayer: Relayer.RpcRelayer
3767
4053
  precondition: IntentPrecondition | null
3768
4054
  feeQuote?: string
3769
4055
  }): Promise<{
@@ -3922,7 +4208,7 @@ async function checkAccountBalance({
3922
4208
  balanceError,
3923
4209
  }
3924
4210
  } catch (error) {
3925
- console.error("[trails-sdk] Error checking account balance:", error)
4211
+ logger.console.error("[trails-sdk] Error checking account balance:", error)
3926
4212
  return {
3927
4213
  hasEnoughBalance: false,
3928
4214
  balance: BigInt(0),
@@ -4014,6 +4300,10 @@ export type UseQuoteProps = {
4014
4300
  quoteProvider?: string | null
4015
4301
  gasless?: boolean
4016
4302
  paymasterUrl?: string
4303
+ selectedFeeToken?: {
4304
+ tokenAddress: string
4305
+ tokenSymbol?: string
4306
+ } | null
4017
4307
  }
4018
4308
 
4019
4309
  export type SwapReturn = {
@@ -4057,6 +4347,8 @@ export type Quote = {
4057
4347
  toAmountUsdDisplay?: string
4058
4348
  gasCostUsd?: number
4059
4349
  gasCostUsdDisplay?: string
4350
+ gasCost?: string
4351
+ gasCostFormatted?: string
4060
4352
  }
4061
4353
 
4062
4354
  export type UseQuoteReturn = {
@@ -4082,6 +4374,7 @@ export function useQuote({
4082
4374
  quoteProvider,
4083
4375
  gasless,
4084
4376
  paymasterUrl,
4377
+ selectedFeeToken,
4085
4378
  relayerEnv,
4086
4379
  nodeGatewayEnv,
4087
4380
  }: Partial<
@@ -4223,6 +4516,7 @@ export function useQuote({
4223
4516
  quoteProvider: quoteProvider,
4224
4517
  gasless: gasless ?? false,
4225
4518
  paymasterUrl: paymasterUrl,
4519
+ selectedFeeToken: selectedFeeToken ?? undefined,
4226
4520
  }
4227
4521
 
4228
4522
  logger.console.log("[trails-sdk] options", options)
@@ -4252,6 +4546,8 @@ export function useQuote({
4252
4546
  prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
4253
4547
  gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
4254
4548
  gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
4549
+ gasCost: prepareSendQuote.gasCost ?? undefined,
4550
+ gasCostFormatted: prepareSendQuote.gasCostFormatted ?? undefined,
4255
4551
  }
4256
4552
 
4257
4553
  const swap = async (): Promise<SwapReturn> => {
@@ -4259,7 +4555,9 @@ export function useQuote({
4259
4555
  originUserTxReceipt,
4260
4556
  destinationMetaTxnReceipt,
4261
4557
  totalCompletionSeconds,
4262
- } = await send({})
4558
+ } = await send({
4559
+ selectedFeeToken: selectedFeeToken ?? undefined,
4560
+ })
4263
4561
 
4264
4562
  return {
4265
4563
  originTransaction: {
@@ -4447,6 +4745,7 @@ export async function getNormalizedQuoteObject({
4447
4745
  originNativeTokenPriceUsd,
4448
4746
  quoteProvider,
4449
4747
  noSufficientBalance,
4748
+ estimatedGasLimit,
4450
4749
  }: {
4451
4750
  originDepositAddress?: string
4452
4751
  destinationDepositAddress?: string
@@ -4470,6 +4769,7 @@ export async function getNormalizedQuoteObject({
4470
4769
  originNativeTokenPriceUsd?: number | null
4471
4770
  quoteProvider?: string
4472
4771
  noSufficientBalance?: boolean
4772
+ estimatedGasLimit?: bigint
4473
4773
  }): Promise<PrepareSendQuote> {
4474
4774
  if (!destinationChainId) {
4475
4775
  throw new Error("Destination chain id is required")
@@ -4497,7 +4797,7 @@ export async function getNormalizedQuoteObject({
4497
4797
  const destinationChain = getChainInfo(destinationChainId)
4498
4798
 
4499
4799
  if (!originToken || !destinationToken || !originChain || !destinationChain) {
4500
- console.error("[trails-sdk] Token or chain not found", {
4800
+ logger.console.error("[trails-sdk] Token or chain not found", {
4501
4801
  originToken,
4502
4802
  destinationToken,
4503
4803
  originChain,
@@ -4571,17 +4871,52 @@ export async function getNormalizedQuoteObject({
4571
4871
 
4572
4872
  let gasCostUsd: number = 0
4573
4873
  let gasCostUsdDisplay: string = "0"
4874
+ let gasCost: string = "0"
4875
+ let gasCostFormatted: string = "0"
4574
4876
  try {
4575
4877
  if (originNativeTokenPriceUsd) {
4878
+ // Use the actual estimated gas limit if provided, otherwise use default
4879
+ // This ensures the quote gas cost matches what will be used in the actual transaction
4880
+ const gasLimitForCost = estimatedGasLimit || DEFAULT_MIN_GASLIMIT
4881
+
4882
+ logger.console.log(
4883
+ "[trails-sdk][gas-estimation] Calculating gas cost for quote with gasLimit:",
4884
+ gasLimitForCost,
4885
+ "(estimated:",
4886
+ estimatedGasLimit,
4887
+ "default:",
4888
+ DEFAULT_MIN_GASLIMIT,
4889
+ ")",
4890
+ )
4891
+
4892
+ const gasCostWei = await estimateGasCost(publicClient, gasLimitForCost)
4893
+ gasCost = gasCostWei.toString()
4894
+ gasCostFormatted = formatUnits(gasCostWei, 18) // Native tokens always use 18 decimals
4895
+
4896
+ // Calculate USD value
4576
4897
  gasCostUsd = await estimateGasCostUsd(
4577
4898
  publicClient,
4578
4899
  originNativeTokenPriceUsd,
4579
- 200_000n,
4900
+ gasLimitForCost,
4580
4901
  )
4581
4902
  gasCostUsdDisplay = formatUsdAmountDisplay(gasCostUsd)
4903
+
4904
+ logger.console.log(
4905
+ "[trails-sdk][gas-estimation] Quote gas cost calculated:",
4906
+ {
4907
+ gasCostWei,
4908
+ gasCost,
4909
+ gasCostFormatted,
4910
+ gasCostUsd,
4911
+ gasCostUsdDisplay,
4912
+ },
4913
+ )
4582
4914
  }
4583
4915
  } catch (error) {
4584
- console.error("[trails-sdk] Error estimating gas cost", error)
4916
+ logger.console.error(
4917
+ "[trails-sdk][gas-estimation] Error estimating gas cost for quote display",
4918
+ error,
4919
+ )
4585
4920
  }
4586
4921
 
4587
4922
  // Calculate exchange rates
@@ -4657,6 +4992,8 @@ export async function getNormalizedQuoteObject({
4657
4992
  transactionStates: transactionStates || [],
4658
4993
  gasCostUsd,
4659
4994
  gasCostUsdDisplay,
4995
+ gasCost,
4996
+ gasCostFormatted,
4660
4997
  originTokenRate: exchangeRates.originTokenRate,
4661
4998
  destinationTokenRate: exchangeRates.destinationTokenRate,
4662
4999
  originAmountDisplay: formatAmountDisplay(originAmountFormatted),