0xtrails 0.8.2 → 0.8.3

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 (68) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/{ccip-ru_Yzdas.js → ccip-Bs-QcZXm.js} +13 -13
  3. package/dist/constants.d.ts +2 -0
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/fees.d.ts +11 -17
  6. package/dist/fees.d.ts.map +1 -1
  7. package/dist/{index-Si7cO9V7.js → index-C_EsqqSn.js} +20320 -20063
  8. package/dist/index.js +425 -847
  9. package/dist/intents.d.ts +1 -2
  10. package/dist/intents.d.ts.map +1 -1
  11. package/dist/prepareSend.d.ts.map +1 -1
  12. package/dist/recover.d.ts +8 -9
  13. package/dist/recover.d.ts.map +1 -1
  14. package/dist/tokenBalances.d.ts +51 -0
  15. package/dist/tokenBalances.d.ts.map +1 -1
  16. package/dist/trailsRouter.d.ts +15 -0
  17. package/dist/trailsRouter.d.ts.map +1 -1
  18. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +1 -3
  19. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  20. package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -3
  21. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  22. package/dist/transactionIntent/handlers/crossChain.d.ts +2 -4
  23. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  24. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +5 -4
  25. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  26. package/dist/transactionIntent/quote/normalizeQuote.d.ts +1 -1
  27. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  28. package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
  29. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  30. package/dist/transactionIntent/types.d.ts +11 -18
  31. package/dist/transactionIntent/types.d.ts.map +1 -1
  32. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  33. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  34. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  35. package/dist/widget/components/SlippageToleranceSettings.d.ts +2 -1
  36. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  37. package/dist/widget/css/compiled.css +1 -1
  38. package/dist/widget/hooks/useQuote.d.ts +94 -35
  39. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  40. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  41. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  42. package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
  43. package/dist/widget/index.js +1 -1
  44. package/package.json +2 -2
  45. package/src/aave.ts +4 -0
  46. package/src/constants.ts +4 -0
  47. package/src/fees.ts +47 -72
  48. package/src/intents.ts +1 -3
  49. package/src/morpho.ts +1 -1
  50. package/src/prepareSend.ts +42 -6
  51. package/src/recover.ts +116 -172
  52. package/src/tokenBalances.ts +301 -1
  53. package/src/trailsRouter.ts +77 -0
  54. package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -6
  55. package/src/transactionIntent/deposits/standardDeposit.ts +167 -184
  56. package/src/transactionIntent/handlers/crossChain.ts +8 -11
  57. package/src/transactionIntent/handlers/sameChainSameToken.ts +619 -608
  58. package/src/transactionIntent/quote/normalizeQuote.ts +32 -46
  59. package/src/transactionIntent/quote/quoteHelpers.ts +4 -2
  60. package/src/transactionIntent/types.ts +11 -18
  61. package/src/widget/compiled.css +1 -1
  62. package/src/widget/components/AccountIntentTransactionHistory.tsx +50 -18
  63. package/src/widget/components/ClassicSwap.tsx +25 -30
  64. package/src/widget/components/QuoteDetails.tsx +18 -27
  65. package/src/widget/components/SlippageToleranceSettings.tsx +55 -25
  66. package/src/widget/hooks/useQuote.ts +317 -79
  67. package/src/widget/hooks/useSendForm.ts +123 -764
  68. package/src/widget/hooks/useTrailsSendTransaction.ts +0 -2
@@ -42,6 +42,7 @@ import { pollIntentReceipt } from "../../intentReceiptPoller.js"
42
42
  import { isNativeToken } from "../../utils.js"
43
43
  import { getAccountTransactionHistory } from "../../transactions.js"
44
44
  import { POLLING_INTERVALS } from "../constants.js"
45
+ import { invalidateTokenBalancesCache } from "../../tokenBalances.js"
45
46
 
46
47
  export async function handleSameChainSameToken({
47
48
  mainSignerAddress,
@@ -54,7 +55,6 @@ export async function handleSameChainSameToken({
54
55
  recipient,
55
56
  walletClient,
56
57
  onTransactionStateChange,
57
- dryMode,
58
58
  account,
59
59
  chain,
60
60
  transactionStates,
@@ -77,6 +77,7 @@ export async function handleSameChainSameToken({
77
77
  isSmartWallet,
78
78
  trailsApiKey,
79
79
  trailsApiUrl,
80
+ tradeType = TradeType.EXACT_INPUT,
80
81
  }: {
81
82
  mainSignerAddress: string
82
83
  originTokenAddress: string
@@ -89,14 +90,13 @@ export async function handleSameChainSameToken({
89
90
  originChainId: number
90
91
  walletClient: WalletClient
91
92
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
92
- dryMode: boolean
93
93
  account: Account
94
94
  chain: Chain
95
95
  transactionStates: TransactionState[]
96
96
  sourceTokenPriceUsd?: number | null
97
97
  destinationTokenPriceUsd?: number | null
98
98
  originNativeTokenPriceUsd?: number | null
99
- slippageTolerance: string
99
+ slippageTolerance: string | null
100
100
  swapProvider?: RouteProvider | null
101
101
  checkoutOnHandlers?: Partial<CheckoutOnHandlers>
102
102
  mode?: "pay" | "fund" | "earn" | "swap"
@@ -116,6 +116,8 @@ export async function handleSameChainSameToken({
116
116
  isSmartWallet?: boolean
117
117
  trailsApiKey?: string
118
118
  trailsApiUrl?: string
119
+ /** Trade type for the transaction. Defaults to EXACT_INPUT. */
120
+ tradeType?: TradeType
119
121
  }): Promise<PrepareSendReturn> {
120
122
  logger.console.log("[trails-sdk] isToSameToken && isToSameChain")
121
123
  const testnet = isTestnetDebugMode()
@@ -273,143 +275,145 @@ export async function handleSameChainSameToken({
273
275
 
274
276
  let originUserTxReceipt: TransactionReceipt | null = null
275
277
 
276
- if (!dryMode) {
277
- // Update transaction state to pending
278
- try {
279
- onTransactionStateChange([
280
- {
281
- transactionHash: "",
282
- explorerUrl: "",
283
- chainId: effectiveOriginChainId,
284
- state: "pending",
285
- label: "Execute",
286
- },
287
- ])
288
- } catch (error) {
289
- logger.console.error(
290
- "[trails-sdk] Error calling onTransactionStateChange:",
291
- error,
292
- )
293
- }
294
-
295
- // Show persistent toast for checkout flow
296
- updatePersistentToast(
297
- "Payment Started",
298
- "Waiting for wallet confirmation...",
299
- "info",
300
- )
301
-
302
- logger.console.log(
303
- "[trails-sdk] origin call params",
304
- originCallParams,
305
- )
306
-
307
- // Use sendOriginTransaction helper which handles gas estimation, fee boosting, and tracking
308
- const txHash = await sendOriginTransaction(
309
- account,
310
- walletClient,
311
- originCallParams as any,
278
+ // Update transaction state to pending
279
+ try {
280
+ onTransactionStateChange([
312
281
  {
313
- depositTokenAmountUsd: depositAmountUsd?.toString(),
282
+ transactionHash: "",
283
+ explorerUrl: "",
284
+ chainId: effectiveOriginChainId,
285
+ state: "pending",
286
+ label: "Execute",
314
287
  },
288
+ ])
289
+ } catch (error) {
290
+ logger.console.error(
291
+ "[trails-sdk] Error calling onTransactionStateChange:",
292
+ error,
315
293
  )
294
+ }
316
295
 
317
- logger.console.log("[trails-sdk] origin tx", txHash)
296
+ // Show persistent toast for checkout flow
297
+ updatePersistentToast(
298
+ "Payment Started",
299
+ "Waiting for wallet confirmation...",
300
+ "info",
301
+ )
318
302
 
319
- if (onOriginSend) {
320
- onOriginSend()
321
- }
303
+ logger.console.log(
304
+ "[trails-sdk] origin call params",
305
+ originCallParams,
306
+ )
307
+
308
+ // Use sendOriginTransaction helper which handles gas estimation, fee boosting, and tracking
309
+ const txHash = await sendOriginTransaction(
310
+ account,
311
+ walletClient,
312
+ originCallParams as any,
313
+ {
314
+ depositTokenAmountUsd: depositAmountUsd?.toString(),
315
+ },
316
+ )
317
+
318
+ logger.console.log("[trails-sdk] origin tx", txHash)
319
+
320
+ if (onOriginSend) {
321
+ onOriginSend()
322
+ }
323
+
324
+ // Wait for transaction receipt
325
+ const receipt = await effectivePublicClient.waitForTransactionReceipt(
326
+ {
327
+ hash: txHash,
328
+ },
329
+ )
330
+ logger.console.log("[trails-sdk] receipt", receipt)
331
+ originUserTxReceipt = receipt
322
332
 
323
- // Wait for transaction receipt
324
- const receipt =
325
- await effectivePublicClient.waitForTransactionReceipt({
326
- hash: txHash,
327
- })
328
- logger.console.log("[trails-sdk] receipt", receipt)
329
- originUserTxReceipt = receipt
330
-
331
- // Track transaction confirmation
332
- trackTransactionConfirmed({
333
- transactionHash: txHash,
334
- chainId: effectiveOriginChainId,
333
+ // Track transaction confirmation
334
+ trackTransactionConfirmed({
335
+ transactionHash: txHash,
336
+ chainId: effectiveOriginChainId,
337
+ userAddress: account.address,
338
+ blockNumber: Number(receipt.blockNumber),
339
+ originTokenAddress: effectiveOriginTokenAddress,
340
+ depositTokenAmountUsd: depositAmountUsd?.toString(),
341
+ })
342
+
343
+ // Remove persistent toast and show success
344
+ const chainInfo = getChainInfo(effectiveOriginChainId)
345
+ updatePersistentToast(
346
+ "Transfer Confirmed",
347
+ `Your transaction on ${(chainInfo as any)?.name || "chain"} has been confirmed`,
348
+ "info",
349
+ )
350
+
351
+ // Update transaction state to completed
352
+ try {
353
+ onTransactionStateChange([
354
+ getTransactionStateFromReceipt(
355
+ originUserTxReceipt,
356
+ effectiveOriginChainId,
357
+ transactionStates[0]?.label || "Execute",
358
+ ),
359
+ ])
360
+ } catch (error) {
361
+ logger.console.error(
362
+ "[trails-sdk] Error calling onTransactionStateChange:",
363
+ error,
364
+ )
365
+ }
366
+
367
+ // Track payment completion for same-chain same-token transaction
368
+ if (originUserTxReceipt && originUserTxReceipt.status === "success") {
369
+ trackPaymentCompleted({
335
370
  userAddress: account.address,
336
- blockNumber: Number(receipt.blockNumber),
371
+ originTxHash: originUserTxReceipt.transactionHash,
372
+ originChainId: effectiveOriginChainId,
373
+ mode,
374
+ fundMethod,
337
375
  originTokenAddress: effectiveOriginTokenAddress,
376
+ originTokenSymbol,
377
+ originTokenAmount: swapAmount,
378
+ originTokenAmountFormatted: depositAmountFormatted.toString(),
379
+ destinationTokenAddress: effectiveOriginTokenAddress, // same chain same token
380
+ destinationTokenSymbol: destinationTokenSymbol,
381
+ depositTokenAmount: swapAmount,
382
+ depositTokenAmountFormatted: depositAmountFormatted.toString(),
338
383
  depositTokenAmountUsd: depositAmountUsd?.toString(),
384
+ destinationTokenAmount: swapAmount, // same as deposit amount
385
+ destinationTokenAmountFormatted:
386
+ depositAmountFormatted.toString(), // same as deposit amount
387
+ destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
388
+ originTokenDecimals: originTokenDecimals,
389
+ destinationTokenDecimals: originTokenDecimals, // same chain same token
339
390
  })
340
391
 
341
- // Remove persistent toast and show success
342
- const chainInfo = getChainInfo(effectiveOriginChainId)
343
- updatePersistentToast(
344
- "Transfer Confirmed",
345
- `Your transaction on ${(chainInfo as any)?.name || "chain"} has been confirmed`,
346
- "info",
347
- )
392
+ // Invalidate token balances cache on successful transactions
393
+ invalidateTokenBalancesCache(account.address)
348
394
 
349
- // Update transaction state to completed
350
- try {
351
- onTransactionStateChange([
352
- getTransactionStateFromReceipt(
353
- originUserTxReceipt,
354
- effectiveOriginChainId,
355
- transactionStates[0]?.label || "Execute",
356
- ),
357
- ])
358
- } catch (error) {
359
- logger.console.error(
360
- "[trails-sdk] Error calling onTransactionStateChange:",
361
- error,
395
+ // Call onCheckoutComplete callback if provided
396
+ if (checkoutOnHandlers?.triggerCheckoutComplete) {
397
+ checkoutOnHandlers.triggerCheckoutComplete(
398
+ "success",
399
+ account.address,
362
400
  )
363
401
  }
402
+ } else if (originUserTxReceipt) {
403
+ trackPaymentError({
404
+ error: "Transaction failed",
405
+ userAddress: account.address,
406
+ mode,
407
+ fundMethod,
408
+ originTokenAddress: effectiveOriginTokenAddress,
409
+ })
364
410
 
365
- // Track payment completion for same-chain same-token transaction
366
- if (
367
- originUserTxReceipt &&
368
- originUserTxReceipt.status === "success"
369
- ) {
370
- trackPaymentCompleted({
371
- userAddress: account.address,
372
- originTxHash: originUserTxReceipt.transactionHash,
373
- originChainId: effectiveOriginChainId,
374
- mode,
375
- fundMethod,
376
- originTokenAddress: effectiveOriginTokenAddress,
377
- originTokenSymbol,
378
- originTokenAmount: swapAmount,
379
- originTokenAmountFormatted: depositAmountFormatted.toString(),
380
- destinationTokenAddress: effectiveOriginTokenAddress, // same chain same token
381
- destinationTokenSymbol: destinationTokenSymbol,
382
- depositTokenAmount: swapAmount,
383
- depositTokenAmountFormatted: depositAmountFormatted.toString(),
384
- depositTokenAmountUsd: depositAmountUsd?.toString(),
385
- destinationTokenAmount: swapAmount, // same as deposit amount
386
- destinationTokenAmountFormatted:
387
- depositAmountFormatted.toString(), // same as deposit amount
388
- destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
389
- originTokenDecimals: originTokenDecimals,
390
- destinationTokenDecimals: originTokenDecimals, // same chain same token
391
- })
392
-
393
- // Call onCheckoutComplete callback if provided
394
- if (checkoutOnHandlers?.triggerCheckoutComplete) {
395
- checkoutOnHandlers.triggerCheckoutComplete(
396
- "success",
397
- account.address,
398
- )
399
- }
400
- } else if (originUserTxReceipt) {
401
- trackPaymentError({
402
- error: "Transaction failed",
403
- userAddress: account.address,
404
- mode,
405
- fundMethod,
406
- originTokenAddress: effectiveOriginTokenAddress,
407
- })
408
-
409
- // Call onCheckoutError callback if provided
410
- if (checkoutOnHandlers?.triggerCheckoutError) {
411
- checkoutOnHandlers.triggerCheckoutError("Transaction failed")
412
- }
411
+ // Invalidate token balances cache even on failure (gas was spent)
412
+ invalidateTokenBalancesCache(account.address)
413
+
414
+ // Call onCheckoutError callback if provided
415
+ if (checkoutOnHandlers?.triggerCheckoutError) {
416
+ checkoutOnHandlers.triggerCheckoutError("Transaction failed")
413
417
  }
414
418
  }
415
419
 
@@ -450,18 +454,20 @@ export async function handleSameChainSameToken({
450
454
  }
451
455
 
452
456
  // For same-chain transactions, use Intent flow to support gasless deposits
457
+ // When using EXACT_OUTPUT, both origin and destination amounts should be swapAmount
458
+ // since it's same-chain same-token (no swap happening)
453
459
  const intentArgs = await getIntentArgs(
454
460
  mainSignerAddress,
455
461
  effectiveOriginChainId,
456
462
  effectiveOriginTokenAddress,
457
- swapAmount, // originTokenAmount
463
+ swapAmount, // originTokenAmount - same for both trade types in same-chain same-token
458
464
  effectiveOriginChainId, // same chain
459
465
  effectiveOriginTokenAddress, // same token
460
- "0", // destinationTokenAmount (exact input)
466
+ tradeType === TradeType.EXACT_OUTPUT ? swapAmount : "0", // destinationTokenAmount
461
467
  recipient,
462
468
  destinationCalldata,
463
469
  slippageTolerance,
464
- TradeType.EXACT_INPUT,
470
+ tradeType,
465
471
  swapProvider,
466
472
  null,
467
473
  undefined, // connector - not available in this context
@@ -691,487 +697,548 @@ export async function handleSameChainSameToken({
691
697
  walletClient,
692
698
  desiredChainId: effectiveOriginChainId,
693
699
  })
694
- if (!dryMode) {
695
- // For gasless flows on same-chain same-token, we need to track 2 transactions:
696
- // [0] = Deposit transaction (relayed)
697
- // [1] = Origin intent transaction (executed on chain after deposit is confirmed)
698
-
699
- // Initialize local transaction states for gasless flows to track both deposit and execute
700
- const createInitialStates = (): TransactionState[] => {
701
- const depositState: TransactionState = {
702
- transactionHash: "",
703
- explorerUrl: "",
704
- chainId: effectiveOriginChainId,
705
- state: "pending",
706
- label: "Deposit",
707
- }
708
700
 
709
- const executeState: TransactionState = {
710
- transactionHash: "",
711
- explorerUrl: "",
712
- chainId: effectiveOriginChainId,
713
- state: "pending",
714
- label: "Execute",
715
- }
701
+ // For gasless flows on same-chain same-token, we need to track 2 transactions:
702
+ // [0] = Deposit transaction (relayed)
703
+ // [1] = Origin intent transaction (executed on chain after deposit is confirmed)
704
+
705
+ // Initialize local transaction states for gasless flows to track both deposit and execute
706
+ const createInitialStates = (): TransactionState[] => {
707
+ const depositState: TransactionState = {
708
+ transactionHash: "",
709
+ explorerUrl: "",
710
+ chainId: effectiveOriginChainId,
711
+ state: "pending",
712
+ label: "Deposit",
713
+ }
716
714
 
717
- return effectiveGasless
718
- ? [depositState, executeState]
719
- : [depositState]
715
+ const executeState: TransactionState = {
716
+ transactionHash: "",
717
+ explorerUrl: "",
718
+ chainId: effectiveOriginChainId,
719
+ state: "pending",
720
+ label: "Execute",
720
721
  }
721
722
 
722
- const localTransactionStates = createInitialStates()
723
+ return effectiveGasless
724
+ ? [depositState, executeState]
725
+ : [depositState]
726
+ }
723
727
 
724
- try {
725
- onTransactionStateChange(localTransactionStates)
726
- // Also trigger checkout status update if handler is provided
727
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
728
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
729
- localTransactionStates,
730
- )
731
- }
732
- } catch (error) {
733
- logger.console.error(
734
- "[trails-sdk] Error calling onTransactionStateChange:",
735
- error,
728
+ const localTransactionStates = createInitialStates()
729
+
730
+ try {
731
+ onTransactionStateChange(localTransactionStates)
732
+ // Also trigger checkout status update if handler is provided
733
+ if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
734
+ checkoutOnHandlers.triggerCheckoutStatusUpdate(
735
+ localTransactionStates,
736
736
  )
737
737
  }
738
+ } catch (error) {
739
+ logger.console.error(
740
+ "[trails-sdk] Error calling onTransactionStateChange:",
741
+ error,
742
+ )
743
+ }
738
744
 
739
- // For gasless flows, pass the localTransactionStates instead of the input parameter
740
- // This ensures we track both deposit and execute transactions
741
- const statesForDeposit = effectiveGasless
742
- ? localTransactionStates
743
- : transactionStates
745
+ // For gasless flows, pass the localTransactionStates instead of the input parameter
746
+ // This ensures we track both deposit and execute transactions
747
+ const statesForDeposit = effectiveGasless
748
+ ? localTransactionStates
749
+ : transactionStates
750
+
751
+ // Use attemptUserDepositTx which handles both gasless and non-gasless flows
752
+ // For non-gasless same-chain same-token, pass recipient to send directly to user instead of intent contract
753
+ depositUserTxnReceipt = await attemptUserDepositTx({
754
+ originTokenAddress: effectiveOriginTokenAddress,
755
+ paymasterUrl,
756
+ chain: effectiveOriginChain,
757
+ account,
758
+ depositAmount: swapAmount,
759
+ originIntentAddress: intent.originIntentAddress,
760
+ onOriginSend,
761
+ publicClient: effectivePublicClient,
762
+ walletClient,
763
+ destinationTokenDecimals: originTokenDecimals,
764
+ sourceTokenDecimals: originTokenDecimals,
765
+ sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
766
+ destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
767
+ swapAmount,
768
+ onTransactionStateChange,
769
+ transactionStates: statesForDeposit,
770
+ fundMethod,
771
+ originTokenSymbol,
772
+ destinationTokenSymbol,
773
+ depositAmountUsd,
774
+ feeOptions: gasFeeOptions,
775
+ trailsContracts,
776
+ trailsClient,
777
+ selectedFeeOption: effectiveSelectedFeeOption,
778
+ walletId,
779
+ abortSignal,
780
+ checkoutOnHandlers,
781
+ intentId: intent.intentId,
782
+ executeIntentFn,
783
+ depositRecipientOverride: recipient,
784
+ isSameChainSameToken: true,
785
+ destinationCalldata,
786
+ })
744
787
 
745
- // Use attemptUserDepositTx which handles both gasless and non-gasless flows
746
- // For non-gasless same-chain same-token, pass recipient to send directly to user instead of intent contract
747
- depositUserTxnReceipt = await attemptUserDepositTx({
748
- originTokenAddress: effectiveOriginTokenAddress,
749
- paymasterUrl,
750
- chain: effectiveOriginChain,
751
- account,
752
- depositAmount: swapAmount,
753
- originIntentAddress: intent.originIntentAddress,
754
- onOriginSend,
755
- publicClient: effectivePublicClient,
756
- walletClient,
757
- destinationTokenDecimals: originTokenDecimals,
758
- sourceTokenDecimals: originTokenDecimals,
759
- fee: "0",
760
- dryMode,
761
- sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
762
- destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
763
- swapAmount,
764
- onTransactionStateChange,
765
- transactionStates: statesForDeposit,
766
- fundMethod,
767
- originTokenSymbol,
768
- destinationTokenSymbol,
769
- depositAmountUsd,
770
- feeOptions: gasFeeOptions,
771
- trailsContracts,
772
- trailsClient,
773
- selectedFeeOption: effectiveSelectedFeeOption,
774
- walletId,
775
- abortSignal,
776
- checkoutOnHandlers,
777
- intentId: intent.intentId,
778
- executeIntentFn,
779
- depositRecipientOverride: recipient,
780
- isSameChainSameToken: true,
781
- destinationCalldata,
782
- })
788
+ // attemptUserDepositTx handles both gasless and non-gasless flows
789
+ // Transaction state updates and toasts are handled within that function
790
+ logger.console.log("[trails-sdk] Deposit transaction completed", {
791
+ hasReceipt: !!depositUserTxnReceipt,
792
+ isGasless: effectiveGasless,
793
+ })
783
794
 
784
- // attemptUserDepositTx handles both gasless and non-gasless flows
785
- // Transaction state updates and toasts are handled within that function
786
- logger.console.log("[trails-sdk] Deposit transaction completed", {
787
- hasReceipt: !!depositUserTxnReceipt,
788
- isGasless: effectiveGasless,
789
- })
795
+ // For QR code mode, we need to detect the deposit first, call executeIntent, then start polling
796
+ // For gasless flows, we can start polling immediately
797
+ if (fundMethod === "qr-code" || fundMethod === "exchange") {
798
+ logger.console.log(
799
+ "[trails-sdk] QR code mode: waiting for deposit transaction before starting polling",
800
+ )
801
+ // Use the same approach as cross-chain: check for deposit via indexer
802
+ const checkForDepositTx = async () => {
803
+ while (true) {
804
+ // Check if we should abort
805
+ if (abortSignal?.aborted) {
806
+ logger.console.log(
807
+ "[trails-sdk] Aborting deposit tx check due to abort signal",
808
+ )
809
+ return null
810
+ }
790
811
 
791
- // For QR code mode, we need to detect the deposit first, call executeIntent, then start polling
792
- // For gasless flows, we can start polling immediately
793
- if (fundMethod === "qr-code" || fundMethod === "exchange") {
794
- logger.console.log(
795
- "[trails-sdk] QR code mode: waiting for deposit transaction before starting polling",
796
- )
797
- // Use the same approach as cross-chain: check for deposit via indexer
798
- const checkForDepositTx = async () => {
799
- while (true) {
800
- // Check if we should abort
801
- if (abortSignal?.aborted) {
802
- logger.console.log(
803
- "[trails-sdk] Aborting deposit tx check due to abort signal",
812
+ try {
813
+ if (!sequenceIndexerUrl) {
814
+ throw new Error(
815
+ "sequenceIndexerUrl is required for QR code mode",
804
816
  )
805
- return null
806
817
  }
807
-
808
- try {
809
- if (!sequenceIndexerUrl) {
810
- throw new Error(
811
- "sequenceIndexerUrl is required for QR code mode",
812
- )
813
- }
814
- if (!sequenceProjectAccessKey) {
815
- throw new Error(
816
- "sequenceProjectAccessKey is required for QR code mode",
817
- )
818
- }
819
- const response = await getAccountTransactionHistory({
820
- chainId: effectiveOriginChainId,
821
- accountAddress: intent.originIntentAddress,
822
- abortSignal,
823
- apiKey: sequenceProjectAccessKey,
824
- indexerUrl: sequenceIndexerUrl,
825
- })
826
- logger.console.log(
827
- "[trails-sdk] getAccountTransactionHistory response",
828
- response,
818
+ if (!sequenceProjectAccessKey) {
819
+ throw new Error(
820
+ "sequenceProjectAccessKey is required for QR code mode",
829
821
  )
830
- if (response.transactions.length > 0) {
831
- const tx = response.transactions[0]
832
- if (!tx?.txnHash) {
833
- await new Promise((resolve) =>
834
- setTimeout(
835
- resolve,
836
- POLLING_INTERVALS.TRANSACTION_HISTORY,
837
- ),
838
- )
839
- continue
840
- }
841
- const depositTxReceipt =
842
- await effectivePublicClient.getTransactionReceipt({
843
- hash: tx.txnHash as `0x${string}`,
844
- })
845
-
846
- // Validate that the deposit amount is sufficient before proceeding
847
- logger.console.log(
848
- "[trails-sdk] Checking if deposit amount is sufficient",
849
- {
850
- depositAddress: intent.originIntentAddress,
851
- requiredAmount: quote.originAmount,
852
- tokenAddress: effectiveOriginTokenAddress,
853
- },
822
+ }
823
+ const response = await getAccountTransactionHistory({
824
+ chainId: effectiveOriginChainId,
825
+ accountAddress: intent.originIntentAddress,
826
+ abortSignal,
827
+ apiKey: sequenceProjectAccessKey,
828
+ indexerUrl: sequenceIndexerUrl,
829
+ })
830
+ logger.console.log(
831
+ "[trails-sdk] getAccountTransactionHistory response",
832
+ response,
833
+ )
834
+ if (response.transactions.length > 0) {
835
+ const tx = response.transactions[0]
836
+ if (!tx?.txnHash) {
837
+ await new Promise((resolve) =>
838
+ setTimeout(
839
+ resolve,
840
+ POLLING_INTERVALS.TRANSACTION_HISTORY,
841
+ ),
854
842
  )
843
+ continue
844
+ }
845
+ const depositTxReceipt =
846
+ await effectivePublicClient.getTransactionReceipt({
847
+ hash: tx.txnHash as `0x${string}`,
848
+ })
855
849
 
856
- const balanceCheck = await checkAccountBalance({
857
- account: {
858
- address: intent.originIntentAddress as `0x${string}`,
859
- } as Account,
850
+ // Validate that the deposit amount is sufficient before proceeding
851
+ logger.console.log(
852
+ "[trails-sdk] Checking if deposit amount is sufficient",
853
+ {
854
+ depositAddress: intent.originIntentAddress,
855
+ requiredAmount: quote.originAmount,
860
856
  tokenAddress: effectiveOriginTokenAddress,
861
- depositAmount: quote.originAmount,
862
- publicClient: effectivePublicClient,
863
- })
857
+ },
858
+ )
864
859
 
865
- if (!balanceCheck.hasEnoughBalance) {
866
- logger.console.warn(
867
- "[trails-sdk] Deposit amount is insufficient, continuing to poll",
868
- {
869
- balance: balanceCheck.balanceFormatted,
870
- required: balanceCheck.requiredAmountFormatted,
871
- percentComplete: (
872
- (Number(balanceCheck.balance) /
873
- Number(balanceCheck.requiredAmount)) *
874
- 100
875
- ).toFixed(2),
876
- },
877
- )
878
- // Continue polling - don't break out of the loop yet
879
- await new Promise((resolve) =>
880
- setTimeout(
881
- resolve,
882
- POLLING_INTERVALS.TRANSACTION_HISTORY,
883
- ),
884
- )
885
- continue
886
- }
860
+ const balanceCheck = await checkAccountBalance({
861
+ account: {
862
+ address: intent.originIntentAddress as `0x${string}`,
863
+ } as Account,
864
+ tokenAddress: effectiveOriginTokenAddress,
865
+ depositAmount: quote.originAmount,
866
+ publicClient: effectivePublicClient,
867
+ })
887
868
 
888
- logger.console.log(
889
- "[trails-sdk] Deposit amount is sufficient, proceeding",
869
+ if (!balanceCheck.hasEnoughBalance) {
870
+ logger.console.warn(
871
+ "[trails-sdk] Deposit amount is insufficient, continuing to poll",
890
872
  {
891
873
  balance: balanceCheck.balanceFormatted,
892
874
  required: balanceCheck.requiredAmountFormatted,
875
+ percentComplete: (
876
+ (Number(balanceCheck.balance) /
877
+ Number(balanceCheck.requiredAmount)) *
878
+ 100
879
+ ).toFixed(2),
893
880
  },
894
881
  )
882
+ // Continue polling - don't break out of the loop yet
883
+ await new Promise((resolve) =>
884
+ setTimeout(
885
+ resolve,
886
+ POLLING_INTERVALS.TRANSACTION_HISTORY,
887
+ ),
888
+ )
889
+ continue
890
+ }
895
891
 
896
- depositUserTxnReceipt = depositTxReceipt
892
+ logger.console.log(
893
+ "[trails-sdk] Deposit amount is sufficient, proceeding",
894
+ {
895
+ balance: balanceCheck.balanceFormatted,
896
+ required: balanceCheck.requiredAmountFormatted,
897
+ },
898
+ )
899
+
900
+ depositUserTxnReceipt = depositTxReceipt
897
901
 
898
- localTransactionStates[0] = getTransactionStateFromReceipt(
899
- depositTxReceipt,
900
- effectiveOriginChainId,
901
- localTransactionStates[0]?.label,
902
+ localTransactionStates[0] = getTransactionStateFromReceipt(
903
+ depositTxReceipt,
904
+ effectiveOriginChainId,
905
+ localTransactionStates[0]?.label,
906
+ )
907
+ onTransactionStateChange(localTransactionStates)
908
+ if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
909
+ checkoutOnHandlers.triggerCheckoutStatusUpdate(
910
+ localTransactionStates,
902
911
  )
903
- onTransactionStateChange(localTransactionStates)
904
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
905
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
906
- localTransactionStates,
907
- )
908
- }
912
+ }
909
913
 
910
- // Call executeIntent after detecting deposit transaction (for QR code mode)
911
- // This triggers the backend to start executing the intent
912
- // Must be called BEFORE waitIntentReceipt polling starts
913
- if (
914
- depositTxReceipt.status === "success" &&
915
- intent.intentId
916
- ) {
914
+ // Call executeIntent after detecting deposit transaction (for QR code mode)
915
+ // This triggers the backend to start executing the intent
916
+ // Must be called BEFORE waitIntentReceipt polling starts
917
+ if (
918
+ depositTxReceipt.status === "success" &&
919
+ intent.intentId
920
+ ) {
921
+ logger.console.log(
922
+ "[trails-sdk] Calling executeIntent with detected deposit transaction hash (QR code mode)",
923
+ {
924
+ intentId: intent.intentId,
925
+ txHash: depositTxReceipt.transactionHash,
926
+ fundMethod,
927
+ },
928
+ )
929
+ try {
930
+ const executeIntentFnToUse =
931
+ executeIntentFn ||
932
+ trailsClient.executeIntent.bind(trailsClient)
933
+ await executeIntentFnToUse({
934
+ intentId: intent.intentId,
935
+ depositTransactionHash:
936
+ depositTxReceipt.transactionHash,
937
+ })
917
938
  logger.console.log(
918
- "[trails-sdk] Calling executeIntent with detected deposit transaction hash (QR code mode)",
919
- {
920
- intentId: intent.intentId,
921
- txHash: depositTxReceipt.transactionHash,
922
- fundMethod,
923
- },
939
+ "[trails-sdk] executeIntent completed successfully (QR code mode)",
924
940
  )
925
- try {
926
- const executeIntentFnToUse =
927
- executeIntentFn ||
928
- trailsClient.executeIntent.bind(trailsClient)
929
- await executeIntentFnToUse({
930
- intentId: intent.intentId,
931
- depositTransactionHash:
932
- depositTxReceipt.transactionHash,
933
- })
934
- logger.console.log(
935
- "[trails-sdk] executeIntent completed successfully (QR code mode)",
936
- )
937
941
 
938
- // Now start polling for intent receipt
939
- break
940
- } catch (error) {
941
- logger.console.error(
942
- "[trails-sdk] Error calling executeIntent (QR code mode):",
943
- error,
944
- )
945
- }
942
+ // Now start polling for intent receipt
943
+ break
944
+ } catch (error) {
945
+ logger.console.error(
946
+ "[trails-sdk] Error calling executeIntent (QR code mode):",
947
+ error,
948
+ )
946
949
  }
950
+ }
947
951
 
948
- if (onOriginSend) {
949
- onOriginSend()
950
- }
951
- break
952
+ if (onOriginSend) {
953
+ onOriginSend()
952
954
  }
953
- } catch (error) {
954
- logger.console.error("Error checking for deposit tx", error)
955
+ break
955
956
  }
956
- await new Promise((resolve) =>
957
- setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
958
- )
957
+ } catch (error) {
958
+ logger.console.error("Error checking for deposit tx", error)
959
959
  }
960
+ await new Promise((resolve) =>
961
+ setTimeout(resolve, POLLING_INTERVALS.TRANSACTION_HISTORY),
962
+ )
960
963
  }
964
+ }
961
965
 
962
- // Wait for deposit and executeIntent
963
- await checkForDepositTx()
966
+ // Wait for deposit and executeIntent
967
+ await checkForDepositTx()
964
968
 
965
- // Now start polling for intent receipt after executeIntent has been called
966
- logger.console.log(
967
- "[trails-sdk] Starting polling for intent receipt after executeIntent (QR code mode)",
968
- )
969
- try {
970
- await pollIntentReceipt({
971
- intentId: intent.intentId,
972
- trailsClient,
973
- callbacks: {
974
- onOriginTransactionFound: async (originTxHash) => {
975
- logger.console.log(
976
- "[trails-sdk] Origin intent transaction discovered for same-chain QR code flow",
977
- { originTxHash },
978
- )
969
+ // Now start polling for intent receipt after executeIntent has been called
970
+ logger.console.log(
971
+ "[trails-sdk] Starting polling for intent receipt after executeIntent (QR code mode)",
972
+ )
973
+ try {
974
+ await pollIntentReceipt({
975
+ intentId: intent.intentId,
976
+ trailsClient,
977
+ callbacks: {
978
+ onOriginTransactionFound: async (originTxHash) => {
979
+ logger.console.log(
980
+ "[trails-sdk] Origin intent transaction discovered for same-chain QR code flow",
981
+ { originTxHash },
982
+ )
983
+
984
+ if (localTransactionStates[1]) {
985
+ try {
986
+ const originTxReceipt =
987
+ await effectivePublicClient.getTransactionReceipt({
988
+ hash: originTxHash as `0x${string}`,
989
+ })
979
990
 
980
- if (localTransactionStates[1]) {
981
- try {
982
- const originTxReceipt =
983
- await effectivePublicClient.getTransactionReceipt({
984
- hash: originTxHash as `0x${string}`,
985
- })
986
-
987
- localTransactionStates[1] =
988
- getTransactionStateFromReceipt(
989
- originTxReceipt,
990
- effectiveOriginChainId,
991
- localTransactionStates[1]?.label,
992
- )
993
- onTransactionStateChange(localTransactionStates)
994
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
995
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
996
- localTransactionStates,
997
- )
998
- }
999
-
1000
- originIntentTransaction = {
1001
- txnHash: originTxHash,
1002
- } as IntentTransaction
1003
- } catch (error) {
1004
- logger.console.error(
1005
- "[trails-sdk] Error fetching origin transaction receipt:",
1006
- error,
991
+ localTransactionStates[1] =
992
+ getTransactionStateFromReceipt(
993
+ originTxReceipt,
994
+ effectiveOriginChainId,
995
+ localTransactionStates[1]?.label,
996
+ )
997
+ onTransactionStateChange(localTransactionStates)
998
+ if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
999
+ checkoutOnHandlers.triggerCheckoutStatusUpdate(
1000
+ localTransactionStates,
1007
1001
  )
1008
1002
  }
1003
+
1004
+ originIntentTransaction = {
1005
+ txnHash: originTxHash,
1006
+ } as IntentTransaction
1007
+ } catch (error) {
1008
+ logger.console.error(
1009
+ "[trails-sdk] Error fetching origin transaction receipt:",
1010
+ error,
1011
+ )
1009
1012
  }
1010
- },
1013
+ }
1011
1014
  },
1012
- maxWaitTime: 120000, // 120 seconds max wait
1013
- })
1014
- } catch (error) {
1015
- logger.console.error(
1016
- "[trails-sdk] Error polling for intent receipt (QR code mode):",
1017
- error,
1018
- )
1019
- }
1020
- } else if (effectiveGasless && !depositUserTxnReceipt) {
1021
- // For gasless flows (non-QR code), start polling immediately
1022
- logger.console.log(
1023
- "[trails-sdk] Starting unified polling for gasless same-chain transaction with 2 states",
1015
+ },
1016
+ maxWaitTime: 120000, // 120 seconds max wait
1017
+ })
1018
+ } catch (error) {
1019
+ logger.console.error(
1020
+ "[trails-sdk] Error polling for intent receipt (QR code mode):",
1021
+ error,
1024
1022
  )
1025
- try {
1026
- await pollIntentReceipt({
1027
- intentId: intent.intentId,
1028
- trailsClient,
1029
- callbacks: {
1030
- onDepositTransactionFound: async (txHash) => {
1031
- logger.console.log(
1032
- "[trails-sdk] Deposit transaction discovered for same-chain gasless flow",
1033
- { txHash },
1034
- )
1023
+ }
1024
+ } else if (effectiveGasless && !depositUserTxnReceipt) {
1025
+ // For gasless flows (non-QR code), start polling immediately
1026
+ logger.console.log(
1027
+ "[trails-sdk] Starting unified polling for gasless same-chain transaction with 2 states",
1028
+ )
1029
+ try {
1030
+ await pollIntentReceipt({
1031
+ intentId: intent.intentId,
1032
+ trailsClient,
1033
+ callbacks: {
1034
+ onDepositTransactionFound: async (txHash) => {
1035
+ logger.console.log(
1036
+ "[trails-sdk] Deposit transaction discovered for same-chain gasless flow",
1037
+ { txHash },
1038
+ )
1039
+
1040
+ if (localTransactionStates[0]) {
1041
+ try {
1042
+ const depositTxReceipt =
1043
+ await effectivePublicClient.getTransactionReceipt({
1044
+ hash: txHash as `0x${string}`,
1045
+ })
1035
1046
 
1036
- if (localTransactionStates[0]) {
1037
- try {
1038
- const depositTxReceipt =
1039
- await effectivePublicClient.getTransactionReceipt({
1040
- hash: txHash as `0x${string}`,
1041
- })
1042
-
1043
- localTransactionStates[0] =
1044
- getTransactionStateFromReceipt(
1045
- depositTxReceipt,
1046
- effectiveOriginChainId,
1047
- localTransactionStates[0]?.label,
1048
- )
1049
- onTransactionStateChange(localTransactionStates)
1050
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
1051
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
1052
- localTransactionStates,
1053
- )
1054
- }
1055
-
1056
- depositUserTxnReceipt = depositTxReceipt
1057
- } catch (error) {
1058
- logger.console.error(
1059
- "[trails-sdk] Error fetching deposit transaction receipt:",
1060
- error,
1047
+ localTransactionStates[0] =
1048
+ getTransactionStateFromReceipt(
1049
+ depositTxReceipt,
1050
+ effectiveOriginChainId,
1051
+ localTransactionStates[0]?.label,
1052
+ )
1053
+ onTransactionStateChange(localTransactionStates)
1054
+ if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
1055
+ checkoutOnHandlers.triggerCheckoutStatusUpdate(
1056
+ localTransactionStates,
1061
1057
  )
1062
1058
  }
1059
+
1060
+ depositUserTxnReceipt = depositTxReceipt
1061
+ } catch (error) {
1062
+ logger.console.error(
1063
+ "[trails-sdk] Error fetching deposit transaction receipt:",
1064
+ error,
1065
+ )
1063
1066
  }
1064
- },
1065
- onOriginTransactionFound: async (originTxHash) => {
1066
- logger.console.log(
1067
- "[trails-sdk] Origin intent transaction discovered for same-chain gasless flow",
1068
- { originTxHash },
1069
- )
1067
+ }
1068
+ },
1069
+ onOriginTransactionFound: async (originTxHash) => {
1070
+ logger.console.log(
1071
+ "[trails-sdk] Origin intent transaction discovered for same-chain gasless flow",
1072
+ { originTxHash },
1073
+ )
1070
1074
 
1071
- if (localTransactionStates[1]) {
1072
- try {
1073
- const originTxReceipt =
1074
- await effectivePublicClient.getTransactionReceipt({
1075
- hash: originTxHash as `0x${string}`,
1076
- })
1077
-
1078
- localTransactionStates[1] =
1079
- getTransactionStateFromReceipt(
1080
- originTxReceipt,
1081
- effectiveOriginChainId,
1082
- localTransactionStates[1]?.label,
1083
- )
1084
- onTransactionStateChange(localTransactionStates)
1085
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
1086
- checkoutOnHandlers.triggerCheckoutStatusUpdate(
1087
- localTransactionStates,
1088
- )
1089
- }
1090
-
1091
- originIntentTransaction = {
1092
- txnHash: originTxHash,
1093
- } as IntentTransaction
1094
- } catch (error) {
1095
- logger.console.error(
1096
- "[trails-sdk] Error fetching origin transaction receipt:",
1097
- error,
1075
+ if (localTransactionStates[1]) {
1076
+ try {
1077
+ const originTxReceipt =
1078
+ await effectivePublicClient.getTransactionReceipt({
1079
+ hash: originTxHash as `0x${string}`,
1080
+ })
1081
+
1082
+ localTransactionStates[1] =
1083
+ getTransactionStateFromReceipt(
1084
+ originTxReceipt,
1085
+ effectiveOriginChainId,
1086
+ localTransactionStates[1]?.label,
1087
+ )
1088
+ onTransactionStateChange(localTransactionStates)
1089
+ if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
1090
+ checkoutOnHandlers.triggerCheckoutStatusUpdate(
1091
+ localTransactionStates,
1098
1092
  )
1099
1093
  }
1094
+
1095
+ originIntentTransaction = {
1096
+ txnHash: originTxHash,
1097
+ } as IntentTransaction
1098
+ } catch (error) {
1099
+ logger.console.error(
1100
+ "[trails-sdk] Error fetching origin transaction receipt:",
1101
+ error,
1102
+ )
1100
1103
  }
1101
- },
1104
+ }
1102
1105
  },
1103
- maxWaitTime: 120000, // 120 seconds max wait for both transactions
1104
- })
1105
- } catch (error) {
1106
- logger.console.error(
1107
- "[trails-sdk] Error polling for gasless same-chain transactions:",
1108
- error,
1109
- )
1110
- }
1106
+ },
1107
+ maxWaitTime: 120000, // 120 seconds max wait for both transactions
1108
+ })
1109
+ } catch (error) {
1110
+ logger.console.error(
1111
+ "[trails-sdk] Error polling for gasless same-chain transactions:",
1112
+ error,
1113
+ )
1111
1114
  }
1115
+ }
1112
1116
 
1113
- // For non-gasless flows, update the transaction state with the deposit receipt
1114
- // For gasless flows, skip this as localTransactionStates is already maintained by polling
1115
- if (depositUserTxnReceipt && !effectiveGasless) {
1116
- try {
1117
- const updatedStates = [
1118
- getTransactionStateFromReceipt(
1119
- depositUserTxnReceipt,
1120
- effectiveOriginChainId,
1121
- transactionStates[0]?.label,
1122
- ),
1123
- ]
1124
- onTransactionStateChange(updatedStates)
1125
- // Also trigger checkout status update if handler is provided
1126
- if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
1127
- checkoutOnHandlers.triggerCheckoutStatusUpdate(updatedStates)
1128
- }
1129
- } catch (error) {
1130
- logger.console.error(
1131
- "[trails-sdk] Error calling onTransactionStateChange:",
1132
- error,
1133
- )
1117
+ // For non-gasless flows, update the transaction state with the deposit receipt
1118
+ // For gasless flows, skip this as localTransactionStates is already maintained by polling
1119
+ if (depositUserTxnReceipt && !effectiveGasless) {
1120
+ try {
1121
+ const updatedStates = [
1122
+ getTransactionStateFromReceipt(
1123
+ depositUserTxnReceipt,
1124
+ effectiveOriginChainId,
1125
+ transactionStates[0]?.label,
1126
+ ),
1127
+ ]
1128
+ onTransactionStateChange(updatedStates)
1129
+ // Also trigger checkout status update if handler is provided
1130
+ if (checkoutOnHandlers?.triggerCheckoutStatusUpdate) {
1131
+ checkoutOnHandlers.triggerCheckoutStatusUpdate(updatedStates)
1134
1132
  }
1133
+ } catch (error) {
1134
+ logger.console.error(
1135
+ "[trails-sdk] Error calling onTransactionStateChange:",
1136
+ error,
1137
+ )
1135
1138
  }
1139
+ }
1136
1140
 
1137
- // Show conditional toast based on transaction status
1138
- const chainInfo = getChainInfo(effectiveOriginChainId)
1139
- if (depositUserTxnReceipt) {
1140
- if (depositUserTxnReceipt?.status === "success") {
1141
- updatePersistentToast(
1142
- "Transfer Confirmed",
1143
- `Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
1144
- "info",
1145
- )
1146
- } else {
1147
- updatePersistentToast(
1148
- "Transfer Failed",
1149
- `Your transaction on ${chainInfo?.name || "chain"} failed`,
1150
- "error",
1151
- )
1152
- }
1141
+ // Show conditional toast based on transaction status
1142
+ const chainInfo = getChainInfo(effectiveOriginChainId)
1143
+ if (depositUserTxnReceipt) {
1144
+ if (depositUserTxnReceipt?.status === "success") {
1145
+ updatePersistentToast(
1146
+ "Transfer Confirmed",
1147
+ `Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
1148
+ "info",
1149
+ )
1150
+ } else {
1151
+ updatePersistentToast(
1152
+ "Transfer Failed",
1153
+ `Your transaction on ${chainInfo?.name || "chain"} failed`,
1154
+ "error",
1155
+ )
1153
1156
  }
1157
+ }
1154
1158
 
1155
- // Track payment completion for same-chain same-token transaction
1156
- if (
1157
- depositUserTxnReceipt &&
1158
- depositUserTxnReceipt.status === "success"
1159
- ) {
1160
- // Check if any transaction failed or was refunded
1161
- const hasAnyCallFailed = transactionStates.some((tx) =>
1162
- tx?.decodedGuestModuleEvents?.some(
1163
- (e: any) => e?.type === "CallFailed",
1164
- ),
1159
+ // Track payment completion for same-chain same-token transaction
1160
+ if (
1161
+ depositUserTxnReceipt &&
1162
+ depositUserTxnReceipt.status === "success"
1163
+ ) {
1164
+ // Check if any transaction failed or was refunded
1165
+ const hasAnyCallFailed = transactionStates.some((tx) =>
1166
+ tx?.decodedGuestModuleEvents?.some(
1167
+ (e: any) => e?.type === "CallFailed",
1168
+ ),
1169
+ )
1170
+ const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
1171
+ const txStatus =
1172
+ !hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
1173
+
1174
+ // Always track payment completion regardless of success/failure
1175
+ trackPaymentCompleted({
1176
+ userAddress: account.address,
1177
+ originTxHash: depositUserTxnReceipt.transactionHash,
1178
+ originChainId: effectiveOriginChainId, // Same chain
1179
+ mode,
1180
+ fundMethod,
1181
+ originTokenAddress,
1182
+ originTokenSymbol,
1183
+ originTokenAmount: swapAmount,
1184
+ originTokenAmountFormatted: depositAmountFormatted.toString(),
1185
+ destinationTokenAddress: originTokenAddress, // same chain same token
1186
+ destinationTokenSymbol: destinationTokenSymbol,
1187
+ depositTokenAmount: swapAmount,
1188
+ depositTokenAmountFormatted: depositAmountFormatted.toString(),
1189
+ depositTokenAmountUsd: depositAmountUsd?.toString(),
1190
+ destinationTokenAmount: swapAmount, // same as deposit amount
1191
+ destinationTokenAmountFormatted: depositAmountFormatted.toString(), // same as deposit amount
1192
+ destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
1193
+ originTokenDecimals: originTokenDecimals,
1194
+ destinationTokenDecimals: originTokenDecimals, // same chain same token
1195
+ })
1196
+
1197
+ // Invalidate token balances cache after transaction (success or failure - gas was spent)
1198
+ invalidateTokenBalancesCache(account.address)
1199
+
1200
+ // Call onCheckoutComplete callback with transaction status
1201
+ if (checkoutOnHandlers?.triggerCheckoutComplete) {
1202
+ checkoutOnHandlers.triggerCheckoutComplete(
1203
+ txStatus,
1204
+ account.address,
1205
+ )
1206
+ }
1207
+ } else if (depositUserTxnReceipt) {
1208
+ trackPaymentError({
1209
+ error: "Transaction failed",
1210
+ userAddress: account.address,
1211
+ mode,
1212
+ fundMethod,
1213
+ originTokenAddress,
1214
+ })
1215
+
1216
+ // Invalidate token balances cache even on failure (gas was spent)
1217
+ invalidateTokenBalancesCache(account.address)
1218
+
1219
+ // Call onCheckoutError callback if provided
1220
+ if (checkoutOnHandlers?.triggerCheckoutError) {
1221
+ checkoutOnHandlers.triggerCheckoutError("Transaction failed")
1222
+ }
1223
+ } else if (effectiveGasless && !depositUserTxnReceipt) {
1224
+ // For gasless flows with polling, check if both transactions are confirmed
1225
+ const depositConfirmed =
1226
+ localTransactionStates[0]?.state === "confirmed"
1227
+ const originConfirmed =
1228
+ localTransactionStates[1]?.state === "confirmed"
1229
+
1230
+ if (depositConfirmed && originConfirmed) {
1231
+ logger.console.log(
1232
+ "[trails-sdk] Gasless same-chain transaction fully completed via polling",
1165
1233
  )
1166
- const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
1167
- const txStatus =
1168
- !hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
1234
+ const txStatus = "success"
1169
1235
 
1170
- // Always track payment completion regardless of success/failure
1171
1236
  trackPaymentCompleted({
1172
1237
  userAddress: account.address,
1173
- originTxHash: depositUserTxnReceipt.transactionHash,
1174
- originChainId: effectiveOriginChainId, // Same chain
1238
+ originTxHash:
1239
+ localTransactionStates[0]?.transactionHash ||
1240
+ localTransactionStates[1]?.transactionHash,
1241
+ originChainId: effectiveOriginChainId,
1175
1242
  mode,
1176
1243
  fundMethod,
1177
1244
  originTokenAddress,
@@ -1186,11 +1253,14 @@ export async function handleSameChainSameToken({
1186
1253
  destinationTokenAmount: swapAmount, // same as deposit amount
1187
1254
  destinationTokenAmountFormatted:
1188
1255
  depositAmountFormatted.toString(), // same as deposit amount
1189
- destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
1256
+ destinationTokenAmountUsd: depositAmountUsd?.toString(),
1190
1257
  originTokenDecimals: originTokenDecimals,
1191
1258
  destinationTokenDecimals: originTokenDecimals, // same chain same token
1192
1259
  })
1193
1260
 
1261
+ // Invalidate token balances cache after transaction (success or failure - gas was spent)
1262
+ invalidateTokenBalancesCache(account.address)
1263
+
1194
1264
  // Call onCheckoutComplete callback with transaction status
1195
1265
  if (checkoutOnHandlers?.triggerCheckoutComplete) {
1196
1266
  checkoutOnHandlers.triggerCheckoutComplete(
@@ -1198,69 +1268,10 @@ export async function handleSameChainSameToken({
1198
1268
  account.address,
1199
1269
  )
1200
1270
  }
1201
- } else if (depositUserTxnReceipt) {
1202
- trackPaymentError({
1203
- error: "Transaction failed",
1204
- userAddress: account.address,
1205
- mode,
1206
- fundMethod,
1207
- originTokenAddress,
1208
- })
1209
-
1210
- // Call onCheckoutError callback if provided
1211
- if (checkoutOnHandlers?.triggerCheckoutError) {
1212
- checkoutOnHandlers.triggerCheckoutError("Transaction failed")
1213
- }
1214
- } else if (effectiveGasless && !depositUserTxnReceipt) {
1215
- // For gasless flows with polling, check if both transactions are confirmed
1216
- const depositConfirmed =
1217
- localTransactionStates[0]?.state === "confirmed"
1218
- const originConfirmed =
1219
- localTransactionStates[1]?.state === "confirmed"
1220
-
1221
- if (depositConfirmed && originConfirmed) {
1222
- logger.console.log(
1223
- "[trails-sdk] Gasless same-chain transaction fully completed via polling",
1224
- )
1225
- const txStatus = "success"
1226
-
1227
- trackPaymentCompleted({
1228
- userAddress: account.address,
1229
- originTxHash:
1230
- localTransactionStates[0]?.transactionHash ||
1231
- localTransactionStates[1]?.transactionHash,
1232
- originChainId: effectiveOriginChainId,
1233
- mode,
1234
- fundMethod,
1235
- originTokenAddress,
1236
- originTokenSymbol,
1237
- originTokenAmount: swapAmount,
1238
- originTokenAmountFormatted: depositAmountFormatted.toString(),
1239
- destinationTokenAddress: originTokenAddress, // same chain same token
1240
- destinationTokenSymbol: destinationTokenSymbol,
1241
- depositTokenAmount: swapAmount,
1242
- depositTokenAmountFormatted: depositAmountFormatted.toString(),
1243
- depositTokenAmountUsd: depositAmountUsd?.toString(),
1244
- destinationTokenAmount: swapAmount, // same as deposit amount
1245
- destinationTokenAmountFormatted:
1246
- depositAmountFormatted.toString(), // same as deposit amount
1247
- destinationTokenAmountUsd: depositAmountUsd?.toString(),
1248
- originTokenDecimals: originTokenDecimals,
1249
- destinationTokenDecimals: originTokenDecimals, // same chain same token
1250
- })
1251
-
1252
- // Call onCheckoutComplete callback with transaction status
1253
- if (checkoutOnHandlers?.triggerCheckoutComplete) {
1254
- checkoutOnHandlers.triggerCheckoutComplete(
1255
- txStatus,
1256
- account.address,
1257
- )
1258
- }
1259
- } else if (depositConfirmed) {
1260
- logger.console.log(
1261
- "[trails-sdk] Gasless same-chain deposit confirmed, waiting for origin transaction",
1262
- )
1263
- }
1271
+ } else if (depositConfirmed) {
1272
+ logger.console.log(
1273
+ "[trails-sdk] Gasless same-chain deposit confirmed, waiting for origin transaction",
1274
+ )
1264
1275
  }
1265
1276
  }
1266
1277