0xtrails 0.1.13 → 0.2.1

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