0xtrails 0.2.2 → 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 (194) hide show
  1. package/dist/aave.d.ts +8 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/{ccip-ConT1gDe.js → ccip-CXlshvBY.js} +1 -1
  4. package/dist/chains.d.ts +5 -1
  5. package/dist/chains.d.ts.map +1 -1
  6. package/dist/config.d.ts +1 -1
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/constants.d.ts +5 -4
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/error.d.ts +1 -0
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/estimate.d.ts +52 -0
  13. package/dist/estimate.d.ts.map +1 -1
  14. package/dist/{index-CMh8uEbV.js → index-_QuyGrjU.js} +86304 -83380
  15. package/dist/index.d.ts +4 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +2 -2
  18. package/dist/intentEntrypoint.d.ts +0 -8
  19. package/dist/intentEntrypoint.d.ts.map +1 -1
  20. package/dist/intents.d.ts +40 -0
  21. package/dist/intents.d.ts.map +1 -1
  22. package/dist/metaTxnMonitor.d.ts +5 -4
  23. package/dist/metaTxnMonitor.d.ts.map +1 -1
  24. package/dist/metaTxns.d.ts +3 -3
  25. package/dist/metaTxns.d.ts.map +1 -1
  26. package/dist/morpho.d.ts +8 -0
  27. package/dist/morpho.d.ts.map +1 -1
  28. package/dist/prepareSend.d.ts +16 -6
  29. package/dist/prepareSend.d.ts.map +1 -1
  30. package/dist/queryParams.d.ts.map +1 -1
  31. package/dist/relayer.d.ts +10 -7
  32. package/dist/relayer.d.ts.map +1 -1
  33. package/dist/sequenceWallet.d.ts +3 -2
  34. package/dist/sequenceWallet.d.ts.map +1 -1
  35. package/dist/tokenBalances.d.ts +7 -0
  36. package/dist/tokenBalances.d.ts.map +1 -1
  37. package/dist/tokens.d.ts +2 -1
  38. package/dist/tokens.d.ts.map +1 -1
  39. package/dist/trails.d.ts +2 -2
  40. package/dist/trails.d.ts.map +1 -1
  41. package/dist/wallets.d.ts.map +1 -1
  42. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  43. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  44. package/dist/widget/components/ClassicSwap.d.ts +2 -0
  45. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  46. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  47. package/dist/widget/components/ConnectedWallets.d.ts +4 -0
  48. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  49. package/dist/widget/components/Earn.d.ts.map +1 -1
  50. package/dist/widget/components/EarnPools.d.ts.map +1 -1
  51. package/dist/widget/components/Fund.d.ts +1 -0
  52. package/dist/widget/components/Fund.d.ts.map +1 -1
  53. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  54. package/dist/widget/components/{FundSendForm.d.ts → FundSwap.d.ts} +11 -5
  55. package/dist/widget/components/FundSwap.d.ts.map +1 -0
  56. package/dist/widget/components/FundingMethodSelectorButton.d.ts +4 -0
  57. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -0
  58. package/dist/widget/components/Modal.d.ts.map +1 -1
  59. package/dist/widget/components/Pay.d.ts +1 -0
  60. package/dist/widget/components/Pay.d.ts.map +1 -1
  61. package/dist/widget/components/PercentageMaxButtons.d.ts +12 -0
  62. package/dist/widget/components/PercentageMaxButtons.d.ts.map +1 -0
  63. package/dist/widget/components/{PaySendForm.d.ts → PoolDeposit.d.ts} +11 -34
  64. package/dist/widget/components/PoolDeposit.d.ts.map +1 -0
  65. package/dist/widget/components/{SimpleSwap.d.ts → PoolWithdraw.d.ts} +16 -8
  66. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -0
  67. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  68. package/dist/widget/components/Receive.d.ts.map +1 -1
  69. package/dist/widget/components/RecipientSelectorButton.d.ts +4 -0
  70. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -0
  71. package/dist/widget/components/Recipients.d.ts.map +1 -1
  72. package/dist/widget/components/RefundWarning.d.ts +1 -0
  73. package/dist/widget/components/RefundWarning.d.ts.map +1 -1
  74. package/dist/widget/components/RequiredPropsError.d.ts +8 -0
  75. package/dist/widget/components/RequiredPropsError.d.ts.map +1 -0
  76. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  77. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  78. package/dist/widget/components/Swap.d.ts +1 -0
  79. package/dist/widget/components/Swap.d.ts.map +1 -1
  80. package/dist/widget/components/SwapSettings.d.ts.map +1 -1
  81. package/dist/widget/components/TokenImage.d.ts +1 -0
  82. package/dist/widget/components/TokenImage.d.ts.map +1 -1
  83. package/dist/widget/components/TokenList.d.ts.map +1 -1
  84. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  85. package/dist/widget/components/TokenSelectorButton.d.ts +16 -0
  86. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -0
  87. package/dist/widget/components/UserPreferences.d.ts.map +1 -1
  88. package/dist/widget/components/WaasFeeOptions.d.ts +8 -0
  89. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -0
  90. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  91. package/dist/widget/components/WalletList.d.ts.map +1 -1
  92. package/dist/widget/css/compiled.css +2 -0
  93. package/dist/widget/css/index.css +554 -0
  94. package/dist/widget/hooks/useBack.d.ts +6 -0
  95. package/dist/widget/hooks/useBack.d.ts.map +1 -1
  96. package/dist/widget/hooks/useCheckout.d.ts +1 -1
  97. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  98. package/dist/widget/hooks/useCurrentScreen.d.ts +1 -1
  99. package/dist/widget/hooks/useCurrentScreen.d.ts.map +1 -1
  100. package/dist/widget/hooks/useDefaultTokenSelection.d.ts +3 -3
  101. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  102. package/dist/widget/hooks/useInitialRedirect.d.ts +7 -0
  103. package/dist/widget/hooks/useInitialRedirect.d.ts.map +1 -0
  104. package/dist/widget/hooks/usePayMessage.d.ts.map +1 -1
  105. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -1
  106. package/dist/widget/hooks/useSelectedFundMethod.d.ts +12 -0
  107. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -0
  108. package/dist/widget/hooks/useSelectedRecipient.d.ts.map +1 -1
  109. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  110. package/dist/widget/index.js +1 -1
  111. package/dist/widget/widget.d.ts +4 -4
  112. package/dist/widget/widget.d.ts.map +1 -1
  113. package/package.json +30 -23
  114. package/src/aave.ts +32 -0
  115. package/src/chains.ts +23 -3
  116. package/src/config.ts +12 -4
  117. package/src/constants.ts +11 -16
  118. package/src/error.ts +20 -2
  119. package/src/estimate.ts +416 -5
  120. package/src/index.ts +8 -3
  121. package/src/intentEntrypoint.ts +0 -15
  122. package/src/intents.ts +161 -11
  123. package/src/metaTxnMonitor.ts +28 -22
  124. package/src/metaTxns.ts +3 -3
  125. package/src/morpho.ts +32 -0
  126. package/src/prepareSend.ts +710 -458
  127. package/src/queryParams.ts +2 -1
  128. package/src/relayer.ts +15 -16
  129. package/src/sequenceWallet.ts +7 -3
  130. package/src/tokenBalances.ts +47 -0
  131. package/src/tokens.ts +17 -1
  132. package/src/trails.ts +2 -2
  133. package/src/wallets.ts +8 -0
  134. package/src/widget/compiled.css +2 -2
  135. package/src/widget/components/AccountActionsDropdown.tsx +9 -15
  136. package/src/widget/components/AccountIntentTransactionHistory.tsx +1 -1
  137. package/src/widget/components/AccountSettings.tsx +10 -27
  138. package/src/widget/components/ChainFilterDropdown.tsx +1 -1
  139. package/src/widget/components/ChainList.tsx +1 -1
  140. package/src/widget/components/ClassicSwap.tsx +111 -155
  141. package/src/widget/components/ConnectWallet.tsx +10 -39
  142. package/src/widget/components/ConnectedWallets.tsx +113 -58
  143. package/src/widget/components/Earn.tsx +73 -589
  144. package/src/widget/components/EarnPools.tsx +2 -1
  145. package/src/widget/components/Fund.tsx +81 -109
  146. package/src/widget/components/FundMethods.tsx +82 -159
  147. package/src/widget/components/FundSwap.tsx +52 -0
  148. package/src/widget/components/FundingMethodSelectorButton.tsx +60 -0
  149. package/src/widget/components/Modal.tsx +6 -2
  150. package/src/widget/components/Pay.tsx +198 -200
  151. package/src/widget/components/PercentageMaxButtons.tsx +77 -0
  152. package/src/widget/components/PoolDeposit.tsx +593 -0
  153. package/src/widget/components/PoolWithdraw.tsx +903 -0
  154. package/src/widget/components/QuoteDetails.tsx +22 -8
  155. package/src/widget/components/Receive.tsx +1 -3
  156. package/src/widget/components/RecipientSelectorButton.tsx +42 -0
  157. package/src/widget/components/Recipients.tsx +64 -156
  158. package/src/widget/components/RefundWarning.tsx +5 -1
  159. package/src/widget/components/RequiredPropsError.tsx +33 -0
  160. package/src/widget/components/ScreenHeader.tsx +5 -1
  161. package/src/widget/components/SlippageToleranceSettings.tsx +2 -1
  162. package/src/widget/components/Swap.tsx +2 -43
  163. package/src/widget/components/SwapSettings.tsx +3 -15
  164. package/src/widget/components/TokenImage.tsx +21 -4
  165. package/src/widget/components/TokenList.tsx +0 -1
  166. package/src/widget/components/TokenSelector.tsx +2 -1
  167. package/src/widget/components/TokenSelectorButton.tsx +75 -0
  168. package/src/widget/components/UserPreferences.tsx +6 -24
  169. package/src/widget/components/WaasFeeOptions.tsx +331 -0
  170. package/src/widget/components/WalletConfirmation.tsx +55 -3
  171. package/src/widget/components/WalletList.tsx +7 -5
  172. package/src/widget/hooks/useBack.tsx +113 -9
  173. package/src/widget/hooks/useCheckout.ts +36 -20
  174. package/src/widget/hooks/useCurrentScreen.tsx +1 -0
  175. package/src/widget/hooks/useDefaultTokenSelection.tsx +104 -28
  176. package/src/widget/hooks/useInitialRedirect.tsx +70 -0
  177. package/src/widget/hooks/usePayMessage.tsx +86 -11
  178. package/src/widget/hooks/useSelectedFeeToken.tsx +10 -16
  179. package/src/widget/hooks/useSelectedFundMethod.tsx +41 -0
  180. package/src/widget/hooks/useSelectedRecipient.tsx +10 -0
  181. package/src/widget/hooks/useSendForm.ts +34 -12
  182. package/src/widget/hooks/useTokenList.ts +1 -1
  183. package/src/widget/index.css +27 -0
  184. package/src/widget/widget.tsx +245 -208
  185. package/dist/widget/components/FundSendForm.d.ts.map +0 -1
  186. package/dist/widget/components/PaySendForm.d.ts.map +0 -1
  187. package/dist/widget/components/SimpleSwap.d.ts.map +0 -1
  188. package/dist/widget/hooks/useSwapSettings.d.ts +0 -16
  189. package/dist/widget/hooks/useSwapSettings.d.ts.map +0 -1
  190. package/src/widget/components/FundSendForm.tsx +0 -903
  191. package/src/widget/components/PaySendForm.tsx +0 -869
  192. package/src/widget/components/SimpleSwap.tsx +0 -983
  193. package/src/widget/hooks/useSwapSettings.tsx +0 -100
  194. /package/dist/{style.css → 0xtrails.css} +0 -0
@@ -4,7 +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/wallet-core"
7
+ import type { Relayer } from "@0xsequence/relayer"
8
8
  import { useQuery } from "@tanstack/react-query"
9
9
  import type {
10
10
  Account,
@@ -56,28 +56,31 @@ import {
56
56
  getSlippageTolerance,
57
57
  type SequenceEnv,
58
58
  } from "./config.js"
59
- import { intentEntrypoints } from "./constants.js"
59
+ import { TRAILS_INTENT_ENTRYPOINT_ADDRESS } from "./constants.js"
60
60
  import {
61
61
  decodeGuestModuleEvents,
62
62
  decodeTrailsTokenSweeperEvents,
63
63
  } from "./decoders.js"
64
64
  import { getERC20TransferData } from "./encoders.js"
65
- import { InsufficientBalanceError } from "./error.js"
66
- import { estimateGasCostUsd } from "./estimate.js"
65
+ import { getFullErrorMessage, InsufficientBalanceError } from "./error.js"
66
+ import {
67
+ estimateGasCost,
68
+ estimateGasCostUsd,
69
+ estimateGasLimit,
70
+ DEFAULT_MIN_GASLIMIT,
71
+ } from "./estimate.js"
67
72
  import { getExplorerUrl } from "./explorer.js"
68
73
  import {
69
74
  getNeedsIntentEntrypointApproval,
70
- getPermitCalls,
71
75
  getPermitSignature,
72
76
  getUserNonce,
73
77
  signIntent,
74
78
  } from "./gasless.js"
75
- import {
76
- getIntentEntrypointFeeOptions,
77
- isIntentEntrypointSupported,
78
- } from "./intentEntrypoint.js"
79
+ import { getIntentEntrypointFeeOptions } from "./intentEntrypoint.js"
79
80
  import { useIndexerGatewayClient } from "./indexerClient.js"
80
81
  import {
82
+ buildSameChainTransactionParams,
83
+ buildCrossChainDepositParams,
81
84
  commitIntentConfig,
82
85
  getIntentCallsPayloads as getIntentCallsPayloadsFromIntents,
83
86
  sendOriginTransaction,
@@ -86,11 +89,6 @@ import type { IntentRequestParams } from "./intents.js"
86
89
  import type { MetaTxn } from "./metaTxnMonitor.js"
87
90
  import { getMetaTxStatus } from "./metaTxnMonitor.js"
88
91
  import { relayerSendMetaTx } from "./metaTxns.js"
89
- import {
90
- getDelegatorSmartAccount,
91
- getPaymasterGaslessTransaction,
92
- sendPaymasterGaslessTransaction,
93
- } from "./paymasterSend.js"
94
92
  import { findFirstPreconditionForChainId } from "./preconditions.js"
95
93
  import { calcAmountUsdPrice, getTokenPrice } from "./prices.js"
96
94
  import { getQueryParam } from "./queryParams.js"
@@ -136,6 +134,11 @@ import { getIsCustomCalldata } from "./contractUtils.js"
136
134
  import type { SequenceAPIClient } from "@0xsequence/api"
137
135
  import { useTrailsClient } from "./trailsClient.js"
138
136
  import { updatePersistentToast } from "./toast.js"
137
+ import {
138
+ getDelegatorSmartAccount,
139
+ getPaymasterGaslessTransaction,
140
+ sendPaymasterGaslessTransaction,
141
+ } from "./paymasterSend.js"
139
142
 
140
143
  export enum TradeType {
141
144
  EXACT_INPUT = "EXACT_INPUT",
@@ -160,8 +163,8 @@ export type PrepareSendOptions = {
160
163
  dryMode: boolean
161
164
  apiClient: SequenceAPIClient
162
165
  trailsClient: TrailsAPIClient
163
- originRelayer: Relayer.Standard.Rpc.RpcRelayer
164
- destinationRelayer: Relayer.Standard.Rpc.RpcRelayer
166
+ originRelayer: Relayer.RpcRelayer
167
+ destinationRelayer: Relayer.RpcRelayer
165
168
  destinationCalldata?: string
166
169
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
167
170
  sourceTokenPriceUsd?: number | null
@@ -178,6 +181,7 @@ export type PrepareSendOptions = {
178
181
  checkoutOnHandlers?: CheckoutOnHandlers
179
182
  refundAddress?: string
180
183
  selectedFeeToken?: any
184
+ walletId?: string
181
185
  }
182
186
 
183
187
  export type PrepareSendFees = {
@@ -222,6 +226,8 @@ export type PrepareSendQuote = {
222
226
  transactionStates: TransactionState[]
223
227
  gasCostUsd: number
224
228
  gasCostUsdDisplay: string
229
+ gasCost: string
230
+ gasCostFormatted: string
225
231
  originTokenRate: string
226
232
  destinationTokenRate: string
227
233
  quoteProvider: QuoteProviderInfo | null
@@ -389,6 +395,7 @@ export async function prepareSend(
389
395
  mode,
390
396
  checkoutOnHandlers,
391
397
  selectedFeeToken,
398
+ walletId,
392
399
  } = options
393
400
  let { sourceTokenPriceUsd, destinationTokenPriceUsd } = options
394
401
 
@@ -572,18 +579,20 @@ export async function prepareSend(
572
579
  label: isToSameChain && isToSameToken ? "Execute" : "Transfer",
573
580
  })
574
581
 
575
- // swap (+ bridge tx)
576
- transactionStates.push({
577
- transactionHash: "",
578
- explorerUrl: "",
579
- chainId: originChainId,
580
- state: "pending",
581
- label: isToSameToken
582
- ? "Bridge"
583
- : isToSameChain && !isToSameToken
584
- ? "Swap"
585
- : "Swap & Bridge",
586
- })
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
+ }
587
596
 
588
597
  // additional execute tx on same chain if needed
589
598
  if (isToSameChain && hasCustomCalldata && !isToSameToken) {
@@ -656,6 +665,13 @@ export async function prepareSend(
656
665
  originNativeTokenPriceUsd,
657
666
  slippageTolerance,
658
667
  checkoutOnHandlers,
668
+ gasless,
669
+ paymasterUrl,
670
+ selectedFeeToken,
671
+ trailsClient,
672
+ originRelayer,
673
+ mode,
674
+ fundMethod,
659
675
  })
660
676
  }
661
677
 
@@ -697,6 +713,7 @@ export async function prepareSend(
697
713
  mode,
698
714
  checkoutOnHandlers,
699
715
  selectedFeeToken,
716
+ walletId,
700
717
  })
701
718
  }
702
719
 
@@ -737,6 +754,7 @@ async function sendHandlerForDifferentChainDifferentToken({
737
754
  mode,
738
755
  checkoutOnHandlers,
739
756
  selectedFeeToken,
757
+ walletId,
740
758
  }: {
741
759
  mainSignerAddress: string
742
760
  originChainId: number
@@ -757,8 +775,8 @@ async function sendHandlerForDifferentChainDifferentToken({
757
775
  destinationTokenDecimals: number
758
776
  gasless: boolean
759
777
  paymasterUrl?: string
760
- originRelayer: Relayer.Standard.Rpc.RpcRelayer
761
- destinationRelayer: Relayer.Standard.Rpc.RpcRelayer
778
+ originRelayer: Relayer.RpcRelayer
779
+ destinationRelayer: Relayer.RpcRelayer
762
780
  walletClient: WalletClient
763
781
  publicClient: PublicClient
764
782
  chain: Chain
@@ -775,6 +793,7 @@ async function sendHandlerForDifferentChainDifferentToken({
775
793
  mode?: "pay" | "fund" | "earn" | "swap" | "receive"
776
794
  checkoutOnHandlers?: CheckoutOnHandlers
777
795
  selectedFeeToken?: any
796
+ walletId?: string
778
797
  }): Promise<PrepareSendReturn> {
779
798
  const testnet = isTestnetDebugMode()
780
799
  const useCctp = getUseCctp(
@@ -1124,6 +1143,42 @@ async function sendHandlerForDifferentChainDifferentToken({
1124
1143
  }
1125
1144
  }
1126
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
+
1127
1182
  const quote = await getNormalizedQuoteObject({
1128
1183
  originDepositAddress: originIntentAddress,
1129
1184
  destinationDepositAddress: intent.payloads.destinationIntentAddress,
@@ -1151,6 +1206,7 @@ async function sendHandlerForDifferentChainDifferentToken({
1151
1206
  originNativeTokenPriceUsd,
1152
1207
  quoteProvider: intent.payloads?.quote?.quoteProvider,
1153
1208
  noSufficientBalance,
1209
+ estimatedGasLimit: estimatedGasLimitForQuote,
1154
1210
  })
1155
1211
 
1156
1212
  // Call onCheckoutQuote callback if provided
@@ -1158,45 +1214,43 @@ async function sendHandlerForDifferentChainDifferentToken({
1158
1214
  checkoutOnHandlers.triggerCheckoutQuote(quote)
1159
1215
  }
1160
1216
 
1161
- // 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
1162
1221
  let intentEntrypointFeeOptions: any = null
1163
- if (gasless && fundMethod === "wallet") {
1222
+ if (gasless && walletId !== "sequence-waas") {
1164
1223
  try {
1165
1224
  logger.console.log(
1166
- "[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:",
1167
1226
  originChainId,
1168
1227
  )
1169
- if (isIntentEntrypointSupported(originChainId)) {
1170
- logger.console.log(
1171
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Chain supported, fetching fee options...",
1172
- )
1173
- intentEntrypointFeeOptions = await getIntentEntrypointFeeOptions({
1174
- trailsClient,
1175
- userAddress: mainSignerAddress as `0x${string}`,
1176
- tokenAddress: originTokenAddress as `0x${string}`,
1177
- amount: depositAmount,
1178
- intentAddress: originIntentAddress as `0x${string}`,
1179
- chainId: originChainId,
1180
- })
1181
- logger.console.log(
1182
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Intent entrypoint fee options:",
1183
- intentEntrypointFeeOptions,
1184
- )
1185
- } else {
1186
- logger.console.log(
1187
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Chain not supported for intent entrypoint:",
1188
- originChainId,
1189
- )
1190
- }
1228
+
1229
+ intentEntrypointFeeOptions = await getIntentEntrypointFeeOptions({
1230
+ trailsClient,
1231
+ userAddress: mainSignerAddress as `0x${string}`,
1232
+ tokenAddress: originTokenAddress as `0x${string}`,
1233
+ amount: depositAmount,
1234
+ intentAddress: originIntentAddress as `0x${string}`,
1235
+ chainId: originChainId,
1236
+ })
1237
+ logger.console.log(
1238
+ "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Intent entrypoint fee options:",
1239
+ intentEntrypointFeeOptions,
1240
+ )
1191
1241
  } catch (error) {
1192
1242
  logger.console.error(
1193
1243
  "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Error getting intent entrypoint fee options:",
1194
1244
  error,
1195
1245
  )
1196
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
+ )
1197
1251
  } else {
1198
1252
  logger.console.log(
1199
- "[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",
1200
1254
  )
1201
1255
  }
1202
1256
 
@@ -1362,6 +1416,7 @@ async function sendHandlerForDifferentChainDifferentToken({
1362
1416
  feeOptions: intentEntrypointFeeOptions,
1363
1417
  trailsClient,
1364
1418
  selectedFeeToken: effectiveSelectedFeeToken,
1419
+ walletId,
1365
1420
  })
1366
1421
 
1367
1422
  if (!originUserTxReceipt) {
@@ -1814,7 +1869,7 @@ async function sendHandlerForDifferentChainDifferentToken({
1814
1869
  }
1815
1870
 
1816
1871
  checkForDepositTx().catch((error) => {
1817
- console.error("Error checking for deposit tx", error)
1872
+ logger.console.error("Error checking for deposit tx", error)
1818
1873
  })
1819
1874
 
1820
1875
  // Phase 1: Send meta transactions and queue CCTP
@@ -2032,11 +2087,14 @@ async function sendHandlerForDifferentChainDifferentToken({
2032
2087
 
2033
2088
  onTransactionStateChange(transactionStates)
2034
2089
  } catch (error) {
2035
- console.error("Error decoding destination tx events", error)
2090
+ logger.console.error(
2091
+ "Error decoding destination tx events",
2092
+ error,
2093
+ )
2036
2094
  }
2037
2095
  }
2038
2096
  } catch (error) {
2039
- console.error(
2097
+ logger.console.error(
2040
2098
  "[trails-sdk] Error waiting for destination receipt:",
2041
2099
  error,
2042
2100
  )
@@ -2072,6 +2130,17 @@ async function sendHandlerForDifferentChainDifferentToken({
2072
2130
 
2073
2131
  // Track payment completion for different chain and different token
2074
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
2075
2144
  trackPaymentCompleted({
2076
2145
  userAddress: account.address,
2077
2146
  originIntentAddress,
@@ -2092,9 +2161,9 @@ async function sendHandlerForDifferentChainDifferentToken({
2092
2161
  effectiveDestinationTokenAmountUsd?.toString(),
2093
2162
  })
2094
2163
 
2095
- // Call onCheckoutComplete callback if provided
2164
+ // Call onCheckoutComplete callback with transaction status
2096
2165
  if (checkoutOnHandlers?.triggerCheckoutComplete) {
2097
- checkoutOnHandlers.triggerCheckoutComplete()
2166
+ checkoutOnHandlers.triggerCheckoutComplete(txStatus)
2098
2167
  }
2099
2168
  } else {
2100
2169
  if (
@@ -2203,6 +2272,11 @@ async function sendHandlerForSameChainSameToken({
2203
2272
  checkoutOnHandlers,
2204
2273
  mode,
2205
2274
  fundMethod,
2275
+ gasless,
2276
+ paymasterUrl,
2277
+ selectedFeeToken,
2278
+ trailsClient,
2279
+ originRelayer,
2206
2280
  }: {
2207
2281
  originTokenAddress: string
2208
2282
  originTokenDecimals: number
@@ -2224,6 +2298,11 @@ async function sendHandlerForSameChainSameToken({
2224
2298
  checkoutOnHandlers?: CheckoutOnHandlers
2225
2299
  mode?: "pay" | "fund" | "earn" | "swap" | "receive"
2226
2300
  fundMethod?: string
2301
+ gasless?: boolean
2302
+ paymasterUrl?: string
2303
+ selectedFeeToken?: any
2304
+ trailsClient: TrailsAPIClient
2305
+ originRelayer: Relayer.RpcRelayer
2227
2306
  }): Promise<PrepareSendReturn> {
2228
2307
  logger.console.log("[trails-sdk] isToSameToken && isToSameChain")
2229
2308
  const testnet = isTestnetDebugMode()
@@ -2250,6 +2329,44 @@ async function sendHandlerForSameChainSameToken({
2250
2329
  noSufficientBalance = true
2251
2330
  }
2252
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
+
2253
2370
  const quote = await getNormalizedQuoteObject({
2254
2371
  originDepositAddress: recipient,
2255
2372
  destinationDepositAddress: recipient,
@@ -2268,6 +2385,7 @@ async function sendHandlerForSameChainSameToken({
2268
2385
  slippageTolerance,
2269
2386
  quoteProvider: "",
2270
2387
  noSufficientBalance,
2388
+ estimatedGasLimit: estimatedGasLimitForQuote,
2271
2389
  })
2272
2390
 
2273
2391
  // Call onCheckoutQuote callback if provided
@@ -2303,26 +2421,33 @@ async function sendHandlerForSameChainSameToken({
2303
2421
  })
2304
2422
 
2305
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
+
2306
2448
  const originCallParams = {
2307
- to: hasCustomCalldata
2308
- ? recipient
2309
- : effectiveOriginTokenAddress === zeroAddress
2310
- ? recipient
2311
- : effectiveOriginTokenAddress,
2312
- data: hasCustomCalldata
2313
- ? destinationCalldata
2314
- : effectiveOriginTokenAddress === zeroAddress
2315
- ? "0x"
2316
- : getERC20TransferData({
2317
- recipient,
2318
- amount: BigInt(swapAmount),
2319
- }),
2320
- value:
2321
- effectiveOriginTokenAddress === zeroAddress
2322
- ? BigInt(swapAmount)
2323
- : "0",
2324
- chainId: effectiveOriginChainId,
2325
- chain: effectiveOriginChain,
2449
+ ...originCallParamsBase,
2450
+ gasLimit,
2326
2451
  }
2327
2452
 
2328
2453
  logger.console.log("[trails-sdk] origin call params", originCallParams)
@@ -2353,105 +2478,207 @@ async function sendHandlerForSameChainSameToken({
2353
2478
  )
2354
2479
  }
2355
2480
 
2356
- 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
2357
2489
  try {
2358
- const needsApproval = await getNeedsApproval({
2359
- publicClient: effectivePublicClient,
2360
- token: effectiveOriginTokenAddress,
2361
- account: account.address,
2362
- spender: recipient,
2363
- 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(),
2364
2535
  })
2365
2536
 
2366
- if (needsApproval) {
2367
- const txHash = await approveERC20({
2368
- walletClient,
2369
- 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,
2370
2563
  spender: recipient,
2371
2564
  amount: maxUint256,
2372
- chain: effectiveOriginChain,
2373
2565
  })
2374
2566
 
2375
- logger.console.log("waiting for approve", txHash)
2376
- await effectivePublicClient.waitForTransactionReceipt({
2377
- hash: txHash,
2378
- })
2379
- 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
+ )
2380
2587
  }
2381
- } catch (error) {
2382
- logger.console.error("[trails-sdk] Error approving ERC20", error)
2383
2588
  }
2384
- }
2385
2589
 
2386
- // Show persistent toast for checkout flow
2387
- updatePersistentToast(
2388
- "Payment Started",
2389
- "Waiting for wallet confirmation...",
2390
- "info",
2391
- )
2590
+ // Show persistent toast for checkout flow
2591
+ updatePersistentToast(
2592
+ "Payment Started",
2593
+ "Waiting for wallet confirmation...",
2594
+ "info",
2595
+ )
2392
2596
 
2393
- logger.console.log(
2394
- "[trails-sdk] origin call params",
2395
- originCallParams,
2396
- )
2397
- const txHash = await sendOriginTransaction(
2398
- account,
2399
- walletClient,
2400
- originCallParams as any,
2401
- {
2402
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2403
- },
2404
- ) // TODO: Add proper type
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
2405
2609
 
2406
- logger.console.log("[trails-sdk] origin tx", txHash)
2610
+ logger.console.log("[trails-sdk] origin tx", txHash)
2407
2611
 
2408
- if (onOriginSend) {
2409
- onOriginSend()
2410
- }
2612
+ if (onOriginSend) {
2613
+ onOriginSend()
2614
+ }
2411
2615
 
2412
- // Wait for transaction receipt
2413
- const receipt = await effectivePublicClient.waitForTransactionReceipt(
2414
- {
2415
- hash: txHash,
2416
- },
2417
- )
2418
- logger.console.log("[trails-sdk] receipt", receipt)
2419
- 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
2420
2623
 
2421
- trackTransactionConfirmed({
2422
- transactionHash: txHash,
2423
- chainId: effectiveOriginChainId,
2424
- userAddress: account.address,
2425
- blockNumber: Number(receipt.blockNumber),
2426
- originTokenAddress,
2427
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2428
- })
2624
+ trackTransactionConfirmed({
2625
+ transactionHash: txHash,
2626
+ chainId: effectiveOriginChainId,
2627
+ userAddress: account.address,
2628
+ blockNumber: Number(receipt.blockNumber),
2629
+ originTokenAddress,
2630
+ depositTokenAmountUsd: depositAmountUsd?.toString(),
2631
+ })
2429
2632
 
2430
- // Remove persistent toast and show success
2431
- const chainInfo = getChainInfo(effectiveOriginChainId)
2432
- updatePersistentToast(
2433
- "Transfer Confirmed",
2434
- `Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
2435
- "info",
2436
- )
2633
+ // Toast will be shown after transaction state analysis
2437
2634
 
2438
- try {
2439
- onTransactionStateChange([
2440
- getTransactionStateFromReceipt(
2441
- originUserTxReceipt,
2442
- effectiveOriginChainId,
2443
- transactionStates[0]?.label,
2444
- ),
2445
- ])
2446
- } catch (error) {
2447
- logger.console.error(
2448
- "[trails-sdk] Error calling onTransactionStateChange:",
2449
- error,
2450
- )
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
+ }
2451
2667
  }
2452
2668
 
2453
2669
  // Track payment completion for same-chain same-token transaction
2454
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
2455
2682
  trackPaymentCompleted({
2456
2683
  userAddress: account.address,
2457
2684
  originTxHash: originUserTxReceipt.transactionHash,
@@ -2463,9 +2690,9 @@ async function sendHandlerForSameChainSameToken({
2463
2690
  destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
2464
2691
  })
2465
2692
 
2466
- // Call onCheckoutComplete callback if provided
2693
+ // Call onCheckoutComplete callback with transaction status
2467
2694
  if (checkoutOnHandlers?.triggerCheckoutComplete) {
2468
- checkoutOnHandlers.triggerCheckoutComplete()
2695
+ checkoutOnHandlers.triggerCheckoutComplete(txStatus)
2469
2696
  }
2470
2697
  } else if (originUserTxReceipt) {
2471
2698
  trackPaymentError({
@@ -2620,7 +2847,7 @@ async function _sendHandlerForSameChainDifferentToken({
2620
2847
  }
2621
2848
  }
2622
2849
  } catch (error) {
2623
- console.error("[trails-sdk] Error decoding function data:", error)
2850
+ logger.console.error("[trails-sdk] Error decoding function data:", error)
2624
2851
  }
2625
2852
  }
2626
2853
 
@@ -2806,7 +3033,7 @@ async function attemptGaslessDeposit({
2806
3033
  chain: Chain
2807
3034
  account: Account
2808
3035
  trailsClient: TrailsAPIClient
2809
- originRelayer: Relayer.Standard.Rpc.RpcRelayer
3036
+ originRelayer: Relayer.RpcRelayer
2810
3037
  feeOptions: any
2811
3038
  selectedFeeToken?: any
2812
3039
  }): Promise<TransactionReceipt | null> {
@@ -2833,29 +3060,28 @@ async function attemptGaslessDeposit({
2833
3060
  transport: http(),
2834
3061
  })
2835
3062
 
2836
- const intentEntrypoint = intentEntrypoints[chain.id]
2837
3063
  logger.console.log("[trails-sdk] [GASLESS-FLOW] Intent entrypoint check:", {
2838
3064
  chainId: chain.id,
2839
3065
  chainName: chain.name,
2840
- intentEntrypoint,
2841
- hasIntentEntrypoint: !!intentEntrypoint,
2842
- availableChains: Object.keys(intentEntrypoints).map(Number),
3066
+ intentEntrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
2843
3067
  })
2844
3068
 
2845
- // If intent entrypoint is not available, fall back to old flow
2846
- if (!intentEntrypoint) {
2847
- logger.console.warn(
2848
- `[trails-sdk] ⚠️ No intent entrypoint configured for chain ${chain.id} (${chain.name}). ` +
2849
- `Gasless deposits with fee options are only supported on chains: ${Object.keys(intentEntrypoints).join(", ")}. ` +
2850
- `Falling back to old flow (permit2/paymaster).`,
2851
- )
3069
+ // NEW FLOW: Use Intent Entrypoint API with permit2 support
3070
+ logger.console.log(
3071
+ "[trails-sdk] Using Intent Entrypoint API flow with permit2 support for gasless deposit",
3072
+ )
2852
3073
 
2853
- let calls: Array<{
2854
- to: string
2855
- data: string
2856
- value: string
2857
- }> = []
3074
+ // Switch to correct chain before requesting signatures
3075
+ logger.console.log(
3076
+ "[trails-sdk] [GASLESS-FLOW] Switching to chain before permit/intent signatures",
3077
+ { originChainId },
3078
+ )
3079
+ await attemptSwitchChain({
3080
+ walletClient,
3081
+ desiredChainId: originChainId,
3082
+ })
2858
3083
 
3084
+ try {
2859
3085
  if (paymasterUrl) {
2860
3086
  logger.console.log(
2861
3087
  "[trails-sdk] [GASLESS-FLOW] doing gasless with paymaster",
@@ -2874,7 +3100,11 @@ async function attemptGaslessDeposit({
2874
3100
  publicClient,
2875
3101
  })
2876
3102
 
2877
- calls = await getPaymasterGaslessTransaction({
3103
+ const calls: Array<{
3104
+ to: string
3105
+ data: string
3106
+ value: string
3107
+ }> = await getPaymasterGaslessTransaction({
2878
3108
  walletClient,
2879
3109
  chain,
2880
3110
  tokenAddress: depositTokenAddress as `0x${string}`,
@@ -2903,97 +3133,14 @@ async function attemptGaslessDeposit({
2903
3133
  })
2904
3134
  logger.console.log("[trails-sdk] receipt", receipt)
2905
3135
  return receipt
2906
- } else {
2907
- logger.console.log(
2908
- "[trails-sdk] [GASLESS-FLOW] doing gasless with sequence wallet",
2909
- )
2910
- const delegatorPrivateKey = generatePrivateKey()
2911
- const delegatorAccount = privateKeyToAccount(delegatorPrivateKey)
2912
- const delegatorClient = createWalletClient({
2913
- account: delegatorAccount,
2914
- chain,
2915
- transport: http(),
2916
- })
2917
-
2918
- logger.console.log("[trails-sdk] attempting to switch chain")
2919
- await attemptSwitchChain({
2920
- walletClient,
2921
- desiredChainId: originChainId,
2922
- })
2923
-
2924
- logger.console.log("[trails-sdk] creating sequence wallet")
2925
- const sequenceWalletAddress = await simpleCreateSequenceWallet(
2926
- delegatorAccount as any,
2927
- )
2928
- logger.console.log(
2929
- "[trails-sdk] sequenceWalletAddress",
2930
- sequenceWalletAddress,
2931
- )
2932
-
2933
- const { signature, deadline } = await getPermitSignature({
2934
- publicClient,
2935
- walletClient,
2936
- signer: account.address,
2937
- spender: sequenceWalletAddress,
2938
- tokenAddress: depositTokenAddress as `0x${string}`,
2939
- amount: BigInt(depositTokenAmount),
2940
- chain,
2941
- })
2942
-
2943
- calls = getPermitCalls(
2944
- account.address,
2945
- sequenceWalletAddress,
2946
- BigInt(depositTokenAmount),
2947
- deadline,
2948
- signature,
2949
- depositRecipient as `0x${string}`,
2950
- depositTokenAddress as `0x${string}`,
2951
- )
2952
-
2953
- logger.console.log("[trails-sdk] calls", calls)
2954
-
2955
- const sequenceTxHash = await sequenceSendTransaction(
2956
- sequenceWalletAddress,
2957
- delegatorClient,
2958
- publicClient,
2959
- calls,
2960
- chain,
2961
- )
2962
- logger.console.log("[trails-sdk] sequenceTxHash", sequenceTxHash)
2963
- if (onOriginSend) {
2964
- onOriginSend()
2965
- }
2966
-
2967
- const receipt = await publicClient.waitForTransactionReceipt({
2968
- hash: sequenceTxHash as `0x${string}`,
2969
- })
2970
- logger.console.log("[trails-sdk] receipt", receipt)
2971
- return receipt
2972
3136
  }
2973
- }
2974
-
2975
- // NEW FLOW: Use Intent Entrypoint API with permit2 support
2976
- logger.console.log(
2977
- "[trails-sdk] Using Intent Entrypoint API flow with permit2 support for gasless deposit",
2978
- )
2979
-
2980
- // Switch to correct chain before requesting signatures
2981
- logger.console.log(
2982
- "[trails-sdk] [GASLESS-FLOW] Switching to chain before permit/intent signatures",
2983
- { originChainId },
2984
- )
2985
- await attemptSwitchChain({
2986
- walletClient,
2987
- desiredChainId: originChainId,
2988
- })
2989
3137
 
2990
- try {
2991
3138
  const deadline = Math.floor(Date.now() / 1000) + 3600 // 1 hour from now
2992
3139
  const hasFeeOptions = Boolean(
2993
3140
  feeOptions && feeOptions.feeOptions?.length > 0,
2994
3141
  )
2995
3142
 
2996
- // 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)
2997
3144
  let requiredAmount = BigInt(depositTokenAmount)
2998
3145
 
2999
3146
  // Match selectedFeeToken by tokenAddress to get the latest fee amount from feeOptions
@@ -3023,21 +3170,43 @@ async function attemptGaslessDeposit({
3023
3170
  )
3024
3171
  }
3025
3172
 
3026
- if (selectedFeeOption?.amount) {
3027
- requiredAmount = requiredAmount + BigInt(selectedFeeOption.amount)
3028
- logger.console.log("[trails-sdk] Including fee in required amount:", {
3029
- depositAmount: depositTokenAmount,
3030
- feeAmount: selectedFeeOption.amount,
3031
- feeTokenAddress: selectedFeeOption.tokenAddress,
3032
- totalRequired: requiredAmount.toString(),
3033
- })
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
+ }
3034
3203
  }
3035
3204
 
3036
3205
  const needsApproval = await getNeedsIntentEntrypointApproval({
3037
3206
  client: publicClient,
3038
3207
  token: depositTokenAddress as `0x${string}`,
3039
3208
  account: account.address,
3040
- entrypoint: intentEntrypoint as `0x${string}`,
3209
+ entrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3041
3210
  amount: requiredAmount, // Check if we have enough allowance for this specific transaction
3042
3211
  })
3043
3212
 
@@ -3055,13 +3224,14 @@ async function attemptGaslessDeposit({
3055
3224
  },
3056
3225
  )
3057
3226
 
3058
- // 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
3059
3229
  let permitSignature: string | undefined
3060
3230
  let permitDeadline: number | undefined
3061
3231
 
3062
3232
  if (needsApproval) {
3063
3233
  logger.console.log(
3064
- "[trails-sdk] Getting permit signature for infinite approval",
3234
+ "[trails-sdk] Getting permit signature for deposit token infinite approval",
3065
3235
  )
3066
3236
 
3067
3237
  // Use infinite approval (maxUint256) so user doesn't need to approve again
@@ -3070,6 +3240,7 @@ async function attemptGaslessDeposit({
3070
3240
  "[trails-sdk] Using infinite approval for gasless deposits",
3071
3241
  {
3072
3242
  depositAmount: depositTokenAmount,
3243
+ depositTokenAddress,
3073
3244
  permitAmount: permitAmount.toString(),
3074
3245
  },
3075
3246
  )
@@ -3078,7 +3249,7 @@ async function attemptGaslessDeposit({
3078
3249
  publicClient,
3079
3250
  walletClient,
3080
3251
  signer: account.address,
3081
- spender: intentEntrypoint as `0x${string}`,
3252
+ spender: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3082
3253
  tokenAddress: depositTokenAddress as `0x${string}`,
3083
3254
  amount: permitAmount, // Infinite approval
3084
3255
  chain,
@@ -3087,7 +3258,7 @@ async function attemptGaslessDeposit({
3087
3258
  permitSignature = permitSig.signature
3088
3259
  permitDeadline = Number(permitSig.deadline)
3089
3260
  logger.console.log(
3090
- "[trails-sdk] Permit signature obtained for infinite approval",
3261
+ "[trails-sdk] Deposit token permit signature obtained for infinite approval",
3091
3262
  )
3092
3263
  }
3093
3264
 
@@ -3096,7 +3267,7 @@ async function attemptGaslessDeposit({
3096
3267
  const nonce = await getUserNonce({
3097
3268
  publicClient,
3098
3269
  userAddress: account.address,
3099
- intentEntrypoint: intentEntrypoint as `0x${string}`,
3270
+ intentEntrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3100
3271
  })
3101
3272
  logger.console.log("[trails-sdk] User nonce:", nonce.toString())
3102
3273
 
@@ -3128,7 +3299,7 @@ async function attemptGaslessDeposit({
3128
3299
  intentAddress: depositRecipient as `0x${string}`,
3129
3300
  deadline: BigInt(deadline),
3130
3301
  chainId: originChainId,
3131
- contractAddress: intentEntrypoint as `0x${string}`,
3302
+ contractAddress: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3132
3303
  nonce,
3133
3304
  feeAmount: BigInt(selectedFeeOption?.amount || "0"),
3134
3305
  feeCollector: feeCollectorAddress,
@@ -3314,24 +3485,31 @@ export async function attemptNonGaslessUserDeposit({
3314
3485
 
3315
3486
  logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
3316
3487
 
3317
- const originCallParams = {
3318
- to:
3319
- originTokenAddress === zeroAddress
3320
- ? originIntentAddress
3321
- : originTokenAddress,
3322
- data:
3323
- originTokenAddress === zeroAddress
3324
- ? "0x"
3325
- : getERC20TransferData({
3326
- recipient: originIntentAddress,
3327
- amount: BigInt(firstPreconditionMin) + BigInt(fee),
3328
- }),
3329
- value:
3330
- originTokenAddress === zeroAddress
3331
- ? BigInt(firstPreconditionMin) + BigInt(fee)
3332
- : "0",
3333
- chainId: originChainId,
3488
+ // Build origin call params
3489
+ const originCallParamsBase = buildCrossChainDepositParams({
3490
+ originTokenAddress,
3491
+ originIntentAddress,
3492
+ depositAmount: firstPreconditionMin,
3493
+ fee,
3494
+ originChainId,
3334
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,
3335
3513
  }
3336
3514
 
3337
3515
  await attemptSwitchChain({
@@ -3587,13 +3765,14 @@ async function attemptUserDepositTx({
3587
3765
  feeOptions,
3588
3766
  trailsClient,
3589
3767
  selectedFeeToken,
3768
+ walletId,
3590
3769
  }: {
3591
3770
  originTokenAddress: string
3592
3771
  gasless: boolean
3593
3772
  paymasterUrl?: string
3594
3773
  chain: Chain
3595
3774
  account: Account
3596
- originRelayer: Relayer.Standard.Rpc.RpcRelayer
3775
+ originRelayer: Relayer.RpcRelayer
3597
3776
  firstPreconditionMin: string
3598
3777
  originIntentAddress: string
3599
3778
  onOriginSend?: () => void
@@ -3615,6 +3794,7 @@ async function attemptUserDepositTx({
3615
3794
  feeOptions?: any
3616
3795
  trailsClient: TrailsAPIClient
3617
3796
  selectedFeeToken?: any
3797
+ walletId?: string
3618
3798
  }): Promise<TransactionReceipt | null> {
3619
3799
  let originUserTxReceipt: TransactionReceipt | null = null
3620
3800
  const originChainId = chain.id
@@ -3638,6 +3818,7 @@ async function attemptUserDepositTx({
3638
3818
  gasless,
3639
3819
  feeOptions,
3640
3820
  selectedFeeToken,
3821
+ walletId,
3641
3822
  )
3642
3823
  logger.console.log(
3643
3824
  "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] doGasless check results:",
@@ -3680,12 +3861,15 @@ async function attemptUserDepositTx({
3680
3861
  })
3681
3862
  } catch (error) {
3682
3863
  logger.console.log("[trails-sdk] gassless attempt failed", error)
3683
- throw error
3864
+ // In strict gasless mode, re-throw error instead of falling back
3865
+ if (gasless) {
3866
+ throw error
3867
+ }
3684
3868
  }
3685
3869
  }
3686
3870
 
3687
3871
  // If gasless attempt failed, try to send a regular transaction
3688
- if (!originUserTxReceipt) {
3872
+ if (!originUserTxReceipt && !gasless) {
3689
3873
  originUserTxReceipt = await attemptNonGaslessUserDeposit({
3690
3874
  originTokenAddress,
3691
3875
  firstPreconditionMin,
@@ -3719,7 +3903,16 @@ export function getDoGasless(
3719
3903
  gasless: boolean,
3720
3904
  feeOptions?: any,
3721
3905
  selectedFeeToken?: any,
3906
+ walletId?: string,
3722
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
+
3723
3916
  const hasFeeOptions = Boolean(feeOptions && feeOptions.feeOptions?.length > 0)
3724
3917
 
3725
3918
  // Important: The UI passes selectedFeeToken in these states:
@@ -3856,7 +4049,7 @@ async function sendMetaTxAndWaitForReceipt({
3856
4049
  feeQuote,
3857
4050
  }: {
3858
4051
  metaTx: MetaTxn
3859
- relayer: Relayer.Standard.Rpc.RpcRelayer
4052
+ relayer: Relayer.RpcRelayer
3860
4053
  precondition: IntentPrecondition | null
3861
4054
  feeQuote?: string
3862
4055
  }): Promise<{
@@ -4015,7 +4208,7 @@ async function checkAccountBalance({
4015
4208
  balanceError,
4016
4209
  }
4017
4210
  } catch (error) {
4018
- console.error("[trails-sdk] Error checking account balance:", error)
4211
+ logger.console.error("[trails-sdk] Error checking account balance:", error)
4019
4212
  return {
4020
4213
  hasEnoughBalance: false,
4021
4214
  balance: BigInt(0),
@@ -4107,6 +4300,10 @@ export type UseQuoteProps = {
4107
4300
  quoteProvider?: string | null
4108
4301
  gasless?: boolean
4109
4302
  paymasterUrl?: string
4303
+ selectedFeeToken?: {
4304
+ tokenAddress: string
4305
+ tokenSymbol?: string
4306
+ } | null
4110
4307
  }
4111
4308
 
4112
4309
  export type SwapReturn = {
@@ -4150,6 +4347,8 @@ export type Quote = {
4150
4347
  toAmountUsdDisplay?: string
4151
4348
  gasCostUsd?: number
4152
4349
  gasCostUsdDisplay?: string
4350
+ gasCost?: string
4351
+ gasCostFormatted?: string
4153
4352
  }
4154
4353
 
4155
4354
  export type UseQuoteReturn = {
@@ -4175,6 +4374,7 @@ export function useQuote({
4175
4374
  quoteProvider,
4176
4375
  gasless,
4177
4376
  paymasterUrl,
4377
+ selectedFeeToken,
4178
4378
  relayerEnv,
4179
4379
  nodeGatewayEnv,
4180
4380
  }: Partial<
@@ -4209,174 +4409,187 @@ export function useQuote({
4209
4409
  quoteProvider,
4210
4410
  ],
4211
4411
  queryFn: async () => {
4212
- if (
4213
- !walletClient ||
4214
- !apiClient ||
4215
- !trailsClient ||
4216
- !fromTokenAddress ||
4217
- !toTokenAddress ||
4218
- !swapAmount ||
4219
- !toRecipient ||
4220
- !fromChainId ||
4221
- !toChainId ||
4222
- !indexerGatewayClient
4223
- ) {
4224
- return null
4225
- }
4226
-
4227
- // Get token balance using async method
4228
- const { balances } = await getTokenBalancesWithPrices({
4229
- account: walletClient.account!.address,
4230
- indexerGatewayClient,
4231
- apiClient,
4232
- })
4412
+ try {
4413
+ if (
4414
+ !walletClient ||
4415
+ !apiClient ||
4416
+ !trailsClient ||
4417
+ !fromTokenAddress ||
4418
+ !toTokenAddress ||
4419
+ !swapAmount ||
4420
+ !toRecipient ||
4421
+ !fromChainId ||
4422
+ !toChainId ||
4423
+ !indexerGatewayClient
4424
+ ) {
4425
+ return null
4426
+ }
4233
4427
 
4234
- const originTokenBalance = balances.find(
4235
- (b) =>
4236
- b.chainId === fromChainId &&
4237
- (b.contractAddress?.toLowerCase() ===
4238
- fromTokenAddress.toLowerCase() ||
4239
- (!b.contractAddress && fromTokenAddress === zeroAddress)),
4240
- )
4428
+ // Get token balance using async method
4429
+ const { balances } = await getTokenBalancesWithPrices({
4430
+ account: walletClient.account!.address,
4431
+ indexerGatewayClient,
4432
+ apiClient,
4433
+ })
4241
4434
 
4242
- const originTokenBalanceAmount = originTokenBalance?.balance ?? "0"
4243
- const destinationRelayer = getRelayer(toChainId)
4244
- const originRelayer = getRelayer(fromChainId)
4435
+ const originTokenBalance = balances.find(
4436
+ (b) =>
4437
+ b.chainId === fromChainId &&
4438
+ (b.contractAddress?.toLowerCase() ===
4439
+ fromTokenAddress.toLowerCase() ||
4440
+ (!b.contractAddress && fromTokenAddress === zeroAddress)),
4441
+ )
4245
4442
 
4246
- // Note: Disable this check for now to allow fetching a quote even when the origin balance is zero
4247
- // if (originTokenBalanceAmount === "0") {
4248
- // return null
4249
- // }
4443
+ const originTokenBalanceAmount = originTokenBalance?.balance ?? "0"
4444
+ const destinationRelayer = getRelayer(toChainId)
4445
+ const originRelayer = getRelayer(fromChainId)
4250
4446
 
4251
- // logger.console.log("supportedTokens", supportedTokens)
4447
+ // Note: Disable this check for now to allow fetching a quote even when the origin balance is zero
4448
+ // if (originTokenBalanceAmount === "0") {
4449
+ // return null
4450
+ // }
4252
4451
 
4253
- const originToken = supportedTokens?.find(
4254
- (token) =>
4255
- token.contractAddress?.toLowerCase() ===
4256
- fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
4257
- )
4258
- const destinationToken = supportedTokens?.find(
4259
- (token) =>
4260
- token.contractAddress?.toLowerCase() ===
4261
- toTokenAddress?.toLowerCase() && token.chainId === toChainId,
4262
- )
4452
+ // logger.console.log("supportedTokens", supportedTokens)
4263
4453
 
4264
- const sourceTokenDecimals = originToken?.decimals
4265
- if (!sourceTokenDecimals) {
4266
- logger.console.error(
4267
- "[trails-sdk] [useQuote] Missing source token decimals:",
4268
- {
4269
- originToken,
4270
- fromTokenAddress,
4271
- fromChainId,
4272
- },
4454
+ const originToken = supportedTokens?.find(
4455
+ (token) =>
4456
+ token.contractAddress?.toLowerCase() ===
4457
+ fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
4273
4458
  )
4274
- throw new Error("Source token decimals not found")
4275
- }
4276
- const destinationTokenDecimals = destinationToken?.decimals
4277
- if (!destinationTokenDecimals) {
4278
- logger.console.error(
4279
- "[trails-sdk] Missing destination token decimals:",
4280
- {
4281
- destinationToken,
4282
- toTokenAddress,
4283
- toChainId,
4284
- },
4459
+ const destinationToken = supportedTokens?.find(
4460
+ (token) =>
4461
+ token.contractAddress?.toLowerCase() ===
4462
+ toTokenAddress?.toLowerCase() && token.chainId === toChainId,
4285
4463
  )
4286
- throw new Error("Destination token decimals not found")
4287
- }
4288
- const destinationTokenSymbol = destinationToken?.symbol ?? ""
4289
- const originTokenSymbol = originToken?.symbol ?? ""
4290
-
4291
- const options = {
4292
- account: walletClient.account!,
4293
- originTokenAddress: fromTokenAddress,
4294
- originChainId: fromChainId,
4295
- originTokenBalance: originTokenBalanceAmount,
4296
- destinationChainId: toChainId,
4297
- recipient: toRecipient,
4298
- destinationTokenAddress: toTokenAddress,
4299
- swapAmount: swapAmount.toString(),
4300
- tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
4301
- originTokenSymbol: originTokenSymbol,
4302
- destinationTokenSymbol: destinationTokenSymbol,
4303
- destinationCalldata: toCalldata as string,
4304
- client: walletClient,
4305
- apiClient,
4306
- trailsClient,
4307
- originRelayer,
4308
- destinationRelayer,
4309
- sourceTokenDecimals,
4310
- destinationTokenDecimals,
4311
- fee: "0",
4312
- dryMode: false,
4313
- onTransactionStateChange: onStatusUpdate ?? (() => {}),
4314
- slippageTolerance: slippageTolerance?.toString(),
4315
- quoteProvider: quoteProvider,
4316
- gasless: gasless ?? false,
4317
- paymasterUrl: paymasterUrl,
4318
- }
4319
4464
 
4320
- logger.console.log("[trails-sdk] options", options)
4321
-
4322
- const { quote: prepareSendQuote, send } = await prepareSend(options)
4323
-
4324
- const quote = {
4325
- fromAmount: prepareSendQuote.originAmount,
4326
- toAmount: prepareSendQuote.destinationAmount,
4327
- fromAmountMin: prepareSendQuote.originAmountMin,
4328
- toAmountMin: prepareSendQuote.destinationAmountMin,
4329
- originToken: prepareSendQuote.originToken,
4330
- destinationToken: prepareSendQuote.destinationToken,
4331
- originChain: prepareSendQuote.originChain,
4332
- destinationChain: prepareSendQuote.destinationChain,
4333
- fees: prepareSendQuote.fees,
4334
- priceImpact: prepareSendQuote.priceImpact,
4335
- completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
4336
- slippageTolerance: prepareSendQuote.slippageTolerance,
4337
- transactionStates: prepareSendQuote.transactionStates,
4338
- originTokenRate: prepareSendQuote.originTokenRate,
4339
- destinationTokenRate: prepareSendQuote.destinationTokenRate,
4340
- quoteProvider: prepareSendQuote.quoteProvider,
4341
- fromAmountUsdDisplay:
4342
- prepareSendQuote.originAmountUsdDisplay ?? undefined,
4343
- toAmountUsdDisplay:
4344
- prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
4345
- gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
4346
- gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
4347
- }
4465
+ const sourceTokenDecimals = originToken?.decimals
4466
+ if (!sourceTokenDecimals) {
4467
+ logger.console.error(
4468
+ "[trails-sdk] [useQuote] Missing source token decimals:",
4469
+ {
4470
+ originToken,
4471
+ fromTokenAddress,
4472
+ fromChainId,
4473
+ },
4474
+ )
4475
+ throw new Error("Source token decimals not found")
4476
+ }
4477
+ const destinationTokenDecimals = destinationToken?.decimals
4478
+ if (!destinationTokenDecimals) {
4479
+ logger.console.error(
4480
+ "[trails-sdk] Missing destination token decimals:",
4481
+ {
4482
+ destinationToken,
4483
+ toTokenAddress,
4484
+ toChainId,
4485
+ },
4486
+ )
4487
+ throw new Error("Destination token decimals not found")
4488
+ }
4489
+ const destinationTokenSymbol = destinationToken?.symbol ?? ""
4490
+ const originTokenSymbol = originToken?.symbol ?? ""
4491
+
4492
+ const options = {
4493
+ account: walletClient.account!,
4494
+ originTokenAddress: fromTokenAddress,
4495
+ originChainId: fromChainId,
4496
+ originTokenBalance: originTokenBalanceAmount,
4497
+ destinationChainId: toChainId,
4498
+ recipient: toRecipient,
4499
+ destinationTokenAddress: toTokenAddress,
4500
+ swapAmount: swapAmount.toString(),
4501
+ tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
4502
+ originTokenSymbol: originTokenSymbol,
4503
+ destinationTokenSymbol: destinationTokenSymbol,
4504
+ destinationCalldata: toCalldata as string,
4505
+ client: walletClient,
4506
+ apiClient,
4507
+ trailsClient,
4508
+ originRelayer,
4509
+ destinationRelayer,
4510
+ sourceTokenDecimals,
4511
+ destinationTokenDecimals,
4512
+ fee: "0",
4513
+ dryMode: false,
4514
+ onTransactionStateChange: onStatusUpdate ?? (() => {}),
4515
+ slippageTolerance: slippageTolerance?.toString(),
4516
+ quoteProvider: quoteProvider,
4517
+ gasless: gasless ?? false,
4518
+ paymasterUrl: paymasterUrl,
4519
+ selectedFeeToken: selectedFeeToken ?? undefined,
4520
+ }
4348
4521
 
4349
- const swap = async (): Promise<SwapReturn> => {
4350
- const {
4351
- originUserTxReceipt,
4352
- destinationMetaTxnReceipt,
4353
- totalCompletionSeconds,
4354
- } = await send({})
4522
+ logger.console.log("[trails-sdk] options", options)
4523
+
4524
+ const { quote: prepareSendQuote, send } = await prepareSend(options)
4525
+
4526
+ const quote = {
4527
+ fromAmount: prepareSendQuote.originAmount,
4528
+ toAmount: prepareSendQuote.destinationAmount,
4529
+ fromAmountMin: prepareSendQuote.originAmountMin,
4530
+ toAmountMin: prepareSendQuote.destinationAmountMin,
4531
+ originToken: prepareSendQuote.originToken,
4532
+ destinationToken: prepareSendQuote.destinationToken,
4533
+ originChain: prepareSendQuote.originChain,
4534
+ destinationChain: prepareSendQuote.destinationChain,
4535
+ fees: prepareSendQuote.fees,
4536
+ priceImpact: prepareSendQuote.priceImpact,
4537
+ completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
4538
+ slippageTolerance: prepareSendQuote.slippageTolerance,
4539
+ transactionStates: prepareSendQuote.transactionStates,
4540
+ originTokenRate: prepareSendQuote.originTokenRate,
4541
+ destinationTokenRate: prepareSendQuote.destinationTokenRate,
4542
+ quoteProvider: prepareSendQuote.quoteProvider,
4543
+ fromAmountUsdDisplay:
4544
+ prepareSendQuote.originAmountUsdDisplay ?? undefined,
4545
+ toAmountUsdDisplay:
4546
+ prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
4547
+ gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
4548
+ gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
4549
+ gasCost: prepareSendQuote.gasCost ?? undefined,
4550
+ gasCostFormatted: prepareSendQuote.gasCostFormatted ?? undefined,
4551
+ }
4355
4552
 
4356
- return {
4357
- originTransaction: {
4358
- transactionHash: originUserTxReceipt?.transactionHash,
4359
- explorerUrl: getExplorerUrl({
4360
- txHash: originUserTxReceipt?.transactionHash as string,
4361
- chainId: fromChainId,
4362
- }),
4363
- receipt: originUserTxReceipt,
4364
- },
4365
- destinationTransaction: {
4366
- transactionHash: destinationMetaTxnReceipt?.txnHash,
4367
- explorerUrl: getExplorerUrl({
4368
- txHash: destinationMetaTxnReceipt?.txnHash as string,
4369
- chainId: toChainId,
4370
- }),
4371
- receipt: destinationMetaTxnReceipt,
4372
- },
4373
- totalCompletionSeconds,
4553
+ const swap = async (): Promise<SwapReturn> => {
4554
+ const {
4555
+ originUserTxReceipt,
4556
+ destinationMetaTxnReceipt,
4557
+ totalCompletionSeconds,
4558
+ } = await send({
4559
+ selectedFeeToken: selectedFeeToken ?? undefined,
4560
+ })
4561
+
4562
+ return {
4563
+ originTransaction: {
4564
+ transactionHash: originUserTxReceipt?.transactionHash,
4565
+ explorerUrl: getExplorerUrl({
4566
+ txHash: originUserTxReceipt?.transactionHash as string,
4567
+ chainId: fromChainId,
4568
+ }),
4569
+ receipt: originUserTxReceipt,
4570
+ },
4571
+ destinationTransaction: {
4572
+ transactionHash: destinationMetaTxnReceipt?.txnHash,
4573
+ explorerUrl: getExplorerUrl({
4574
+ txHash: destinationMetaTxnReceipt?.txnHash as string,
4575
+ chainId: toChainId,
4576
+ }),
4577
+ receipt: destinationMetaTxnReceipt,
4578
+ },
4579
+ totalCompletionSeconds,
4580
+ }
4374
4581
  }
4375
- }
4376
4582
 
4377
- return {
4378
- quote,
4379
- swap,
4583
+ return {
4584
+ quote,
4585
+ swap,
4586
+ }
4587
+ } catch (error) {
4588
+ logger.console.error(
4589
+ "[trails-sdk] [useQuote] Error getting quote:",
4590
+ error,
4591
+ )
4592
+ throw getFullErrorMessage(error)
4380
4593
  }
4381
4594
  },
4382
4595
  // Prevent unnecessary refetching
@@ -4532,6 +4745,7 @@ export async function getNormalizedQuoteObject({
4532
4745
  originNativeTokenPriceUsd,
4533
4746
  quoteProvider,
4534
4747
  noSufficientBalance,
4748
+ estimatedGasLimit,
4535
4749
  }: {
4536
4750
  originDepositAddress?: string
4537
4751
  destinationDepositAddress?: string
@@ -4555,6 +4769,7 @@ export async function getNormalizedQuoteObject({
4555
4769
  originNativeTokenPriceUsd?: number | null
4556
4770
  quoteProvider?: string
4557
4771
  noSufficientBalance?: boolean
4772
+ estimatedGasLimit?: bigint
4558
4773
  }): Promise<PrepareSendQuote> {
4559
4774
  if (!destinationChainId) {
4560
4775
  throw new Error("Destination chain id is required")
@@ -4582,7 +4797,7 @@ export async function getNormalizedQuoteObject({
4582
4797
  const destinationChain = getChainInfo(destinationChainId)
4583
4798
 
4584
4799
  if (!originToken || !destinationToken || !originChain || !destinationChain) {
4585
- console.error("[trails-sdk] Token or chain not found", {
4800
+ logger.console.error("[trails-sdk] Token or chain not found", {
4586
4801
  originToken,
4587
4802
  destinationToken,
4588
4803
  originChain,
@@ -4656,17 +4871,52 @@ export async function getNormalizedQuoteObject({
4656
4871
 
4657
4872
  let gasCostUsd: number = 0
4658
4873
  let gasCostUsdDisplay: string = "0"
4874
+ let gasCost: string = "0"
4875
+ let gasCostFormatted: string = "0"
4659
4876
  try {
4660
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
4661
4897
  gasCostUsd = await estimateGasCostUsd(
4662
4898
  publicClient,
4663
4899
  originNativeTokenPriceUsd,
4664
- 200_000n,
4900
+ gasLimitForCost,
4665
4901
  )
4666
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
+ )
4667
4914
  }
4668
4915
  } catch (error) {
4669
- 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
+ )
4670
4920
  }
4671
4921
 
4672
4922
  // Calculate exchange rates
@@ -4742,6 +4992,8 @@ export async function getNormalizedQuoteObject({
4742
4992
  transactionStates: transactionStates || [],
4743
4993
  gasCostUsd,
4744
4994
  gasCostUsdDisplay,
4995
+ gasCost,
4996
+ gasCostFormatted,
4745
4997
  originTokenRate: exchangeRates.originTokenRate,
4746
4998
  destinationTokenRate: exchangeRates.destinationTokenRate,
4747
4999
  originAmountDisplay: formatAmountDisplay(originAmountFormatted),