0xtrails 0.12.2 → 0.13.0

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 (243) hide show
  1. package/dist/abis/trailsHydrate.d.ts.map +1 -1
  2. package/dist/analytics.d.ts +41 -0
  3. package/dist/analytics.d.ts.map +1 -1
  4. package/dist/{ccip-62W6LwH2.js → ccip-Cg9-lJ6K.js} +16 -16
  5. package/dist/chainSwitch.d.ts.map +1 -1
  6. package/dist/chains.d.ts +9 -3
  7. package/dist/chains.d.ts.map +1 -1
  8. package/dist/error.d.ts +1 -0
  9. package/dist/error.d.ts.map +1 -1
  10. package/dist/{index-C0QTNYIA.js → index-DEojZg7b.js} +50431 -50424
  11. package/dist/index.d.ts +1 -3
  12. package/dist/index.d.ts.map +1 -1
  13. package/dist/index.js +377 -421
  14. package/dist/intentReceiptPoller.d.ts.map +1 -1
  15. package/dist/intents.d.ts +1 -3
  16. package/dist/intents.d.ts.map +1 -1
  17. package/dist/mutations.d.ts +1 -4
  18. package/dist/mutations.d.ts.map +1 -1
  19. package/dist/prepareSend.d.ts.map +1 -1
  20. package/dist/query/balance.hooks.d.ts.map +1 -1
  21. package/dist/query/chains.hooks.d.ts.map +1 -1
  22. package/dist/query/chains.queries.d.ts +4 -1
  23. package/dist/query/chains.queries.d.ts.map +1 -1
  24. package/dist/queryParams.d.ts.map +1 -1
  25. package/dist/recover.d.ts.map +1 -1
  26. package/dist/tokens.d.ts.map +1 -1
  27. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  28. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -1
  29. package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -7
  30. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  31. package/dist/transactionIntent/handlers/intentHandler.d.ts.map +1 -1
  32. package/dist/transactionIntent/helpers/transactionStateHelpers.d.ts +10 -1
  33. package/dist/transactionIntent/helpers/transactionStateHelpers.d.ts.map +1 -1
  34. package/dist/transactionIntent/types.d.ts +3 -6
  35. package/dist/transactionIntent/types.d.ts.map +1 -1
  36. package/dist/transactionIntent/utils/resilientDepositTracker.d.ts +3 -3
  37. package/dist/transactionIntent/utils/resilientDepositTracker.d.ts.map +1 -1
  38. package/dist/transactions.d.ts +2 -0
  39. package/dist/transactions.d.ts.map +1 -1
  40. package/dist/umd/trails.min.js +200 -200
  41. package/dist/walletUtils.d.ts +4 -0
  42. package/dist/walletUtils.d.ts.map +1 -1
  43. package/dist/wallets.d.ts +2 -1
  44. package/dist/wallets.d.ts.map +1 -1
  45. package/dist/widget/components/AccountActionsDropdown.d.ts.map +1 -1
  46. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -1
  47. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  48. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  49. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  50. package/dist/widget/components/ConnectedWallets.d.ts +5 -1
  51. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  52. package/dist/widget/components/DepositTracker.d.ts +1 -1
  53. package/dist/widget/components/DepositTracker.d.ts.map +1 -1
  54. package/dist/widget/components/DirectTransfer.d.ts +0 -8
  55. package/dist/widget/components/DirectTransfer.d.ts.map +1 -1
  56. package/dist/widget/components/Fund.d.ts +1 -1
  57. package/dist/widget/components/Fund.d.ts.map +1 -1
  58. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  59. package/dist/widget/components/FundWalletSelection.d.ts +0 -8
  60. package/dist/widget/components/FundWalletSelection.d.ts.map +1 -1
  61. package/dist/widget/components/FundingMethodSelectorButton.d.ts +1 -1
  62. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -1
  63. package/dist/widget/components/MeldStepsFlow.d.ts.map +1 -1
  64. package/dist/widget/components/OnrampErrorScreen.d.ts.map +1 -1
  65. package/dist/widget/components/OnrampPaymentMethods.d.ts.map +1 -1
  66. package/dist/widget/components/OnrampProviderConfirmation.d.ts +0 -6
  67. package/dist/widget/components/OnrampProviderConfirmation.d.ts.map +1 -1
  68. package/dist/widget/components/Pay.d.ts.map +1 -1
  69. package/dist/widget/components/QrCode.d.ts.map +1 -1
  70. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  71. package/dist/widget/components/Receipt.d.ts.map +1 -1
  72. package/dist/widget/components/ReceiptRecoverableFunds.d.ts +25 -0
  73. package/dist/widget/components/ReceiptRecoverableFunds.d.ts.map +1 -0
  74. package/dist/widget/components/RecipientSelectorButton.d.ts +3 -1
  75. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
  76. package/dist/widget/components/Recipients.d.ts.map +1 -1
  77. package/dist/widget/components/Swap.d.ts +2 -16
  78. package/dist/widget/components/Swap.d.ts.map +1 -1
  79. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  80. package/dist/widget/components/TransactionDetails.d.ts.map +1 -1
  81. package/dist/widget/components/TransactionHistoryItem.d.ts.map +1 -1
  82. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  83. package/dist/widget/components/TruncatedTransactionHash.d.ts.map +1 -1
  84. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  85. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  86. package/dist/widget/components/WidgetProviders.d.ts.map +1 -1
  87. package/dist/widget/components/Withdraw.d.ts.map +1 -1
  88. package/dist/widget/css/compiled.css +1 -1
  89. package/dist/widget/hooks/useClickTracking.d.ts.map +1 -1
  90. package/dist/widget/hooks/useDebugScreens.d.ts +1 -10
  91. package/dist/widget/hooks/useDebugScreens.d.ts.map +1 -1
  92. package/dist/widget/hooks/useDepositMonitor.d.ts +2 -4
  93. package/dist/widget/hooks/useDepositMonitor.d.ts.map +1 -1
  94. package/dist/widget/hooks/useExternalFundingReceiptSync.d.ts +11 -0
  95. package/dist/widget/hooks/useExternalFundingReceiptSync.d.ts.map +1 -0
  96. package/dist/widget/hooks/useIntentReceiptBalances.d.ts +16 -0
  97. package/dist/widget/hooks/useIntentReceiptBalances.d.ts.map +1 -0
  98. package/dist/widget/hooks/useQuote.d.ts +0 -4
  99. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  100. package/dist/widget/hooks/useScreenTracking.d.ts +2 -0
  101. package/dist/widget/hooks/useScreenTracking.d.ts.map +1 -0
  102. package/dist/widget/hooks/useSelectedFundMethod.d.ts +0 -2
  103. package/dist/widget/hooks/useSelectedFundMethod.d.ts.map +1 -1
  104. package/dist/widget/hooks/useSendForm.d.ts +0 -4
  105. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  106. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  107. package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
  108. package/dist/widget/hooks/useViewManager.d.ts +89 -0
  109. package/dist/widget/hooks/useViewManager.d.ts.map +1 -0
  110. package/dist/widget/hooks/useWalletConnectUri.d.ts.map +1 -1
  111. package/dist/widget/hooks/useWalletConnectionContext.d.ts +1 -1
  112. package/dist/widget/hooks/useWalletConnectionContext.d.ts.map +1 -1
  113. package/dist/widget/index.d.ts +1 -1
  114. package/dist/widget/index.d.ts.map +1 -1
  115. package/dist/widget/index.js +7 -6
  116. package/dist/widget/providers/TrailsProvider.d.ts.map +1 -1
  117. package/dist/widget/types/commonProps.d.ts +1 -6
  118. package/dist/widget/types/commonProps.d.ts.map +1 -1
  119. package/dist/widget/utils/forexRateStore.d.ts.map +1 -1
  120. package/dist/widget/utils/fundMethodSwitchState.d.ts +10 -0
  121. package/dist/widget/utils/fundMethodSwitchState.d.ts.map +1 -0
  122. package/dist/widget/utils/localeStore.d.ts.map +1 -1
  123. package/dist/widget/utils/viewManagerGuards.d.ts +5 -0
  124. package/dist/widget/utils/viewManagerGuards.d.ts.map +1 -0
  125. package/dist/widget/widget.d.ts +23 -2
  126. package/dist/widget/widget.d.ts.map +1 -1
  127. package/package.json +2 -2
  128. package/src/abis/trailsHydrate.ts +2 -1
  129. package/src/analytics.ts +60 -0
  130. package/src/chainSwitch.ts +11 -8
  131. package/src/chains.ts +82 -37
  132. package/src/constants.ts +2 -2
  133. package/src/error.ts +8 -0
  134. package/src/index.ts +1 -12
  135. package/src/intentReceiptPoller.ts +27 -0
  136. package/src/intents.ts +36 -87
  137. package/src/mutations.ts +11 -102
  138. package/src/onramp-client/index.ts +3 -3
  139. package/src/prepareSend.ts +6 -2
  140. package/src/query/balance.hooks.ts +31 -10
  141. package/src/query/chains.hooks.ts +7 -1
  142. package/src/query/chains.queries.ts +8 -5
  143. package/src/queryParams.ts +8 -6
  144. package/src/recover.ts +9 -9
  145. package/src/tokens.ts +4 -2
  146. package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -2
  147. package/src/transactionIntent/deposits/gaslessDeposit.ts +8 -0
  148. package/src/transactionIntent/deposits/standardDeposit.ts +25 -35
  149. package/src/transactionIntent/handlers/intentHandler.ts +234 -138
  150. package/src/transactionIntent/helpers/transactionStateHelpers.ts +108 -1
  151. package/src/transactionIntent/types.ts +14 -8
  152. package/src/transactionIntent/utils/resilientDepositTracker.ts +72 -183
  153. package/src/transactions.ts +16 -0
  154. package/src/walletUtils.ts +188 -1
  155. package/src/wallets.ts +50 -15
  156. package/src/widget/compiled.css +1 -1
  157. package/src/widget/components/AccountActionsDropdown.tsx +4 -6
  158. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +4 -6
  159. package/src/widget/components/AccountSettings.tsx +36 -22
  160. package/src/widget/components/ClassicSwap.tsx +67 -9
  161. package/src/widget/components/ConnectWallet.tsx +5 -7
  162. package/src/widget/components/ConnectedWallets.tsx +143 -82
  163. package/src/widget/components/DepositTracker.tsx +4 -5
  164. package/src/widget/components/DirectTransfer.tsx +85 -84
  165. package/src/widget/components/Earn.tsx +3 -3
  166. package/src/widget/components/Fund.tsx +90 -17
  167. package/src/widget/components/FundMethods.tsx +77 -43
  168. package/src/widget/components/FundWalletSelection.tsx +13 -397
  169. package/src/widget/components/FundingMethodSelectorButton.tsx +11 -10
  170. package/src/widget/components/MeldStepsFlow.tsx +64 -30
  171. package/src/widget/components/OnrampErrorScreen.tsx +2 -18
  172. package/src/widget/components/OnrampPaymentMethods.tsx +4 -6
  173. package/src/widget/components/OnrampProviderConfirmation.tsx +91 -110
  174. package/src/widget/components/OriginTransferInformation.tsx +2 -2
  175. package/src/widget/components/Pay.tsx +27 -7
  176. package/src/widget/components/PaymentMethods.tsx +10 -10
  177. package/src/widget/components/PoolDeposit.tsx +2 -2
  178. package/src/widget/components/QrCode.tsx +13 -11
  179. package/src/widget/components/QuoteDetails.tsx +16 -6
  180. package/src/widget/components/Receipt.tsx +66 -6
  181. package/src/widget/components/ReceiptRecoverableFunds.tsx +135 -0
  182. package/src/widget/components/RecipientSelectorButton.tsx +6 -17
  183. package/src/widget/components/Recipients.tsx +38 -29
  184. package/src/widget/components/Swap.tsx +2 -25
  185. package/src/widget/components/TokenList.tsx +2 -2
  186. package/src/widget/components/TokenSelector.tsx +11 -11
  187. package/src/widget/components/TokenSelectorButton.tsx +3 -3
  188. package/src/widget/components/TrailsHookModal.tsx +1 -1
  189. package/src/widget/components/TransactionDetails.tsx +43 -37
  190. package/src/widget/components/TransactionHistoryItem.tsx +1 -42
  191. package/src/widget/components/TransferPendingVertical.tsx +11 -4
  192. package/src/widget/components/TruncatedTransactionHash.tsx +5 -0
  193. package/src/widget/components/WalletConfirmation.tsx +5 -8
  194. package/src/widget/components/WalletConnect.tsx +6 -2
  195. package/src/widget/components/WidgetProviders.tsx +34 -43
  196. package/src/widget/components/Withdraw.tsx +25 -11
  197. package/src/widget/hooks/useClickTracking.ts +5 -0
  198. package/src/widget/hooks/useDebugScreens.ts +40 -86
  199. package/src/widget/hooks/useDepositMonitor.ts +14 -149
  200. package/src/widget/hooks/useExternalFundingReceiptSync.ts +79 -0
  201. package/src/widget/hooks/useIntentReceiptBalances.ts +141 -0
  202. package/src/widget/hooks/useQuote.ts +7 -16
  203. package/src/widget/hooks/useScreenTracking.ts +14 -0
  204. package/src/widget/hooks/useSelectedFundMethod.tsx +0 -5
  205. package/src/widget/hooks/useSendForm.ts +5 -14
  206. package/src/widget/hooks/useTokenList.ts +3 -16
  207. package/src/widget/hooks/useTrailsSendTransaction.ts +1 -5
  208. package/src/widget/hooks/useViewManager.tsx +505 -0
  209. package/src/widget/hooks/useWalletConnectUri.tsx +77 -18
  210. package/src/widget/hooks/useWalletConnectionContext.tsx +1 -1
  211. package/src/widget/index.tsx +1 -0
  212. package/src/widget/providers/TrailsProvider.tsx +0 -41
  213. package/src/widget/styles.ts +1 -1
  214. package/src/widget/types/commonProps.ts +0 -8
  215. package/src/widget/utils/forexRateStore.ts +0 -2
  216. package/src/widget/utils/fundMethodSwitchState.ts +25 -0
  217. package/src/widget/utils/localeStore.ts +0 -1
  218. package/src/widget/utils/viewManagerGuards.ts +49 -0
  219. package/src/widget/widget.tsx +405 -316
  220. package/dist/intentStorage.d.ts +0 -24
  221. package/dist/intentStorage.d.ts.map +0 -1
  222. package/dist/mode.d.ts +0 -2
  223. package/dist/mode.d.ts.map +0 -1
  224. package/dist/widget/hooks/useBack.d.ts +0 -22
  225. package/dist/widget/hooks/useBack.d.ts.map +0 -1
  226. package/dist/widget/hooks/useCurrentScreen.d.ts +0 -13
  227. package/dist/widget/hooks/useCurrentScreen.d.ts.map +0 -1
  228. package/dist/widget/hooks/useInitialRedirect.d.ts +0 -7
  229. package/dist/widget/hooks/useInitialRedirect.d.ts.map +0 -1
  230. package/dist/widget/hooks/useMode.d.ts +0 -20
  231. package/dist/widget/hooks/useMode.d.ts.map +0 -1
  232. package/dist/widget/hooks/usePreviousScreen.d.ts +0 -12
  233. package/dist/widget/hooks/usePreviousScreen.d.ts.map +0 -1
  234. package/dist/widget/workers/intentExecutionWorker.d.ts +0 -73
  235. package/dist/widget/workers/intentExecutionWorker.d.ts.map +0 -1
  236. package/src/intentStorage.ts +0 -106
  237. package/src/mode.ts +0 -1
  238. package/src/widget/hooks/useBack.tsx +0 -210
  239. package/src/widget/hooks/useCurrentScreen.tsx +0 -73
  240. package/src/widget/hooks/useInitialRedirect.tsx +0 -70
  241. package/src/widget/hooks/useMode.tsx +0 -51
  242. package/src/widget/hooks/usePreviousScreen.ts +0 -36
  243. package/src/widget/workers/intentExecutionWorker.ts +0 -502
@@ -22,7 +22,11 @@ import {
22
22
  IntentStatus,
23
23
  } from "@0xtrails/api"
24
24
  import type { PrepareSendReturn, SendReturn, FundMethod } from "../types.js"
25
- import { TradeType, toApiFundMethod } from "../types.js"
25
+ import {
26
+ isExternalFundingMethod,
27
+ TradeType,
28
+ toApiFundMethod,
29
+ } from "../types.js"
26
30
  import type { QueryClient } from "@tanstack/react-query"
27
31
  import type { TransactionState } from "../../transactions.js"
28
32
  import type { CheckoutOnHandlers } from "../../widget/hooks/useCheckout.js"
@@ -35,6 +39,7 @@ import {
35
39
  quoteIntent,
36
40
  commitIntent,
37
41
  sendOriginTransaction,
42
+ executeIntent as executeIntentRequest,
38
43
  } from "../../intents.js"
39
44
  import {
40
45
  decodeTokenSweeperEvents,
@@ -65,12 +70,15 @@ import { isNativeToken } from "../../utils/address.js"
65
70
  import { ensureErc20Approval } from "../utils/erc20Approval.js"
66
71
  import { HOUR_MS } from "../../utils/time.js"
67
72
  import { attemptSwitchChain } from "../../chainSwitch.js"
73
+ import { getDoGasless } from "../deposits/gaslessDeposit.js"
68
74
  import {
75
+ applyIntentReceiptToTransactionStates,
69
76
  getNormalizedTransactionStates,
70
- updateTransactionStateWithDecodedEvents,
71
77
  insertRefundTransactionState,
78
+ updateTransactionStateWithDecodedEvents,
72
79
  } from "../helpers/transactionStateHelpers.js"
73
80
  import { isSameChain, isSameToken } from "../validators.js"
81
+ import { isSyntheticTransactionHash } from "../../transactions.js"
74
82
 
75
83
  /**
76
84
  * Parameters for the unified intent handler.
@@ -293,6 +301,16 @@ async function executePassthrough(
293
301
  // Wait for transaction receipt
294
302
  const receipt = await effectivePublicClient.waitForTransactionReceipt({
295
303
  hash: finalDepositTransactionHash,
304
+ onReplaced: (replacement) => {
305
+ logger.console.log(
306
+ "[trails-sdk] Passthrough deposit transaction replaced",
307
+ {
308
+ reason: replacement.reason,
309
+ replacedHash: replacement.replacedTransaction.hash,
310
+ replacementHash: replacement.transaction.hash,
311
+ },
312
+ )
313
+ },
296
314
  })
297
315
  logger.console.log("[trails-sdk] Passthrough receipt:", receipt)
298
316
 
@@ -391,8 +409,6 @@ async function executeIntent(
391
409
  selectedFeeOption?: FeeOption | null
392
410
  depositTransactionHash?: string
393
411
  skipCommit?: boolean
394
- commitOnly?: boolean
395
- skipIntentMonitoring?: boolean
396
412
  },
397
413
  ): Promise<SendReturn> {
398
414
  const {
@@ -427,7 +443,6 @@ async function executeIntent(
427
443
  onOriginSend,
428
444
  selectedFeeOption: runtimeSelectedFeeOption,
429
445
  depositTransactionHash,
430
- commitOnly,
431
446
  } = options
432
447
 
433
448
  const effectiveSelectedFeeOption = runtimeSelectedFeeOption
@@ -466,23 +481,81 @@ async function executeIntent(
466
481
  let depositUserTxnReceipt: TransactionReceipt | null = null
467
482
  let originIntentTransaction: IntentTransaction | null = null
468
483
  let destinationIntentTransaction: IntentTransaction | null = null
484
+ let hasExecutedIntentEarly = false
469
485
 
470
486
  // Create destination public client for cross-chain polling
471
487
  const destinationPublicClient = getChainRpcClient(params.destinationChainId)
472
488
 
473
- if (commitOnly) {
474
- logger.console.log("[trails-sdk] Commit only mode, returning early")
475
- return {
476
- depositUserTxnReceipt: null,
477
- originIntentTransaction: null,
478
- destinationIntentTransaction: null,
489
+ const shouldUseGaslessFlow =
490
+ fundMethod === "wallet" &&
491
+ (getDoGasless(
492
+ effectiveOriginTokenAddress,
493
+ gasFeeOptions,
494
+ effectiveSelectedFeeOption,
495
+ walletId,
496
+ ) ||
497
+ Boolean(paymasterUrl))
498
+
499
+ // Signal executeIntent immediately after commit for all funding flows.
500
+ if (intent.intentId && !shouldUseGaslessFlow) {
501
+ try {
502
+ if (executeIntentFn) {
503
+ await executeIntentFn({
504
+ intentId: intent.intentId,
505
+ })
506
+ } else {
507
+ await executeIntentRequest(trailsClient, intent.intentId, undefined)
508
+ }
509
+ hasExecutedIntentEarly = true
510
+ logger.console.log(
511
+ "[trails-sdk] executeIntent called immediately after commit",
512
+ {
513
+ intentId: intent.intentId,
514
+ fundMethod,
515
+ depositTransactionHash,
516
+ },
517
+ )
518
+ } catch (executeError: unknown) {
519
+ if (getIsAlreadyExecutingError(executeError)) {
520
+ hasExecutedIntentEarly = true
521
+ logger.console.log(
522
+ "[trails-sdk] Intent already executing after early executeIntent call",
523
+ )
524
+ } else {
525
+ throw executeError
526
+ }
527
+ }
528
+ } else if (intent.intentId && shouldUseGaslessFlow) {
529
+ logger.console.log(
530
+ "[trails-sdk] Skipping early executeIntent for gasless flow, waiting for deposit signature",
531
+ {
532
+ intentId: intent.intentId,
533
+ fundMethod,
534
+ hasPaymasterUrl: Boolean(paymasterUrl),
535
+ hasSelectedFeeOption: effectiveSelectedFeeOption !== undefined,
536
+ },
537
+ )
538
+ }
539
+
540
+ // External funding flows without a client-provided deposit hash should return
541
+ // immediately after commit/early execute. Their UI will monitor balance at the
542
+ // origin intent address and continue independently of this send() call.
543
+ const publishTransactionStates = (states: TransactionState[]) => {
544
+ onTransactionStateChange(states)
545
+ if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
546
+ checkoutOnHandlers.triggerCheckoutStatusUpdate(states)
479
547
  }
480
548
  }
481
549
 
482
- // For onramp-meld flow without depositTransactionHash, just return after commit
483
- if (fundMethod === "onramp-meld" && !depositTransactionHash) {
550
+ if (isExternalFundingMethod(fundMethod) && !depositTransactionHash) {
551
+ publishTransactionStates(localTransactionStates)
552
+
484
553
  logger.console.log(
485
- "[trails-sdk] Onramp-meld: Intent committed, returning early",
554
+ "[trails-sdk] External funding flow: intent committed, returning early",
555
+ {
556
+ fundMethod,
557
+ intentId: intent.intentId,
558
+ },
486
559
  )
487
560
  return {
488
561
  depositUserTxnReceipt: null,
@@ -513,10 +586,7 @@ async function executeIntent(
513
586
  })
514
587
 
515
588
  // Set up transaction state tracking
516
- onTransactionStateChange(localTransactionStates)
517
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
518
- checkoutOnHandlers.triggerCheckoutStatusUpdate(localTransactionStates)
519
- }
589
+ publishTransactionStates(localTransactionStates)
520
590
 
521
591
  if (!intent.intentProtocolVersion) {
522
592
  logger.console.log(
@@ -540,8 +610,32 @@ async function executeIntent(
540
610
  // Execute deposit transaction
541
611
  if (depositTransactionHash && fundMethod !== "wallet") {
542
612
  // External deposit with provided hash (onramp-meld, direct-transfer, etc.)
543
- depositUserTxnReceipt = await effectivePublicClient.getTransactionReceipt({
544
- hash: depositTransactionHash as `0x${string}`,
613
+ let resolvedDepositTransactionHash = depositTransactionHash as `0x${string}`
614
+ depositUserTxnReceipt =
615
+ await effectivePublicClient.waitForTransactionReceipt({
616
+ hash: depositTransactionHash as `0x${string}`,
617
+ onReplaced: (replacement) => {
618
+ resolvedDepositTransactionHash = replacement.transaction.hash
619
+ logger.console.log(
620
+ "[trails-sdk] External deposit transaction replaced",
621
+ {
622
+ reason: replacement.reason,
623
+ replacedHash: replacement.replacedTransaction.hash,
624
+ replacementHash: replacement.transaction.hash,
625
+ },
626
+ )
627
+ },
628
+ })
629
+
630
+ if (depositUserTxnReceipt?.transactionHash) {
631
+ resolvedDepositTransactionHash =
632
+ depositUserTxnReceipt.transactionHash as `0x${string}`
633
+ }
634
+
635
+ logger.console.log("[trails-sdk] External deposit receipt resolved", {
636
+ providedHash: depositTransactionHash,
637
+ resolvedHash: resolvedDepositTransactionHash,
638
+ status: depositUserTxnReceipt.status,
545
639
  })
546
640
 
547
641
  // Update transaction state with receipt
@@ -551,14 +645,11 @@ async function executeIntent(
551
645
  effectiveOriginChainId,
552
646
  localTransactionStates[0]?.label,
553
647
  )
554
- onTransactionStateChange(localTransactionStates)
555
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
556
- checkoutOnHandlers.triggerCheckoutStatusUpdate(localTransactionStates)
557
- }
648
+ publishTransactionStates(localTransactionStates)
558
649
  }
559
650
 
560
651
  if (depositUserTxnReceipt.status === "success" && intent.intentId) {
561
- // Call onOriginSend to signal deposit was processed (navigates to pending screen)
652
+ // Always signal external deposit success so UI can navigate to pending.
562
653
  if (onOriginSend) {
563
654
  logger.console.log(
564
655
  "[trails-sdk] Calling onOriginSend for external deposit",
@@ -566,23 +657,28 @@ async function executeIntent(
566
657
  onOriginSend()
567
658
  }
568
659
 
569
- // Try to execute the intent, but handle the case where it's already executing
570
- // (the system may have auto-started execution when funds were deposited)
571
- try {
572
- const executeIntentFnToUse =
573
- executeIntentFn || trailsClient.executeIntent.bind(trailsClient)
574
- await executeIntentFnToUse({
575
- intentId: intent.intentId,
576
- depositTransactionHash,
577
- })
578
- } catch (executeError: unknown) {
579
- // If intent is already executing, that's fine - it means the system picked it up
580
- if (getIsAlreadyExecutingError(executeError)) {
581
- logger.console.log(
582
- "[trails-sdk] Intent already executing, continuing...",
583
- )
584
- } else {
585
- throw executeError
660
+ if (hasExecutedIntentEarly) {
661
+ // Intent execution was already triggered immediately after commit.
662
+ // Skip the duplicate executeIntent call, but continue into receipt polling.
663
+ } else {
664
+ // Try to execute the intent if early execution didn't run.
665
+ try {
666
+ if (executeIntentFn) {
667
+ await executeIntentFn({
668
+ intentId: intent.intentId,
669
+ })
670
+ } else {
671
+ await executeIntentRequest(trailsClient, intent.intentId, undefined)
672
+ }
673
+ } catch (executeError: unknown) {
674
+ // If intent is already executing, that's fine - it means the system picked it up
675
+ if (getIsAlreadyExecutingError(executeError)) {
676
+ logger.console.log(
677
+ "[trails-sdk] Intent already executing, continuing...",
678
+ )
679
+ } else {
680
+ throw executeError
681
+ }
586
682
  }
587
683
  }
588
684
  }
@@ -651,38 +747,12 @@ async function executeIntent(
651
747
  )
652
748
  }
653
749
 
654
- onTransactionStateChange(localTransactionStates)
655
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
656
- checkoutOnHandlers.triggerCheckoutStatusUpdate(localTransactionStates)
657
- }
658
-
659
- // Call executeIntent
660
- if (depositUserTxnReceipt.status === "success" && intent.intentId) {
661
- try {
662
- const executeIntentFnToUse =
663
- executeIntentFn || trailsClient.executeIntent.bind(trailsClient)
664
- await executeIntentFnToUse({
665
- intentId: intent.intentId,
666
- depositTransactionHash: depositUserTxnReceipt.transactionHash,
667
- })
668
- } catch (executeError: unknown) {
669
- // If intent is already executing (e.g. from early executeIntent call), that's fine
670
- if (getIsAlreadyExecutingError(executeError)) {
671
- logger.console.log(
672
- "[trails-sdk] Intent already executing after deposit, continuing...",
673
- )
674
- } else {
675
- logger.console.error(
676
- "[trails-sdk] Error calling executeIntent:",
677
- executeError,
678
- )
679
- }
680
- }
681
- }
750
+ publishTransactionStates(localTransactionStates)
682
751
  }
683
752
  }
684
753
  // Start polling for intent completion to update transaction states
685
754
  let finalReceipt: Awaited<ReturnType<typeof pollIntentReceipt>> = null
755
+ let pollStartMs = 0
686
756
  if (intent.intentId) {
687
757
  // Determine max wait time based on route provider.
688
758
  // OFT routes (LayerZero OFT) can take 5-10+ minutes to complete.
@@ -695,18 +765,43 @@ async function executeIntent(
695
765
  routeProviders: intent.quote?.routeProviders,
696
766
  })
697
767
  try {
768
+ pollStartMs = Date.now()
698
769
  finalReceipt = await pollIntentReceipt({
699
770
  intentId: intent.intentId,
700
771
  trailsClient,
701
772
  abortSignal,
702
773
  callbacks: {
703
- onDepositTransactionFound: async (txHash) => {
774
+ onDepositTransactionFound: async (txHash, receipt) => {
704
775
  logger.console.log(
705
776
  "[trails-sdk] Deposit transaction found:",
706
777
  txHash,
707
778
  )
708
779
 
709
780
  if (localTransactionStates[0]) {
781
+ if (isSyntheticTransactionHash(txHash)) {
782
+ logger.console.log(
783
+ "[trails-sdk] Deposit transaction hash is synthetic, marking deposit from status",
784
+ {
785
+ txHash,
786
+ status: receipt.depositTransaction?.status,
787
+ },
788
+ )
789
+
790
+ if (receipt.depositTransaction?.status === "SUCCEEDED") {
791
+ localTransactionStates[0] = {
792
+ ...localTransactionStates[0],
793
+ state: "confirmed",
794
+ transactionHash: "",
795
+ explorerUrl: "",
796
+ txnMinedAt:
797
+ receipt.depositTransaction?.txnMinedAt || undefined,
798
+ }
799
+
800
+ publishTransactionStates(localTransactionStates)
801
+ }
802
+ return
803
+ }
804
+
710
805
  try {
711
806
  const depositTxReceipt =
712
807
  await effectivePublicClient.getTransactionReceipt({
@@ -725,12 +820,7 @@ async function executeIntent(
725
820
  localTransactionStates[0].decodedGuestModuleEvents =
726
821
  decodeGuestModuleEvents(depositTxReceipt)
727
822
 
728
- onTransactionStateChange(localTransactionStates)
729
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
730
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
731
- localTransactionStates,
732
- )
733
- }
823
+ publishTransactionStates(localTransactionStates)
734
824
  } catch (error) {
735
825
  logger.console.error(
736
826
  "[trails-sdk] Error fetching deposit receipt:",
@@ -739,7 +829,7 @@ async function executeIntent(
739
829
  }
740
830
  }
741
831
  },
742
- onOriginTransactionFound: async (txHash) => {
832
+ onOriginTransactionFound: async (txHash, receipt) => {
743
833
  logger.console.log("[trails-sdk] Origin transaction found:", txHash)
744
834
 
745
835
  originIntentTransaction = { txnHash: txHash } as IntentTransaction
@@ -770,12 +860,27 @@ async function executeIntent(
770
860
  ].decodedGuestModuleEvents =
771
861
  decodeGuestModuleEvents(originTxReceipt)
772
862
 
773
- onTransactionStateChange(localTransactionStates)
774
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
775
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
776
- localTransactionStates,
863
+ // Some external funding flows do not surface a distinct
864
+ // depositTransaction in the receipt. In those cases the origin
865
+ // transaction implicitly covers the deposit step as well.
866
+ if (
867
+ !receipt.depositTransaction?.txnHash &&
868
+ !receipt.depositTransaction?.status &&
869
+ localTransactionStates.length > 1 &&
870
+ localTransactionStates[0]
871
+ ) {
872
+ localTransactionStates[0] = getTransactionStateFromReceipt(
873
+ originTxReceipt,
874
+ effectiveOriginChainId,
875
+ localTransactionStates[0]?.label,
777
876
  )
877
+ localTransactionStates[0].decodedTokenSweeperEvents =
878
+ decodeTokenSweeperEvents(originTxReceipt)
879
+ localTransactionStates[0].decodedGuestModuleEvents =
880
+ decodeGuestModuleEvents(originTxReceipt)
778
881
  }
882
+
883
+ publishTransactionStates(localTransactionStates)
779
884
  }
780
885
  } catch (error) {
781
886
  logger.console.error(
@@ -820,12 +925,7 @@ async function executeIntent(
820
925
  ].decodedGuestModuleEvents =
821
926
  decodeGuestModuleEvents(destinationTxReceipt)
822
927
 
823
- onTransactionStateChange(localTransactionStates)
824
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
825
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
826
- localTransactionStates,
827
- )
828
- }
928
+ publishTransactionStates(localTransactionStates)
829
929
  }
830
930
  } catch (error) {
831
931
  logger.console.error(
@@ -844,6 +944,15 @@ async function executeIntent(
844
944
  hasRefund: !!receipt.refundTransaction,
845
945
  })
846
946
 
947
+ const reconciledStates = applyIntentReceiptToTransactionStates({
948
+ currentStates: localTransactionStates,
949
+ receipt,
950
+ })
951
+ if (reconciledStates.changed) {
952
+ localTransactionStates = reconciledStates.transactionStates
953
+ publishTransactionStates(localTransactionStates)
954
+ }
955
+
847
956
  // Handle terminal status - mark pending steps as aborted/failed
848
957
  if (
849
958
  done &&
@@ -863,12 +972,7 @@ async function executeIntent(
863
972
  }
864
973
  }
865
974
 
866
- onTransactionStateChange(localTransactionStates)
867
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
868
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
869
- localTransactionStates,
870
- )
871
- }
975
+ publishTransactionStates(localTransactionStates)
872
976
  }
873
977
 
874
978
  // Handle deposit tx revert - mark all txs as failed
@@ -883,12 +987,7 @@ async function executeIntent(
883
987
  }
884
988
  }
885
989
 
886
- onTransactionStateChange(localTransactionStates)
887
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
888
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
889
- localTransactionStates,
890
- )
891
- }
990
+ publishTransactionStates(localTransactionStates)
892
991
  }
893
992
 
894
993
  // Insert refund transaction state if refund exists
@@ -911,12 +1010,7 @@ async function executeIntent(
911
1010
  if (statesChanged) {
912
1011
  localTransactionStates.length = 0
913
1012
  localTransactionStates.push(...updatedStates)
914
- onTransactionStateChange(localTransactionStates)
915
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
916
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
917
- localTransactionStates,
918
- )
919
- }
1013
+ publishTransactionStates(localTransactionStates)
920
1014
  }
921
1015
  }
922
1016
  },
@@ -931,22 +1025,43 @@ async function executeIntent(
931
1025
  IntentStatus.REFUNDED,
932
1026
  IntentStatus.ABORTED,
933
1027
  ]
1028
+ const summary = (
1029
+ finalReceipt as unknown as { summary?: { finishedAt?: string | null } }
1030
+ )?.summary
1031
+ const hasFinishedMarker = Boolean(
1032
+ summary?.finishedAt ||
1033
+ finalReceipt?.destinationTransaction?.txnMinedAt ||
1034
+ finalReceipt?.destinationTransaction?.txnHash,
1035
+ )
1036
+ const destinationSucceeded =
1037
+ finalReceipt?.destinationTransaction?.status === "SUCCEEDED"
1038
+ const completeViaDestinationFallback =
1039
+ !!finalReceipt && destinationSucceeded && hasFinishedMarker
934
1040
  const isComplete =
935
- finalReceipt && terminalStatuses.includes(finalReceipt.status)
1041
+ !!finalReceipt &&
1042
+ (terminalStatuses.includes(finalReceipt.status) ||
1043
+ completeViaDestinationFallback)
936
1044
 
937
1045
  if (!isComplete) {
938
- // Intent still executing after timeout - don't report success
1046
+ const elapsedMs = pollStartMs > 0 ? Date.now() - pollStartMs : 0
1047
+ const timedOut = elapsedMs >= maxWaitTime
1048
+ const reason = timedOut
1049
+ ? `Intent execution timed out after ${Math.round(maxWaitTime / 60000)} minutes.`
1050
+ : "Unable to confirm final intent status from receipt polling."
1051
+
939
1052
  logger.console.warn(
940
- "[trails-sdk] Intent polling timed out before reaching terminal status",
1053
+ "[trails-sdk] Intent polling ended without terminal status",
941
1054
  {
942
1055
  intentId: intent.intentId,
943
1056
  status: finalReceipt?.status,
944
1057
  maxWaitTime,
1058
+ elapsedMs,
1059
+ timedOut,
945
1060
  },
946
1061
  )
947
1062
 
948
1063
  trackPaymentError({
949
- error: `Transaction still in progress after ${Math.round(maxWaitTime / 60000)} minutes - please check transaction history`,
1064
+ error: `${reason} Please check transaction history or query GetIntentReceipt.`,
950
1065
  userAddress: account.address,
951
1066
  mode,
952
1067
  fundMethod,
@@ -957,12 +1072,12 @@ async function executeIntent(
957
1072
 
958
1073
  if (checkoutOnHandlers?.triggerCheckoutError) {
959
1074
  checkoutOnHandlers.triggerCheckoutError(
960
- "Transaction still in progress - please check your transaction history for the final status",
1075
+ "Unable to confirm final transaction status",
961
1076
  )
962
1077
  }
963
1078
 
964
1079
  throw new Error(
965
- `Intent execution timed out after ${Math.round(maxWaitTime / 60000)} minutes - transaction still in progress. Please check your transaction history for the final status.`,
1080
+ `${reason} Please check transaction history or query GetIntentReceipt with this intent ID.`,
966
1081
  )
967
1082
  }
968
1083
  } catch (error) {
@@ -1375,7 +1490,7 @@ export async function handleIntent(
1375
1490
  quote,
1376
1491
  feeOptions: gasFeeOptions,
1377
1492
  send: async (options) => {
1378
- const { skipCommit, commitOnly, skipIntentMonitoring } = options
1493
+ const { skipCommit } = options
1379
1494
 
1380
1495
  try {
1381
1496
  // Recalculate passthrough decision at send time based on fee option.
@@ -1426,19 +1541,12 @@ export async function handleIntent(
1426
1541
 
1427
1542
  const commitIntentFnToUse =
1428
1543
  commitIntentFn ||
1429
- ((intent: Intent, options?: { skipIntentMonitoring?: boolean }) =>
1430
- commitIntent(
1431
- trailsClient,
1432
- intent,
1433
- {
1434
- originTokenSymbol,
1435
- destinationTokenSymbol,
1436
- },
1437
- {
1438
- skipIntentMonitoring: options?.skipIntentMonitoring,
1439
- },
1440
- ))
1441
- await commitIntentFnToUse(intent, { skipIntentMonitoring })
1544
+ ((intent: Intent) =>
1545
+ commitIntent(trailsClient, intent, {
1546
+ originTokenSymbol,
1547
+ destinationTokenSymbol,
1548
+ }))
1549
+ await commitIntentFnToUse(intent)
1442
1550
 
1443
1551
  logger.console.log("[trails-sdk] Intent committed successfully")
1444
1552
  } catch (error) {
@@ -1447,18 +1555,6 @@ export async function handleIntent(
1447
1555
  }
1448
1556
  }
1449
1557
 
1450
- if (commitOnly) {
1451
- // Handle commit-only case
1452
- if (!sendUsePassthrough) {
1453
- await commitIntentIfNeeded()
1454
- }
1455
- return {
1456
- depositUserTxnReceipt: null,
1457
- originIntentTransaction: null,
1458
- destinationIntentTransaction: null,
1459
- }
1460
- }
1461
-
1462
1558
  if (sendUsePassthrough && sendDepositTransaction) {
1463
1559
  return executePassthrough(
1464
1560
  params,