0xtrails 0.2.5 → 0.3.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 (267) hide show
  1. package/dist/aave.d.ts +2 -0
  2. package/dist/aave.d.ts.map +1 -1
  3. package/dist/abortController.d.ts +8 -0
  4. package/dist/abortController.d.ts.map +1 -0
  5. package/dist/{ccip-CXlshvBY.js → ccip-BMB3uDZt.js} +1 -1
  6. package/dist/config.d.ts +0 -5
  7. package/dist/config.d.ts.map +1 -1
  8. package/dist/constants.d.ts +4 -4
  9. package/dist/constants.d.ts.map +1 -1
  10. package/dist/error.d.ts +4 -1
  11. package/dist/error.d.ts.map +1 -1
  12. package/dist/fees.d.ts +19 -0
  13. package/dist/fees.d.ts.map +1 -0
  14. package/dist/{index-_QuyGrjU.js → index-QXPUrZVv.js} +48719 -50852
  15. package/dist/index.d.ts +9 -8
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +811 -784
  18. package/dist/intentReceiptMonitor.d.ts +24 -0
  19. package/dist/intentReceiptMonitor.d.ts.map +1 -0
  20. package/dist/intentReceiptPoller.d.ts +69 -0
  21. package/dist/intentReceiptPoller.d.ts.map +1 -0
  22. package/dist/intents.d.ts +15 -11
  23. package/dist/intents.d.ts.map +1 -1
  24. package/dist/morpho.d.ts +6 -5
  25. package/dist/morpho.d.ts.map +1 -1
  26. package/dist/mutations.d.ts +16 -0
  27. package/dist/mutations.d.ts.map +1 -0
  28. package/dist/preconditions.d.ts +5 -4
  29. package/dist/preconditions.d.ts.map +1 -1
  30. package/dist/prepareSend.d.ts +7 -258
  31. package/dist/prepareSend.d.ts.map +1 -1
  32. package/dist/prices.d.ts +9 -6
  33. package/dist/prices.d.ts.map +1 -1
  34. package/dist/sequenceWallet.d.ts +3 -16
  35. package/dist/sequenceWallet.d.ts.map +1 -1
  36. package/dist/tokenBalances.d.ts +17 -13
  37. package/dist/tokenBalances.d.ts.map +1 -1
  38. package/dist/trails.d.ts +24 -40
  39. package/dist/trails.d.ts.map +1 -1
  40. package/dist/transactionIntent/constants.d.ts +7 -0
  41. package/dist/transactionIntent/constants.d.ts.map +1 -0
  42. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +44 -0
  43. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -0
  44. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts +30 -0
  45. package/dist/transactionIntent/deposits/gaslessDeposit.d.ts.map +1 -0
  46. package/dist/transactionIntent/deposits/index.d.ts +4 -0
  47. package/dist/transactionIntent/deposits/index.d.ts.map +1 -0
  48. package/dist/transactionIntent/deposits/standardDeposit.d.ts +30 -0
  49. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -0
  50. package/dist/transactionIntent/execution/index.d.ts +2 -0
  51. package/dist/transactionIntent/execution/index.d.ts.map +1 -0
  52. package/dist/transactionIntent/execution/transactionState.d.ts +5 -0
  53. package/dist/transactionIntent/execution/transactionState.d.ts.map +1 -0
  54. package/dist/transactionIntent/handlers/crossChain.d.ts +82 -0
  55. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -0
  56. package/dist/transactionIntent/handlers/index.d.ts +4 -0
  57. package/dist/transactionIntent/handlers/index.d.ts.map +1 -0
  58. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts +62 -0
  59. package/dist/transactionIntent/handlers/sameChainDifferentToken.d.ts.map +1 -0
  60. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +72 -0
  61. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -0
  62. package/dist/transactionIntent/index.d.ts +9 -0
  63. package/dist/transactionIntent/index.d.ts.map +1 -0
  64. package/dist/transactionIntent/quote/feeExtractors.d.ts +17 -0
  65. package/dist/transactionIntent/quote/feeExtractors.d.ts.map +1 -0
  66. package/dist/transactionIntent/quote/index.d.ts +4 -0
  67. package/dist/transactionIntent/quote/index.d.ts.map +1 -0
  68. package/dist/transactionIntent/quote/normalizeQuote.d.ts +34 -0
  69. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -0
  70. package/dist/transactionIntent/quote/quoteHelpers.d.ts +5 -0
  71. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -0
  72. package/dist/transactionIntent/types.d.ts +131 -0
  73. package/dist/transactionIntent/types.d.ts.map +1 -0
  74. package/dist/transactionIntent/utils/balanceChecker.d.ts +18 -0
  75. package/dist/transactionIntent/utils/balanceChecker.d.ts.map +1 -0
  76. package/dist/transactionIntent/utils/index.d.ts +4 -0
  77. package/dist/transactionIntent/utils/index.d.ts.map +1 -0
  78. package/dist/transactionIntent/utils/lifiHelpers.d.ts +10 -0
  79. package/dist/transactionIntent/utils/lifiHelpers.d.ts.map +1 -0
  80. package/dist/transactionIntent/utils/testnetHelpers.d.ts +3 -0
  81. package/dist/transactionIntent/utils/testnetHelpers.d.ts.map +1 -0
  82. package/dist/transactionIntent/validators.d.ts +6 -0
  83. package/dist/transactionIntent/validators.d.ts.map +1 -0
  84. package/dist/transactions.d.ts +6 -3
  85. package/dist/transactions.d.ts.map +1 -1
  86. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
  87. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
  88. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  89. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  90. package/dist/widget/components/ClassicSwap.d.ts +2 -3
  91. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  92. package/dist/widget/components/ConfigDisplay.d.ts.map +1 -1
  93. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  94. package/dist/widget/components/ConnectedWallets.d.ts.map +1 -1
  95. package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
  96. package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
  97. package/dist/widget/components/DynamicSizeInputField.d.ts +13 -0
  98. package/dist/widget/components/DynamicSizeInputField.d.ts.map +1 -0
  99. package/dist/widget/components/Earn.d.ts +2 -3
  100. package/dist/widget/components/Earn.d.ts.map +1 -1
  101. package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
  102. package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
  103. package/dist/widget/components/FeeBreakdown.d.ts +9 -0
  104. package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
  105. package/dist/widget/components/FeeOptions.d.ts +5 -13
  106. package/dist/widget/components/FeeOptions.d.ts.map +1 -1
  107. package/dist/widget/components/Fund.d.ts +2 -3
  108. package/dist/widget/components/Fund.d.ts.map +1 -1
  109. package/dist/widget/components/FundMethods.d.ts.map +1 -1
  110. package/dist/widget/components/FundSwap.d.ts +2 -3
  111. package/dist/widget/components/FundSwap.d.ts.map +1 -1
  112. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -1
  113. package/dist/widget/components/Identicon.d.ts.map +1 -1
  114. package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
  115. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  116. package/dist/widget/components/Modal.d.ts.map +1 -1
  117. package/dist/widget/components/Pay.d.ts +2 -3
  118. package/dist/widget/components/Pay.d.ts.map +1 -1
  119. package/dist/widget/components/PoolDeposit.d.ts +3 -3
  120. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  121. package/dist/widget/components/PoolWithdraw.d.ts +3 -20
  122. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -1
  123. package/dist/widget/components/QuoteDetails.d.ts +2 -0
  124. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  125. package/dist/widget/components/Receipt.d.ts.map +1 -1
  126. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
  127. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  128. package/dist/widget/components/Swap.d.ts +2 -3
  129. package/dist/widget/components/Swap.d.ts.map +1 -1
  130. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  131. package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
  132. package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
  133. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  134. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -1
  135. package/dist/widget/components/Tooltip.d.ts +9 -0
  136. package/dist/widget/components/Tooltip.d.ts.map +1 -0
  137. package/dist/widget/components/TransferPendingVertical.d.ts.map +1 -1
  138. package/dist/widget/components/WaasFeeOptions.d.ts +1 -0
  139. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -1
  140. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  141. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  142. package/dist/widget/css/compiled.css +2 -2
  143. package/dist/widget/hooks/useCheckout.d.ts +17 -4
  144. package/dist/widget/hooks/useCheckout.d.ts.map +1 -1
  145. package/dist/widget/hooks/useDefaultTokenSelection.d.ts.map +1 -1
  146. package/dist/widget/hooks/useQuote.d.ts +82 -0
  147. package/dist/widget/hooks/useQuote.d.ts.map +1 -0
  148. package/dist/widget/hooks/useSelectedFeeToken.d.ts +1 -0
  149. package/dist/widget/hooks/useSelectedFeeToken.d.ts.map +1 -1
  150. package/dist/widget/hooks/useSendForm.d.ts +5 -6
  151. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  152. package/dist/widget/hooks/useTokenList.d.ts.map +1 -1
  153. package/dist/widget/hooks/useWalletConnectionContext.d.ts +25 -0
  154. package/dist/widget/hooks/useWalletConnectionContext.d.ts.map +1 -0
  155. package/dist/widget/index.js +2 -2
  156. package/dist/widget/widget.d.ts +17 -7
  157. package/dist/widget/widget.d.ts.map +1 -1
  158. package/package.json +19 -21
  159. package/src/aave.ts +54 -1
  160. package/src/abortController.ts +35 -0
  161. package/src/config.ts +57 -58
  162. package/src/constants.ts +11 -9
  163. package/src/error.ts +21 -3
  164. package/src/fees.ts +210 -0
  165. package/src/index.ts +35 -13
  166. package/src/intentReceiptMonitor.ts +102 -0
  167. package/src/intentReceiptPoller.ts +299 -0
  168. package/src/intents.ts +205 -171
  169. package/src/morpho.ts +58 -9
  170. package/src/mutations.ts +129 -0
  171. package/src/preconditions.ts +16 -21
  172. package/src/prepareSend.ts +92 -4699
  173. package/src/prices.ts +26 -22
  174. package/src/relaySdk.ts +2 -2
  175. package/src/sequenceWallet.ts +6 -73
  176. package/src/tokenBalances.ts +175 -69
  177. package/src/trails.ts +230 -722
  178. package/src/transactionIntent/constants.ts +11 -0
  179. package/src/transactionIntent/deposits/depositOrchestrator.ts +210 -0
  180. package/src/transactionIntent/deposits/gaslessDeposit.ts +588 -0
  181. package/src/transactionIntent/deposits/index.ts +3 -0
  182. package/src/transactionIntent/deposits/standardDeposit.ts +379 -0
  183. package/src/transactionIntent/execution/index.ts +1 -0
  184. package/src/transactionIntent/execution/transactionState.ts +35 -0
  185. package/src/transactionIntent/handlers/crossChain.ts +1707 -0
  186. package/src/transactionIntent/handlers/index.ts +3 -0
  187. package/src/transactionIntent/handlers/sameChainDifferentToken.ts +323 -0
  188. package/src/transactionIntent/handlers/sameChainSameToken.ts +712 -0
  189. package/src/transactionIntent/index.ts +9 -0
  190. package/src/transactionIntent/quote/feeExtractors.ts +81 -0
  191. package/src/transactionIntent/quote/index.ts +3 -0
  192. package/src/transactionIntent/quote/normalizeQuote.ts +367 -0
  193. package/src/transactionIntent/quote/quoteHelpers.ts +53 -0
  194. package/src/transactionIntent/types.ts +157 -0
  195. package/src/transactionIntent/utils/balanceChecker.ts +96 -0
  196. package/src/transactionIntent/utils/index.ts +3 -0
  197. package/src/transactionIntent/utils/lifiHelpers.ts +68 -0
  198. package/src/transactionIntent/utils/testnetHelpers.ts +10 -0
  199. package/src/transactionIntent/validators.ts +57 -0
  200. package/src/transactions.ts +98 -71
  201. package/src/widget/compiled.css +2 -2
  202. package/src/widget/components/AccountIntentTransactionHistory.tsx +36 -36
  203. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
  204. package/src/widget/components/AccountSettings.tsx +70 -41
  205. package/src/widget/components/ChainFilterDropdown.tsx +24 -3
  206. package/src/widget/components/ClassicSwap.tsx +44 -107
  207. package/src/widget/components/ConfigDisplay.tsx +0 -11
  208. package/src/widget/components/ConnectWallet.tsx +4 -1
  209. package/src/widget/components/ConnectedWallets.tsx +51 -25
  210. package/src/widget/components/DynamicInputStyles.tsx +76 -0
  211. package/src/widget/components/DynamicSizeInputField.tsx +109 -0
  212. package/src/widget/components/Earn.tsx +34 -45
  213. package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
  214. package/src/widget/components/FeeBreakdown.tsx +155 -0
  215. package/src/widget/components/FeeOption.tsx +2 -2
  216. package/src/widget/components/FeeOptions.tsx +151 -112
  217. package/src/widget/components/Fund.tsx +10 -29
  218. package/src/widget/components/FundMethods.tsx +4 -3
  219. package/src/widget/components/FundSwap.tsx +2 -3
  220. package/src/widget/components/FundingMethodSelectorButton.tsx +24 -14
  221. package/src/widget/components/Identicon.tsx +164 -95
  222. package/src/widget/components/MeshConnectExchanges.tsx +2 -15
  223. package/src/widget/components/Modal.tsx +0 -12
  224. package/src/widget/components/Pay.tsx +72 -75
  225. package/src/widget/components/PoolDeposit.tsx +221 -242
  226. package/src/widget/components/PoolWithdraw.tsx +347 -469
  227. package/src/widget/components/PriceImpactWarning.tsx +1 -1
  228. package/src/widget/components/QuoteDetails.tsx +906 -484
  229. package/src/widget/components/Receipt.tsx +16 -2
  230. package/src/widget/components/RecipientSelectorButton.tsx +7 -5
  231. package/src/widget/components/Recipients.tsx +1 -1
  232. package/src/widget/components/ScreenHeader.tsx +60 -36
  233. package/src/widget/components/Swap.tsx +2 -3
  234. package/src/widget/components/ThemeProvider.tsx +2 -1
  235. package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
  236. package/src/widget/components/TokenImage.tsx +1 -1
  237. package/src/widget/components/TokenSelector.tsx +62 -53
  238. package/src/widget/components/TokenSelectorButton.tsx +38 -15
  239. package/src/widget/components/Tooltip.tsx +51 -0
  240. package/src/widget/components/TransferPendingVertical.tsx +12 -8
  241. package/src/widget/components/WaasFeeOptions.tsx +139 -4
  242. package/src/widget/components/WalletConfirmation.tsx +23 -13
  243. package/src/widget/components/WalletConnect.tsx +93 -29
  244. package/src/widget/hooks/useAmountUsd.ts +9 -9
  245. package/src/widget/hooks/useCheckout.ts +97 -9
  246. package/src/widget/hooks/useDefaultTokenSelection.tsx +27 -21
  247. package/src/widget/hooks/useQuote.ts +466 -0
  248. package/src/widget/hooks/useSelectedFeeToken.tsx +32 -37
  249. package/src/widget/hooks/useSendForm.ts +45 -51
  250. package/src/widget/hooks/useTokenList.ts +34 -26
  251. package/src/widget/hooks/useWalletConnectionContext.tsx +128 -0
  252. package/src/widget/widget.tsx +365 -390
  253. package/dist/apiClient.d.ts +0 -9
  254. package/dist/apiClient.d.ts.map +0 -1
  255. package/dist/intentEntrypoint.d.ts +0 -114
  256. package/dist/intentEntrypoint.d.ts.map +0 -1
  257. package/dist/metaTxnMonitor.d.ts +0 -15
  258. package/dist/metaTxnMonitor.d.ts.map +0 -1
  259. package/dist/metaTxns.d.ts +0 -11
  260. package/dist/metaTxns.d.ts.map +0 -1
  261. package/dist/relayer.d.ts +0 -43
  262. package/dist/relayer.d.ts.map +0 -1
  263. package/src/apiClient.ts +0 -35
  264. package/src/intentEntrypoint.ts +0 -203
  265. package/src/metaTxnMonitor.ts +0 -171
  266. package/src/metaTxns.ts +0 -45
  267. package/src/relayer.ts +0 -289
@@ -1,368 +1,57 @@
1
- import type {
2
- GetIntentCallsPayloadParams,
3
- GetIntentCallsPayloadsReturn,
4
- IntentPrecondition,
5
- } from "@0xsequence/trails-api"
6
- import type { TrailsAPIClient } from "@0xsequence/trails-api"
7
- import type { Relayer } from "@0xsequence/relayer"
8
- import { useQuery } from "@tanstack/react-query"
9
- import type {
10
- Account,
11
- Chain,
12
- PublicClient,
13
- TransactionReceipt,
14
- WalletClient,
15
- } from "viem"
16
- import {
17
- createPublicClient,
18
- createWalletClient,
19
- decodeFunctionData,
20
- erc20Abi,
21
- formatUnits,
22
- http,
23
- maxUint256,
24
- parseUnits,
25
- zeroAddress,
26
- } from "viem"
27
- import { generatePrivateKey, privateKeyToAccount } from "viem/accounts"
28
- import { mainnet } from "viem/chains"
29
- import {
30
- trackPaymentCompleted,
31
- trackPaymentError,
32
- trackPaymentStarted,
33
- trackRelayerCallCompleted,
34
- trackRelayerCallError,
35
- trackRelayerCallStarted,
36
- trackTransactionConfirmed,
37
- } from "./analytics.js"
38
- import { useAPIClient } from "./apiClient.js"
39
- import type { Attestation } from "./cctp.js"
40
- import {
41
- approveERC20,
42
- cctpTransfer,
43
- cctpTransferWithCustomCall,
44
- getCCTPRelayerCallData,
45
- getIsUsdcAddress,
46
- getMessageTransmitter,
47
- getMintUSDCData,
48
- getNeedsApproval,
49
- getUSDCTokenAddress,
50
- } from "./cctp.js"
51
- import { queueCCTPTransfer } from "./cctpqueue.js"
52
- import { getChainInfo, getTestnetChainInfo } from "./chains.js"
53
- import { attemptSwitchChain } from "./chainSwitch.js"
54
- import {
55
- getSequenceEnv,
56
- getSlippageTolerance,
57
- type SequenceEnv,
58
- } from "./config.js"
59
- import { TRAILS_INTENT_ENTRYPOINT_ADDRESS } from "./constants.js"
60
- import {
61
- decodeGuestModuleEvents,
62
- decodeTrailsTokenSweeperEvents,
63
- } from "./decoders.js"
1
+ import { abortControllerRegistry } from "./abortController.js"
2
+ import { createPublicClient, http, zeroAddress } from "viem"
3
+ import { trackPaymentError, trackPaymentStarted } from "./analytics.js"
4
+ import { getSlippageTolerance } from "./config.js"
64
5
  import { getERC20TransferData } from "./encoders.js"
65
- import { getFullErrorMessage, InsufficientBalanceError } from "./error.js"
66
- import {
67
- estimateGasCost,
68
- estimateGasCostUsd,
69
- estimateGasLimit,
70
- DEFAULT_MIN_GASLIMIT,
71
- } from "./estimate.js"
72
- import { getExplorerUrl } from "./explorer.js"
73
- import {
74
- getNeedsIntentEntrypointApproval,
75
- getPermitSignature,
76
- getUserNonce,
77
- signIntent,
78
- } from "./gasless.js"
79
- import { getIntentEntrypointFeeOptions } from "./intentEntrypoint.js"
80
- import { useIndexerGatewayClient } from "./indexerClient.js"
81
- import {
82
- buildSameChainTransactionParams,
83
- buildCrossChainDepositParams,
84
- commitIntentConfig,
85
- getIntentCallsPayloads as getIntentCallsPayloadsFromIntents,
86
- sendOriginTransaction,
87
- } from "./intents.js"
88
- import type { IntentRequestParams } from "./intents.js"
89
- import type { MetaTxn } from "./metaTxnMonitor.js"
90
- import { getMetaTxStatus } from "./metaTxnMonitor.js"
91
- import { relayerSendMetaTx } from "./metaTxns.js"
92
- import { findFirstPreconditionForChainId } from "./preconditions.js"
93
- import { calcAmountUsdPrice, getTokenPrice } from "./prices.js"
94
- import { getQueryParam } from "./queryParams.js"
95
- import type { MetaTxnReceipt, RelayerEnv } from "./relayer.js"
96
- import { useRelayers } from "./relayer.js"
97
- import {
98
- executeSimpleRelayTransaction,
99
- getRelaySDKQuote,
100
- getTxHashFromRelayResult,
101
- waitForRelayDestinationTx,
102
- type RelayQuote,
103
- type RelayTradeType,
104
- } from "./relaySdk.js"
105
- import {
106
- sequenceSendTransaction,
107
- simpleCreateSequenceWallet,
108
- } from "./sequenceWallet.js"
109
- import {
110
- formatAmount,
111
- formatAmountDisplay,
112
- formatRawAmount,
113
- formatUsdAmountDisplay,
114
- getTokenBalancesWithPrices,
115
- } from "./tokenBalances.js"
116
- import {
117
- getTokenInfo,
118
- useSupportedTokens,
119
- type SupportedToken,
120
- } from "./tokens.js"
6
+ import { getTokenPrice } from "./prices.js"
121
7
  import {
122
8
  TRAILS_ROUTER_PLACEHOLDER_AMOUNT,
123
9
  wrapCalldataWithTrailsRouterIfNeeded,
124
10
  } from "./trailsRouter.js"
125
- import type {
126
- TransactionState,
127
- TransactionStateStatus,
128
- } from "./transactions.js"
129
- import { getAccountTransactionHistory, getTxTimeDiff } from "./transactions.js"
130
- import { requestWithTimeout } from "./utils.js"
131
- import type { CheckoutOnHandlers } from "./widget/hooks/useCheckout.js"
11
+ import type { TransactionState } from "./transactions.js"
132
12
  import { logger } from "./logger.js"
133
13
  import { getIsCustomCalldata } from "./contractUtils.js"
134
- import type { SequenceAPIClient } from "@0xsequence/api"
135
- import { useTrailsClient } from "./trailsClient.js"
136
- import { updatePersistentToast } from "./toast.js"
137
- import {
138
- getDelegatorSmartAccount,
139
- getPaymasterGaslessTransaction,
140
- sendPaymasterGaslessTransaction,
141
- } from "./paymasterSend.js"
142
-
143
- export enum TradeType {
144
- EXACT_INPUT = "EXACT_INPUT",
145
- EXACT_OUTPUT = "EXACT_OUTPUT",
146
- }
147
-
148
- export type PrepareSendOptions = {
149
- account: Account
150
- originTokenAddress: string
151
- originChainId: number
152
- originTokenBalance: string
153
- destinationChainId: number
154
- recipient: string
155
- destinationTokenAddress: string
156
- swapAmount: string
157
- tradeType?: TradeType
158
- originTokenSymbol: string
159
- destinationTokenSymbol: string
160
- sequenceProjectAccessKey?: string
161
- fee: string
162
- client?: WalletClient
163
- dryMode: boolean
164
- apiClient: SequenceAPIClient
165
- trailsClient: TrailsAPIClient
166
- originRelayer: Relayer.RpcRelayer
167
- destinationRelayer: Relayer.RpcRelayer
168
- destinationCalldata?: string
169
- onTransactionStateChange: (transactionStates: TransactionState[]) => void
170
- sourceTokenPriceUsd?: number | null
171
- destinationTokenPriceUsd?: number | null
172
- sourceTokenDecimals: number
173
- destinationTokenDecimals: number
174
- paymasterUrl?: string
175
- gasless?: boolean
176
- slippageTolerance?: string
177
- originNativeTokenPriceUsd?: number | null
178
- quoteProvider?: string | null
179
- fundMethod?: string
180
- mode?: "pay" | "fund" | "earn" | "swap" | "receive"
181
- checkoutOnHandlers?: CheckoutOnHandlers
182
- refundAddress?: string
183
- selectedFeeToken?: any
184
- walletId?: string
185
- }
186
-
187
- export type PrepareSendFees = {
188
- feeTokenAddress: string | null
189
- totalFeeAmount: string | null
190
- totalFeeAmountUsd: string | null
191
- totalFeeAmountUsdDisplay: string | null
192
- }
193
-
194
- export type PrepareSendQuote = {
195
- originDepositAddress: string
196
- destinationDepositAddress: string
197
- destinationAddress: string
198
- destinationCalldata: string
199
- originChain: Chain
200
- destinationChain: Chain
201
- originAmount: string
202
- originAmountDisplay: string
203
- originAmountMin: string
204
- originAmountMinDisplay: string
205
- originAmountMinUsdFormatted: string
206
- originAmountMinUsdDisplay: string
207
- destinationAmount: string
208
- destinationAmountDisplay: string
209
- destinationAmountMin: string
210
- destinationAmountMinDisplay: string
211
- destinationAmountMinUsdFormatted: string
212
- destinationAmountMinUsdDisplay: string
213
- originAmountFormatted: string
214
- originAmountUsdFormatted: string
215
- originAmountUsdDisplay: string
216
- destinationAmountFormatted: string
217
- destinationAmountUsdFormatted: string
218
- destinationAmountUsdDisplay: string
219
- originToken: SupportedToken
220
- destinationToken: SupportedToken
221
- fees: PrepareSendFees
222
- slippageTolerance: string
223
- priceImpact: string
224
- priceImpactUsdDisplay: string
225
- completionEstimateSeconds: number
226
- transactionStates: TransactionState[]
227
- gasCostUsd: number
228
- gasCostUsdDisplay: string
229
- gasCost: string
230
- gasCostFormatted: string
231
- originTokenRate: string
232
- destinationTokenRate: string
233
- quoteProvider: QuoteProviderInfo | null
234
- noSufficientBalance: boolean
235
- }
236
-
237
- export type PrepareSendReturn = {
238
- quote: PrepareSendQuote
239
- feeOptions?: any
240
- send: ({
241
- onOriginSend,
242
- selectedFeeToken,
243
- }: {
244
- onOriginSend?: () => void
245
- selectedFeeToken?: any
246
- }) => Promise<SendReturn>
247
- }
248
-
249
- export type SendReturn = {
250
- originUserTxReceipt: TransactionReceipt | null
251
- originMetaTxnReceipt: MetaTxnReceipt | null
252
- destinationMetaTxnReceipt: MetaTxnReceipt | null
253
- totalCompletionSeconds?: number
254
- }
255
-
256
- export function getIsToSameChain(
257
- originChainId: number,
258
- destinationChainId: number,
259
- ): boolean {
260
- return originChainId?.toString() === destinationChainId?.toString()
261
- }
262
-
263
- export function getIsToSameToken(
264
- originTokenAddress: string,
265
- destinationTokenAddress: string,
266
- ): boolean {
267
- return (
268
- originTokenAddress?.toLowerCase() === destinationTokenAddress?.toLowerCase()
269
- )
270
- }
271
-
272
- export function getIsToSameChainAndToken(
273
- originChainId: number,
274
- originTokenAddress: string,
275
- destinationChainId: number,
276
- destinationTokenAddress: string,
277
- ): boolean {
278
- return (
279
- getIsToSameChain(originChainId, destinationChainId) &&
280
- getIsToSameToken(originTokenAddress, destinationTokenAddress)
281
- )
282
- }
283
-
284
- export function getUseCctp(
285
- originTokenAddress: string,
286
- destinationTokenAddress: string,
287
- originChainId: number,
288
- destinationChainId: number,
289
- ) {
290
- return (
291
- getIsUsdcAddress(originTokenAddress, originChainId) &&
292
- getIsUsdcAddress(destinationTokenAddress, destinationChainId)
293
- )
294
- }
295
-
296
- export function validateCctpDestinationToken(
297
- destinationTokenAddress: string,
298
- destinationChainId: number,
299
- quoteProvider?: string | null,
300
- ): void {
301
- // If CCTP provider is selected, destination token must be USDC
302
- if (
303
- quoteProvider === "cctp" &&
304
- !getIsUsdcAddress(destinationTokenAddress, destinationChainId)
305
- ) {
306
- throw new Error(
307
- `CCTP provider requires destination token to be USDC. Current destination token: ${destinationTokenAddress} on chain ${destinationChainId}. Please ensure the destination token is USDC as listed in the Circle CCTP documentation.`,
308
- )
309
- }
310
- }
311
-
312
- function isTestnetDebugMode(): boolean {
313
- return getQueryParam("testnet") === "true"
314
- }
315
-
316
- function getTestnetOriginTokenAddress(testnetChainId: number): string {
317
- return getUSDCTokenAddress(testnetChainId)!
318
- }
319
-
320
- function getIntentArgs(
321
- mainSignerAddress: string,
322
- originChainId: number,
323
- originTokenAddress: string,
324
- originTokenAmount: string,
325
- destinationChainId: number,
326
- destinationTokenAddress: string,
327
- destinationTokenAmount: string,
328
- destinationTokenSymbol: string,
329
- recipient: string,
330
- destinationCalldata: string | undefined,
331
- destinationSalt: string = Date.now().toString(),
332
- slippageTolerance: string, // 0.03 = 3%
333
- tradeType: TradeType,
334
- provider?: string | null,
335
- ): GetIntentCallsPayloadParams {
336
- const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
337
-
338
- if (!provider || provider === "auto") {
339
- provider = undefined
340
- }
341
-
342
- const intentArgs = {
343
- userAddress: mainSignerAddress,
344
- originChainId,
345
- originTokenAddress,
346
- originTokenAmount: originTokenAmount, // max amount for exact_output
347
- destinationChainId,
348
- destinationToAddress: recipient,
349
- destinationTokenAddress: destinationTokenAddress,
350
- destinationTokenAmount: destinationTokenAmount,
351
- destinationTokenSymbol: destinationTokenSymbol,
352
- destinationCallData: hasCustomCalldata ? destinationCalldata : "0x",
353
- destinationCallValue: "0",
354
- destinationSalt,
355
- slippageTolerance: Number(slippageTolerance),
356
- tradeType,
357
- provider,
358
- }
14
+ import { getChainInfo } from "./chains.js"
359
15
 
360
- return intentArgs
361
- }
16
+ // Import from transactionIntent module
17
+ import {
18
+ TradeType,
19
+ type PrepareSendOptions,
20
+ type PrepareSendReturn,
21
+ handleSameChainSameToken,
22
+ handleCrossChain,
23
+ isSameChain,
24
+ isSameToken,
25
+ validateCctpDestinationToken,
26
+ } from "./transactionIntent/index.js"
27
+
28
+ // Re-export types for backward compatibility
29
+ export { TradeType } from "./transactionIntent/index.js"
30
+ export type {
31
+ PrepareSendOptions,
32
+ PrepareSendFees,
33
+ PrepareSendQuote,
34
+ PrepareSendReturn,
35
+ SendReturn,
36
+ SelectedFeeToken,
37
+ } from "./transactionIntent/index.js"
38
+
39
+ // Re-export validators for backward compatibility
40
+ export {
41
+ isSameChain,
42
+ isSameToken,
43
+ validateCctpDestinationToken,
44
+ } from "./transactionIntent/index.js"
362
45
 
363
46
  export async function prepareSend(
364
47
  options: PrepareSendOptions,
365
48
  ): Promise<PrepareSendReturn> {
49
+ // Abort all existing operations when a new quote is generated
50
+ logger.console.log(
51
+ "[trails-sdk] New quote generated - aborting all existing operations",
52
+ )
53
+ abortControllerRegistry.abortAll()
54
+
366
55
  const {
367
56
  account,
368
57
  originTokenAddress,
@@ -378,16 +67,12 @@ export async function prepareSend(
378
67
  fee,
379
68
  client: walletClient,
380
69
  dryMode = false,
381
- apiClient,
382
70
  trailsClient,
383
- originRelayer,
384
- destinationRelayer,
385
71
  destinationCalldata,
386
72
  onTransactionStateChange,
387
73
  sourceTokenDecimals,
388
74
  destinationTokenDecimals,
389
75
  paymasterUrl,
390
- gasless = false,
391
76
  slippageTolerance = getSlippageTolerance(),
392
77
  originNativeTokenPriceUsd,
393
78
  quoteProvider,
@@ -396,6 +81,9 @@ export async function prepareSend(
396
81
  checkoutOnHandlers,
397
82
  selectedFeeToken,
398
83
  walletId,
84
+ abortSignal,
85
+ commitIntentFn,
86
+ executeIntentFn,
399
87
  } = options
400
88
  let { sourceTokenPriceUsd, destinationTokenPriceUsd } = options
401
89
 
@@ -464,12 +152,12 @@ export async function prepareSend(
464
152
  originTokenAddress,
465
153
  originChainId,
466
154
  })
467
- const price = await getTokenPrice(apiClient, {
468
- tokenId: originTokenAddress,
469
- contractAddress: originTokenAddress,
155
+ const price = await getTokenPrice(trailsClient, {
156
+ tokenSymbol: originTokenAddress,
157
+ tokenAddress: originTokenAddress,
470
158
  chainId: originChainId,
471
159
  })
472
- sourceTokenPriceUsd = price?.price?.value ?? 0
160
+ sourceTokenPriceUsd = price?.priceUsd ?? 0
473
161
  logger.console.log(
474
162
  "[trails-sdk] source token price:",
475
163
  sourceTokenPriceUsd,
@@ -488,12 +176,12 @@ export async function prepareSend(
488
176
  destinationTokenAddress,
489
177
  destinationChainId,
490
178
  })
491
- const price = await getTokenPrice(apiClient, {
492
- tokenId: destinationTokenAddress,
493
- contractAddress: destinationTokenAddress,
179
+ const price = await getTokenPrice(trailsClient, {
180
+ tokenSymbol: destinationTokenAddress,
181
+ tokenAddress: destinationTokenAddress,
494
182
  chainId: destinationChainId,
495
183
  })
496
- destinationTokenPriceUsd = price?.price?.value ?? 0
184
+ destinationTokenPriceUsd = price?.priceUsd ?? 0
497
185
  logger.console.log(
498
186
  "[trails-sdk] destination token price:",
499
187
  destinationTokenPriceUsd,
@@ -556,11 +244,8 @@ export async function prepareSend(
556
244
  })
557
245
  throw new Error(`Chain ${originChainId} not found`)
558
246
  }
559
- const isToSameChain = getIsToSameChain(originChainId, destinationChainId)
560
- const isToSameToken = getIsToSameToken(
561
- originTokenAddress,
562
- destinationTokenAddress,
563
- )
247
+ const isToSameChain = isSameChain(originChainId, destinationChainId)
248
+ const isToSameToken = isSameToken(originTokenAddress, destinationTokenAddress)
564
249
 
565
250
  logger.console.log("[trails-sdk] isToSameChain", isToSameChain)
566
251
  logger.console.log("[trails-sdk] isToSameToken", isToSameToken)
@@ -646,15 +331,17 @@ export async function prepareSend(
646
331
  // }
647
332
 
648
333
  if (isToSameToken && isToSameChain) {
649
- return await sendHandlerForSameChainSameToken({
334
+ return await handleSameChainSameToken({
335
+ mainSignerAddress,
650
336
  originTokenAddress,
651
337
  originTokenDecimals: sourceTokenDecimals,
338
+ originTokenSymbol,
339
+ destinationTokenSymbol,
652
340
  swapAmount,
653
341
  destinationCalldata: effectiveDestinationCalldata,
654
342
  recipient: effectiveDestinationAddress,
655
343
  originChainId,
656
344
  walletClient,
657
- publicClient,
658
345
  onTransactionStateChange,
659
346
  dryMode,
660
347
  account,
@@ -663,19 +350,21 @@ export async function prepareSend(
663
350
  sourceTokenPriceUsd,
664
351
  destinationTokenPriceUsd,
665
352
  originNativeTokenPriceUsd,
666
- slippageTolerance,
353
+ slippageTolerance: slippageTolerance,
667
354
  checkoutOnHandlers,
668
- gasless,
669
355
  paymasterUrl,
670
356
  selectedFeeToken,
357
+ walletId,
671
358
  trailsClient,
672
- originRelayer,
673
359
  mode,
674
360
  fundMethod,
361
+ abortSignal,
362
+ commitIntentFn,
363
+ executeIntentFn,
675
364
  })
676
365
  }
677
366
 
678
- return await sendHandlerForDifferentChainDifferentToken({
367
+ return await handleCrossChain({
679
368
  mainSignerAddress,
680
369
  originChainId,
681
370
  originTokenAddress,
@@ -687,16 +376,12 @@ export async function prepareSend(
687
376
  destinationTokenSymbol,
688
377
  recipient: effectiveDestinationAddress,
689
378
  destinationCalldata: effectiveDestinationCalldata,
690
- apiClient,
691
379
  trailsClient,
692
380
  sourceTokenPriceUsd,
693
381
  destinationTokenPriceUsd,
694
382
  sourceTokenDecimals,
695
383
  destinationTokenDecimals,
696
- gasless,
697
384
  paymasterUrl,
698
- originRelayer,
699
- destinationRelayer,
700
385
  walletClient,
701
386
  publicClient,
702
387
  chain,
@@ -714,4325 +399,33 @@ export async function prepareSend(
714
399
  checkoutOnHandlers,
715
400
  selectedFeeToken,
716
401
  walletId,
402
+ abortSignal,
403
+ commitIntentFn,
404
+ executeIntentFn,
717
405
  })
718
406
  }
719
407
 
720
- async function sendHandlerForDifferentChainDifferentToken({
721
- mainSignerAddress,
722
- originChainId,
723
- originTokenAddress,
724
- originTokenBalance,
725
- destinationChainId,
726
- destinationTokenAddress,
727
- swapAmount,
728
- originTokenSymbol,
729
- destinationTokenSymbol,
730
- recipient,
731
- destinationCalldata,
732
- trailsClient,
733
- sourceTokenPriceUsd,
734
- destinationTokenPriceUsd,
735
- sourceTokenDecimals,
736
- destinationTokenDecimals,
737
- gasless,
738
- paymasterUrl,
739
- originRelayer,
740
- destinationRelayer,
741
- walletClient,
742
- publicClient,
743
- chain,
744
- account,
745
- fee,
746
- dryMode,
747
- onTransactionStateChange,
748
- transactionStates,
749
- slippageTolerance,
750
- tradeType,
751
- originNativeTokenPriceUsd,
752
- quoteProvider,
753
- fundMethod,
754
- mode,
755
- checkoutOnHandlers,
756
- selectedFeeToken,
757
- walletId,
758
- }: {
759
- mainSignerAddress: string
760
- originChainId: number
761
- originTokenAddress: string
762
- originTokenBalance: string
763
- destinationChainId: number
764
- destinationTokenAddress: string
765
- swapAmount: string
766
- originTokenSymbol: string
767
- destinationTokenSymbol: string
768
- recipient: string
769
- destinationCalldata?: string
770
- apiClient: SequenceAPIClient
771
- trailsClient: TrailsAPIClient
772
- sourceTokenPriceUsd?: number | null
773
- destinationTokenPriceUsd?: number | null
774
- sourceTokenDecimals: number
775
- destinationTokenDecimals: number
776
- gasless: boolean
777
- paymasterUrl?: string
778
- originRelayer: Relayer.RpcRelayer
779
- destinationRelayer: Relayer.RpcRelayer
780
- walletClient: WalletClient
781
- publicClient: PublicClient
782
- chain: Chain
783
- account: Account
784
- fee: string
785
- dryMode: boolean
786
- onTransactionStateChange: (transactionStates: TransactionState[]) => void
787
- transactionStates: TransactionState[]
788
- slippageTolerance: string
789
- tradeType: TradeType
790
- originNativeTokenPriceUsd?: number | null
791
- quoteProvider?: string | null
792
- fundMethod?: string
793
- mode?: "pay" | "fund" | "earn" | "swap" | "receive"
794
- checkoutOnHandlers?: CheckoutOnHandlers
795
- selectedFeeToken?: any
796
- walletId?: string
797
- }): Promise<PrepareSendReturn> {
798
- const testnet = isTestnetDebugMode()
799
- const useCctp = getUseCctp(
800
- originTokenAddress,
801
- destinationTokenAddress,
802
- originChainId,
803
- destinationChainId,
804
- )
805
-
806
- const cctpFlag = getQueryParam("cctp") === "true"
807
- const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
808
-
809
- // Validate CCTP destination token requirement for explicit CCTP usage
810
- if (useCctp && cctpFlag) {
811
- validateCctpDestinationToken(
812
- destinationTokenAddress,
813
- destinationChainId,
814
- "cctp",
815
- )
816
- logger.console.log("[trails-sdk] using cctp")
817
-
818
- const quote = await getNormalizedQuoteObject({
819
- destinationAddress: recipient,
820
- destinationCalldata,
821
- originAmount: swapAmount,
822
- originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
823
- destinationAmount: swapAmount,
824
- destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
825
- originTokenAddress: originTokenAddress,
826
- destinationTokenAddress: destinationTokenAddress,
827
- originChainId,
828
- destinationChainId,
829
- transactionStates,
830
- originNativeTokenPriceUsd,
831
- slippageTolerance,
832
- quoteProvider: "cctp",
833
- })
834
-
835
- // Call onCheckoutQuote callback if provided
836
- if (checkoutOnHandlers?.triggerCheckoutQuote) {
837
- checkoutOnHandlers.triggerCheckoutQuote(quote)
838
- }
839
-
840
- return {
841
- quote,
842
- send: async ({
843
- onOriginSend,
844
- selectedFeeToken: runtimeSelectedFeeToken,
845
- }: {
846
- onOriginSend?: () => void
847
- selectedFeeToken?: any
848
- }): Promise<SendReturn> => {
849
- // Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
850
- const effectiveSelectedFeeToken =
851
- runtimeSelectedFeeToken ?? selectedFeeToken
852
- logger.console.log(
853
- "[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called (LEGACY PATH):",
854
- {
855
- runtimeSelectedFeeToken,
856
- prepareTimeSelectedFeeToken: selectedFeeToken,
857
- effectiveSelectedFeeToken,
858
- },
859
- )
860
- const originChain = testnet ? getTestnetChainInfo(chain)! : chain
861
- const destinationChain = testnet
862
- ? getTestnetChainInfo(destinationChainId)!
863
- : getChainInfo(destinationChainId)
864
-
865
- if (!originChain || !destinationChain) {
866
- logger.console.error("[trails-sdk] Invalid chain", {
867
- originChain,
868
- destinationChain,
869
- originChainId,
870
- destinationChainId,
871
- chain,
872
- testnet,
873
- })
874
- throw new Error("Invalid chain")
875
- }
876
-
877
- logger.console.log("[trails-sdk] originChain", originChain)
878
- logger.console.log("[trails-sdk] destinationChain", destinationChain)
879
-
880
- const originPublicClient = createPublicClient({
881
- chain: originChain,
882
- transport: http(),
883
- })
884
-
885
- let txHash: `0x${string}`
886
- let waitForAttestation: () => Promise<Attestation>
887
-
888
- if (hasCustomCalldata) {
889
- const result = await cctpTransferWithCustomCall({
890
- walletClient,
891
- originChain,
892
- destinationChain,
893
- amount: BigInt(swapAmount),
894
- })
895
-
896
- txHash = result.txHash
897
- waitForAttestation = result.waitForAttestation
898
- } else {
899
- const result = await cctpTransfer({
900
- walletClient,
901
- originChain,
902
- destinationChain,
903
- amount: BigInt(swapAmount),
904
- })
905
-
906
- txHash = result.txHash
907
- waitForAttestation = result.waitForAttestation
908
- }
909
-
910
- if (onOriginSend) {
911
- onOriginSend()
912
- }
913
-
914
- logger.console.log("[trails-sdk] waiting for tx", txHash)
915
-
916
- const receipt = await originPublicClient.waitForTransactionReceipt({
917
- hash: txHash,
918
- })
919
-
920
- logger.console.log("[trails-sdk] tx receipt", receipt)
921
-
922
- transactionStates[0] = getTransactionStateFromReceipt(
923
- receipt,
924
- originChain.id,
925
- transactionStates[0]?.label,
926
- )
927
- transactionStates[1] = getTransactionStateFromReceipt(
928
- receipt,
929
- originChain.id,
930
- transactionStates[1]?.label,
931
- )
932
-
933
- onTransactionStateChange(transactionStates)
934
-
935
- const attestation = await waitForAttestation()
936
-
937
- if (!attestation) {
938
- throw new Error("Failed to retrieve attestation")
939
- }
940
-
941
- const tokenMessenger = getMessageTransmitter(destinationChain.id)!
942
-
943
- logger.console.log("[trails-sdk] tokenMessenger", tokenMessenger)
944
- const calls: {
945
- to: `0x${string}`
946
- data: `0x${string}`
947
- value: bigint
948
- }[] = []
949
-
950
- if (hasCustomCalldata) {
951
- calls.push(
952
- await getCCTPRelayerCallData({
953
- attestation,
954
- targetContract: recipient,
955
- calldata: destinationCalldata as `0x${string}`,
956
- gasLimit: 300000n,
957
- destinationChain,
958
- }),
959
- )
960
- } else {
961
- calls.push(
962
- await getMintUSDCData({
963
- tokenMessenger,
964
- attestation,
965
- }),
966
- )
967
- }
968
-
969
- logger.console.log("[trails-sdk] calls", calls)
970
- const delegatorPrivateKey = generatePrivateKey()
971
- const delegatorAccount = privateKeyToAccount(delegatorPrivateKey)
972
- const delegatorClient = createWalletClient({
973
- account: delegatorAccount,
974
- chain: destinationChain,
975
- transport: http(),
976
- })
977
- const destinationPublicClient = createPublicClient({
978
- chain: destinationChain,
979
- transport: http(),
980
- })
981
- logger.console.log("[trails-sdk] delegatorClient", delegatorClient)
982
-
983
- const sequenceWalletAddress = await simpleCreateSequenceWallet(
984
- delegatorAccount as any,
985
- )
986
- logger.console.log(
987
- "[trails-sdk] sequenceWalletAddress",
988
- sequenceWalletAddress,
989
- )
990
- const sequenceTxHash = await sequenceSendTransaction(
991
- sequenceWalletAddress,
992
- delegatorClient,
993
- destinationPublicClient,
994
- calls,
995
- destinationChain,
996
- )
997
-
998
- const destinationReceipt =
999
- await destinationPublicClient.waitForTransactionReceipt({
1000
- hash: sequenceTxHash as `0x${string}`,
1001
- })
1002
-
1003
- logger.console.log(
1004
- "[trails-sdk] destinationReceipt",
1005
- destinationReceipt,
1006
- )
1007
-
1008
- if (transactionStates[2]) {
1009
- transactionStates[2] = getTransactionStateFromReceipt(
1010
- destinationReceipt,
1011
- destinationChain.id,
1012
- transactionStates[2]?.label,
1013
- )
1014
- }
1015
-
1016
- onTransactionStateChange(transactionStates)
1017
-
1018
- return {
1019
- originUserTxReceipt: receipt,
1020
- originMetaTxnReceipt: null,
1021
- destinationMetaTxnReceipt: null,
1022
- totalCompletionSeconds: 0,
1023
- }
1024
- },
1025
- }
1026
- }
1027
-
1028
- const destinationSalt = Date.now().toString()
1029
-
1030
- const intentArgs = getIntentArgs(
1031
- mainSignerAddress,
1032
- originChainId,
1033
- originTokenAddress,
1034
- tradeType === TradeType.EXACT_OUTPUT ? originTokenBalance : swapAmount, // originTokenAmount
1035
- destinationChainId,
1036
- destinationTokenAddress,
1037
- tradeType === TradeType.EXACT_OUTPUT ? swapAmount : "0", // destinationTokenAmount
1038
- destinationTokenSymbol,
1039
- recipient,
1040
- destinationCalldata,
1041
- destinationSalt,
1042
- slippageTolerance,
1043
- tradeType,
1044
- quoteProvider,
1045
- )
1046
-
1047
- logger.console.log("[trails-sdk] Creating intent with args:", intentArgs)
1048
-
1049
- const intent = await getIntentCallsPayloadsFromIntents(
1050
- trailsClient,
1051
- {
1052
- params: intentArgs,
1053
- },
1054
- {
1055
- originTokenSymbol,
1056
- destinationTokenSymbol,
1057
- feeTokenSymbol: originTokenSymbol,
1058
- },
1059
- )
1060
- logger.console.log("[trails-sdk] Got intent:", intent)
1061
-
1062
- if (
1063
- !intent.payloads.preconditions?.length ||
1064
- !intent.payloads.calls?.length
1065
- ) {
1066
- throw new Error("Invalid intent")
1067
- }
1068
-
1069
- const originIntentAddress = intent.payloads.originIntentAddress
1070
- logger.console.log(
1071
- "[trails-sdk] origin intent address:",
1072
- originIntentAddress.toString(),
1073
- )
1074
-
1075
- const firstPrecondition = findFirstPreconditionForChainId(
1076
- intent.payloads.preconditions,
1077
- originChainId,
1078
- )
1079
-
1080
- if (!firstPrecondition) {
1081
- throw new Error("No precondition found for origin chain")
1082
- }
1083
-
1084
- const firstPreconditionMin = firstPrecondition?.data?.min?.toString()
1085
- const depositAmount = firstPreconditionMin
1086
-
1087
- const quoteToAmount = intent.payloads.quote.toAmount
1088
- const quoteToAmountMin = intent.payloads.quote.toAmountMin
1089
-
1090
- const originSendAmountFormatted = formatRawAmount(
1091
- depositAmount,
1092
- sourceTokenDecimals,
1093
- )
1094
-
1095
- const depositAmountUsd = calcAmountUsdPrice({
1096
- amount: originSendAmountFormatted,
1097
- usdPrice: sourceTokenPriceUsd,
1098
- })
1099
-
1100
- logger.console.log("[trails-sdk] depositAmountUsd", depositAmountUsd, {
1101
- amount: originSendAmountFormatted,
1102
- usdPrice: sourceTokenPriceUsd,
1103
- originTokenSymbol,
1104
- originSendAmountFormatted,
1105
- })
1106
-
1107
- const effectiveDestinationTokenAmount = quoteToAmount
1108
- const effectiveDestinationTokenAmountFormatted = formatRawAmount(
1109
- effectiveDestinationTokenAmount,
1110
- destinationTokenDecimals,
1111
- )
1112
-
1113
- const effectiveDestinationTokenAmountUsd = calcAmountUsdPrice({
1114
- amount: effectiveDestinationTokenAmountFormatted,
1115
- usdPrice: destinationTokenPriceUsd,
1116
- })
1117
-
1118
- logger.console.log(
1119
- "[trails-sdk] effectiveDestinationTokenAmountUsd",
1120
- effectiveDestinationTokenAmountUsd,
1121
- {
1122
- amount: effectiveDestinationTokenAmountFormatted,
1123
- usdPrice: destinationTokenPriceUsd,
1124
- destinationTokenSymbol,
1125
- effectiveDestinationTokenAmount,
1126
- destinationTokenAddress,
1127
- destinationChainId,
1128
- },
1129
- )
1130
-
1131
- let noSufficientBalance = false
1132
-
1133
- if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
1134
- const { hasEnoughBalance } = await checkAccountBalance({
1135
- account,
1136
- tokenAddress: originTokenAddress,
1137
- depositAmount,
1138
- publicClient,
1139
- })
1140
-
1141
- if (!hasEnoughBalance) {
1142
- noSufficientBalance = true
1143
- }
1144
- }
1145
-
1146
- // Estimate gas limit for the quote (same logic as in attemptNonGaslessUserDeposit)
1147
- const originCallParamsForEstimate = buildCrossChainDepositParams({
1148
- originTokenAddress,
1149
- originIntentAddress,
1150
- depositAmount: firstPreconditionMin,
1151
- fee,
1152
- originChainId,
1153
- chain,
1154
- })
1155
-
1156
- logger.console.log(
1157
- "[trails-sdk][gas-estimation] About to estimate gas limit for cross-chain quote with params:",
1158
- {
1159
- account: account.address,
1160
- to: originCallParamsForEstimate.to,
1161
- data: originCallParamsForEstimate.data,
1162
- value: originCallParamsForEstimate.value,
1163
- },
1164
- )
1165
-
1166
- const estimatedGasLimitForQuote = await estimateGasLimit(
1167
- publicClient,
1168
- {
1169
- account: account.address,
1170
- to: originCallParamsForEstimate.to,
1171
- data: originCallParamsForEstimate.data,
1172
- value: BigInt(originCallParamsForEstimate.value),
1173
- },
1174
- "quote",
1175
- )
1176
-
1177
- logger.console.log(
1178
- "[trails-sdk][gas-estimation] Estimated gas limit for cross-chain quote:",
1179
- estimatedGasLimitForQuote,
1180
- )
1181
-
1182
- const quote = await getNormalizedQuoteObject({
1183
- originDepositAddress: originIntentAddress,
1184
- destinationDepositAddress: intent.payloads.destinationIntentAddress,
1185
- destinationAddress: recipient,
1186
- destinationCalldata,
1187
- originAmount: depositAmount,
1188
- destinationAmount: quoteToAmount,
1189
- originAmountMin: depositAmount,
1190
- destinationAmountMin: quoteToAmountMin,
1191
- originTokenAddress: originTokenAddress,
1192
- destinationTokenAddress: destinationTokenAddress,
1193
- originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
1194
- destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
1195
- fees: getFeesFromIntent(intent, {
1196
- tradeType,
1197
- fromAmountUsd: depositAmountUsd,
1198
- toAmountUsd: effectiveDestinationTokenAmountUsd,
1199
- }),
1200
- originChainId,
1201
- destinationChainId,
1202
- slippageTolerance: slippageTolerance,
1203
- priceImpact: getPriceImpactFromIntent(intent),
1204
- priceImpactUsd: getPriceImpactUsdFromIntent(intent),
1205
- transactionStates,
1206
- originNativeTokenPriceUsd,
1207
- quoteProvider: intent.payloads?.quote?.quoteProvider,
1208
- noSufficientBalance,
1209
- estimatedGasLimit: estimatedGasLimitForQuote,
1210
- })
1211
-
1212
- // Call onCheckoutQuote callback if provided
1213
- if (checkoutOnHandlers?.triggerCheckoutQuote) {
1214
- checkoutOnHandlers.triggerCheckoutQuote(quote)
1215
- }
1216
-
1217
- // Get intent entrypoint fee options if supported and gasless is enabled
1218
- // Note: We fetch fee options whenever gasless is true, regardless of fundMethod,
1219
- // because gasless scenarios (including tests) need fee collector information
1220
- // Skip gasless fee options for sequence-waas wallet
1221
- let intentEntrypointFeeOptions: any = null
1222
- if (gasless && walletId !== "sequence-waas") {
1223
- try {
1224
- logger.console.log(
1225
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless enabled, checking intent entrypoint support for chain:",
1226
- originChainId,
1227
- )
1228
-
1229
- intentEntrypointFeeOptions = await getIntentEntrypointFeeOptions({
1230
- trailsClient,
1231
- userAddress: mainSignerAddress as `0x${string}`,
1232
- tokenAddress: originTokenAddress as `0x${string}`,
1233
- amount: depositAmount,
1234
- intentAddress: originIntentAddress as `0x${string}`,
1235
- chainId: originChainId,
1236
- })
1237
- logger.console.log(
1238
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Intent entrypoint fee options:",
1239
- intentEntrypointFeeOptions,
1240
- )
1241
- } catch (error) {
1242
- logger.console.error(
1243
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Error getting intent entrypoint fee options:",
1244
- error,
1245
- )
1246
- }
1247
- } else if (walletId === "sequence-waas") {
1248
- logger.console.log(
1249
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Skipping gasless fee options for sequence-waas wallet",
1250
- )
1251
- } else {
1252
- logger.console.log(
1253
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] Gasless disabled, skipping intent entrypoint fee options fetch",
1254
- )
1255
- }
1256
-
1257
- return {
1258
- quote,
1259
- feeOptions: intentEntrypointFeeOptions,
1260
- send: async ({
1261
- onOriginSend,
1262
- selectedFeeToken: runtimeSelectedFeeToken,
1263
- }: {
1264
- onOriginSend?: () => void
1265
- selectedFeeToken?: any
1266
- }): Promise<SendReturn> => {
1267
- try {
1268
- // Use runtime selectedFeeToken if provided, otherwise fall back to the one from prepareSend
1269
- const effectiveSelectedFeeToken =
1270
- runtimeSelectedFeeToken ?? selectedFeeToken
1271
- logger.console.log(
1272
- "[trails-sdk] [FEE-SELECT] [GASLESS-FLOW] send() called with:",
1273
- {
1274
- runtimeSelectedFeeToken,
1275
- prepareTimeSelectedFeeToken: selectedFeeToken,
1276
- effectiveSelectedFeeToken,
1277
- },
1278
- )
1279
- await commitIntentConfig(
1280
- trailsClient,
1281
- mainSignerAddress,
1282
- intent.payloads.calls,
1283
- intent.payloads.preconditions,
1284
- {
1285
- originTokenSymbol,
1286
- destinationTokenSymbol,
1287
- },
1288
- intentArgs as IntentRequestParams,
1289
- )
1290
-
1291
- if (!(fundMethod === "qr-code" || fundMethod === "exchange")) {
1292
- const { hasEnoughBalance, balanceError } = await checkAccountBalance({
1293
- account,
1294
- tokenAddress: originTokenAddress,
1295
- depositAmount,
1296
- publicClient,
1297
- })
1298
-
1299
- if (!hasEnoughBalance) {
1300
- throw balanceError
1301
- }
1302
- }
1303
-
1304
- logger.console.log("[trails-sdk] sending origin transaction")
1305
- const usingLIfi = false
1306
- let needsNativeFee = false
1307
-
1308
- if (usingLIfi) {
1309
- needsNativeFee = getNeedsLifiNativeFee({
1310
- originTokenAddress,
1311
- destinationTokenAmount: swapAmount,
1312
- destinationTokenDecimals,
1313
- sourceTokenDecimals,
1314
- sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
1315
- destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
1316
- depositAmount,
1317
- })
1318
- }
1319
-
1320
- logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
1321
- logger.console.log(
1322
- "[trails-sdk] sourceTokenPriceUsd",
1323
- sourceTokenPriceUsd,
1324
- )
1325
- logger.console.log(
1326
- "[trails-sdk] destinationTokenPriceUsd",
1327
- destinationTokenPriceUsd,
1328
- )
1329
- logger.console.log(
1330
- "[trails-sdk] sourceTokenDecimals",
1331
- sourceTokenDecimals,
1332
- )
1333
- logger.console.log(
1334
- "[trails-sdk] destinationTokenDecimals",
1335
- destinationTokenDecimals,
1336
- )
1337
-
1338
- let originUserTxReceipt: TransactionReceipt | null = null
1339
- let originMetaTxnReceipt: MetaTxnReceipt | null = null
1340
- let destinationMetaTxnReceipt: MetaTxnReceipt | null = null
1341
-
1342
- const testnet = isTestnetDebugMode()
1343
- const effectiveOriginChain = testnet
1344
- ? getTestnetChainInfo(chain)!
1345
- : chain
1346
- const effectiveOriginTokenAddress = testnet
1347
- ? getTestnetOriginTokenAddress(effectiveOriginChain.id)
1348
- : originTokenAddress
1349
-
1350
- logger.console.log("[trails-sdk] testnet", testnet)
1351
-
1352
- const destinationPublicClient = createPublicClient({
1353
- chain: getChainInfo(destinationChainId)!,
1354
- transport: http(),
1355
- })
1356
-
1357
- const depositPromise = async () => {
1358
- logger.console.log(
1359
- "[trails-sdk] depositPromise called - starting deposit transaction",
1360
- )
1361
- logger.console.log("[trails-sdk] fundMethod value:", fundMethod)
1362
-
1363
- // Skip wallet deposit if fund method is qr-code
1364
- if (fundMethod === "qr-code" || fundMethod === "exchange") {
1365
- logger.console.log(
1366
- "[trails-sdk] Skipping wallet deposit for QR code mode - fundMethod is:",
1367
- fundMethod,
1368
- )
1369
- return
1370
- }
1371
-
1372
- logger.console.log(
1373
- "[trails-sdk] Calling attemptUserDepositTx with params:",
1374
- {
1375
- originTokenAddress: effectiveOriginTokenAddress,
1376
- gasless,
1377
- paymasterUrl,
1378
- chain: effectiveOriginChain.id,
1379
- account: account.address,
1380
- firstPreconditionMin,
1381
- originIntentAddress,
1382
- fee,
1383
- dryMode,
1384
- feeOptions: intentEntrypointFeeOptions,
1385
- selectedFeeToken: effectiveSelectedFeeToken,
1386
- selectedFeeTokenType: typeof effectiveSelectedFeeToken,
1387
- selectedFeeTokenValue: JSON.stringify(effectiveSelectedFeeToken),
1388
- },
1389
- )
1390
-
1391
- originUserTxReceipt = await attemptUserDepositTx({
1392
- originTokenAddress: effectiveOriginTokenAddress,
1393
- gasless,
1394
- paymasterUrl,
1395
- chain: effectiveOriginChain,
1396
- account,
1397
- originRelayer,
1398
- firstPreconditionMin,
1399
- originIntentAddress,
1400
- onOriginSend,
1401
- publicClient,
1402
- walletClient,
1403
- destinationTokenDecimals,
1404
- sourceTokenDecimals,
1405
- fee,
1406
- dryMode,
1407
- sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
1408
- destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
1409
- swapAmount,
1410
- onTransactionStateChange,
1411
- transactionStates,
1412
- fundMethod,
1413
- originTokenSymbol,
1414
- destinationTokenSymbol,
1415
- depositAmountUsd,
1416
- feeOptions: intentEntrypointFeeOptions,
1417
- trailsClient,
1418
- selectedFeeToken: effectiveSelectedFeeToken,
1419
- walletId,
1420
- })
1421
-
1422
- if (!originUserTxReceipt) {
1423
- throw new Error("Failed to send origin transaction")
1424
- }
1425
-
1426
- transactionStates[0] = getTransactionStateFromReceipt(
1427
- originUserTxReceipt,
1428
- originChainId,
1429
- transactionStates[0]?.label,
1430
- )
1431
-
1432
- try {
1433
- transactionStates[0].decodedTrailsTokenSweeperEvents =
1434
- decodeTrailsTokenSweeperEvents(originUserTxReceipt)
1435
- transactionStates[0].decodedGuestModuleEvents =
1436
- decodeGuestModuleEvents(originUserTxReceipt)
1437
- transactionStates[0].refunded =
1438
- transactionStates[0].decodedTrailsTokenSweeperEvents.findIndex(
1439
- (event) =>
1440
- event.type === "Refund" || event.type === "RefundAndSweep",
1441
- ) !== -1
1442
- logger.console.log(
1443
- "[trails-sdk] [GASLESS-FLOW] Gasless deposit events decoded",
1444
- {
1445
- chainId: originChainId,
1446
- callFailed: (
1447
- transactionStates[0].decodedGuestModuleEvents || []
1448
- ).filter((e: any) => e?.type === "CallFailed").length,
1449
- sweeperEvents: (
1450
- transactionStates[0].decodedTrailsTokenSweeperEvents || []
1451
- ).length,
1452
- refunded: transactionStates[0].refunded,
1453
- },
1454
- )
1455
-
1456
- // Check for transaction failure or refund
1457
- const hasCallFailed = (
1458
- transactionStates[0].decodedGuestModuleEvents || []
1459
- ).some((e: any) => e?.type === "CallFailed")
1460
-
1461
- if (transactionStates[0].refunded || hasCallFailed) {
1462
- const errorMessage = transactionStates[0].refunded
1463
- ? "Transaction was refunded"
1464
- : "Transaction call failed"
1465
-
1466
- logger.console.error(
1467
- "[trails-sdk] [GASLESS-FLOW] Deposit transaction failed",
1468
- {
1469
- refunded: transactionStates[0].refunded,
1470
- callFailed: hasCallFailed,
1471
- },
1472
- )
1473
-
1474
- // Call onCheckoutError callback if provided
1475
- if (checkoutOnHandlers?.triggerCheckoutError) {
1476
- checkoutOnHandlers.triggerCheckoutError(errorMessage)
1477
- }
1478
- }
1479
- } catch (error) {
1480
- logger.console.error(
1481
- "[trails-sdk] Error decoding gasless deposit events",
1482
- error,
1483
- )
1484
- }
1485
-
1486
- onTransactionStateChange(transactionStates)
1487
-
1488
- setTimeout(() => {
1489
- const destinationChain = getChainInfo(destinationChainId)
1490
- updatePersistentToast(
1491
- "In Progress",
1492
- `Your transaction to ${destinationChain?.name || "chain"} is in progress`,
1493
- "info",
1494
- )
1495
- }, 1000)
1496
- }
1497
-
1498
- const checkForDepositTx = async () => {
1499
- while (true) {
1500
- try {
1501
- const response = await getAccountTransactionHistory({
1502
- chainId: originChainId,
1503
- accountAddress: originIntentAddress,
1504
- })
1505
- logger.console.log(
1506
- "[trails-sdk] getAccountTransactionHistory response",
1507
- response,
1508
- )
1509
- if (response.transactions.length > 0) {
1510
- const tx = response.transactions[0]
1511
- if (!tx?.txnHash) {
1512
- await new Promise((resolve) => setTimeout(resolve, 1000))
1513
- continue
1514
- }
1515
- // const isReceive = tx.transfers.some(
1516
- // (transfer) => transfer.transferType === "RECEIVE",
1517
- // )
1518
- // if (!isReceive) {
1519
- // await new Promise((resolve) => setTimeout(resolve, 1000))
1520
- // continue
1521
- // }
1522
- const originDepositTxReceipt =
1523
- await publicClient.getTransactionReceipt({
1524
- hash: tx.txnHash as `0x${string}`,
1525
- })
1526
-
1527
- originUserTxReceipt = originDepositTxReceipt
1528
-
1529
- transactionStates[0] = getTransactionStateFromReceipt(
1530
- originDepositTxReceipt,
1531
- originChainId,
1532
- transactionStates[0]?.label,
1533
- )
1534
- onTransactionStateChange(transactionStates)
1535
-
1536
- if (onOriginSend) {
1537
- onOriginSend()
1538
- }
1539
- break
1540
- }
1541
- } catch (error) {
1542
- logger.console.error("Error checking for deposit tx", error)
1543
- }
1544
- await new Promise((resolve) => setTimeout(resolve, 1000))
1545
- }
1546
- }
1547
-
1548
- const _checkForDestinationDepositTx: () => Promise<TransactionReceipt | null> =
1549
- async () => {
1550
- while (true) {
1551
- try {
1552
- const response = await getAccountTransactionHistory({
1553
- chainId: destinationChainId,
1554
- accountAddress: intent.payloads
1555
- .destinationIntentAddress as `0x${string}`,
1556
- })
1557
- logger.console.log(
1558
- "[trails-sdk] getAccountTransactionHistory response",
1559
- response,
1560
- )
1561
- if (response.transactions.length > 0) {
1562
- const tx = response.transactions[0]
1563
- if (!tx?.txnHash) {
1564
- await new Promise((resolve) => setTimeout(resolve, 1000))
1565
- continue
1566
- }
1567
- // const isReceive = tx.transfers.some(
1568
- // (transfer) => transfer.transferType === "RECEIVE",
1569
- // )
1570
- // if (!isReceive) {
1571
- // await new Promise((resolve) => setTimeout(resolve, 1000))
1572
- // continue
1573
- // }
1574
- const destinationDepositTxReceipt =
1575
- await destinationPublicClient.getTransactionReceipt({
1576
- hash: tx.txnHash as `0x${string}`,
1577
- })
1578
-
1579
- transactionStates[2] = getTransactionStateFromReceipt(
1580
- destinationDepositTxReceipt,
1581
- destinationChainId,
1582
- transactionStates[2]?.label,
1583
- )
1584
- onTransactionStateChange(transactionStates)
1585
-
1586
- return destinationDepositTxReceipt
1587
- }
1588
- } catch (error) {
1589
- logger.console.error("Error checking for deposit tx", error)
1590
- }
1591
- await new Promise((resolve) => setTimeout(resolve, 1000))
1592
- }
1593
- }
1594
-
1595
- // Variables to store the waitForReceipt functions
1596
- let originMetaTxnReceiptPromise:
1597
- | (() => Promise<MetaTxnReceipt | null>)
1598
- | null = null
1599
- let destinationMetaTxnReceiptPromise:
1600
- | ((abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>)
1601
- | ((abortSignal?: AbortSignal) => Promise<TransactionReceipt | null>)
1602
- | null = null
1603
-
1604
- // First phase: Send meta transactions and queue CCTP
1605
- const originSendMetaTxnPromise = async () => {
1606
- logger.console.log("[trails-sdk] Starting originSendMetaTxnPromise", {
1607
- hasMetaTxn: !!intent.payloads.metaTxns[0],
1608
- hasPrecondition: !!intent.payloads.preconditions[0],
1609
- metaTxnId: intent.payloads.metaTxns[0]?.id,
1610
- chainId: intent.payloads.metaTxns[0]?.chainId,
1611
- })
1612
-
1613
- if (intent.payloads.metaTxns[0] && intent.payloads.preconditions[0]) {
1614
- // Extract fee quote from intent response using metatxnid as key
1615
- const metaTxnId = intent.payloads.metaTxns[0].id
1616
- const feeQuote = intent.payloads.feeQuotes?.[metaTxnId]
1617
- logger.console.log(
1618
- "[trails-sdk] Extracted fee quote for origin meta txn",
1619
- {
1620
- metaTxnId,
1621
- feeQuote,
1622
- hasFeeQuote: !!feeQuote,
1623
- },
1624
- )
1625
-
1626
- logger.console.log(
1627
- "[trails-sdk] Calling sendMetaTxAndWaitForReceipt for origin",
1628
- {
1629
- metaTxnId,
1630
- chainId: intent.payloads.metaTxns[0].chainId,
1631
- walletAddress: intent.payloads.metaTxns[0].walletAddress,
1632
- contract: intent.payloads.metaTxns[0].contract,
1633
- },
1634
- )
1635
-
1636
- const { waitForReceipt } = await sendMetaTxAndWaitForReceipt({
1637
- metaTx: intent.payloads.metaTxns[0] as MetaTxn,
1638
- relayer: originRelayer,
1639
- precondition: intent.payloads
1640
- .preconditions[0] as IntentPrecondition,
1641
- feeQuote: feeQuote,
1642
- })
1643
-
1644
- logger.console.log(
1645
- "[trails-sdk] Origin meta transaction sent successfully",
1646
- {
1647
- metaTxnId,
1648
- },
1649
- )
1650
-
1651
- // Store the waitForReceipt function for later use
1652
- originMetaTxnReceiptPromise = waitForReceipt
1653
- } else {
1654
- logger.console.warn(
1655
- "[trails-sdk] Skipping origin sendMetaTxn - missing metaTxn or precondition",
1656
- {
1657
- hasMetaTxn: !!intent.payloads.metaTxns[0],
1658
- hasPrecondition: !!intent.payloads.preconditions[0],
1659
- },
1660
- )
1661
- }
1662
- }
1663
-
1664
- const destinationSendMetaTxnPromise = async () => {
1665
- logger.console.log(
1666
- "[trails-sdk] Starting destinationSendMetaTxnPromise",
1667
- {
1668
- quoteProvider: intent.payloads.quote.quoteProvider,
1669
- hasQuoteProviderRequestId:
1670
- !!intent.payloads.quote.quoteProviderRequestId,
1671
- hasPrecondition1: !!intent.payloads.preconditions[1],
1672
- hasMetaTxn1: !!intent.payloads.metaTxns[1],
1673
- },
1674
- )
1675
-
1676
- if (
1677
- intent.payloads.quote.quoteProvider === "relay" &&
1678
- intent.payloads.quote.quoteProviderRequestId &&
1679
- !intent.payloads.preconditions[1] &&
1680
- !intent.payloads.metaTxns[1]
1681
- ) {
1682
- logger.console.log(
1683
- "[trails-sdk] Setting up relay destination promise",
1684
- {
1685
- quoteProviderRequestId:
1686
- intent.payloads.quote.quoteProviderRequestId,
1687
- },
1688
- )
1689
- // For relay, we'll wait for the receipt in the wait phase
1690
- // Just store the requestId for later use
1691
- destinationMetaTxnReceiptPromise = async (
1692
- abortSignal?: AbortSignal,
1693
- ) => {
1694
- logger.console.log(
1695
- "[trails-sdk] waitForRelayDestinationTx starting",
1696
- {
1697
- quoteProviderRequestId:
1698
- intent.payloads.quote.quoteProviderRequestId,
1699
- aborted: abortSignal?.aborted,
1700
- },
1701
- )
1702
- try {
1703
- // Check if we should abort before starting
1704
- if (abortSignal?.aborted) {
1705
- logger.console.log(
1706
- "[trails-sdk] Aborting relay destination tx due to abort signal",
1707
- )
1708
- return null
1709
- }
1710
-
1711
- const txHash = await waitForRelayDestinationTx(
1712
- intent.payloads.quote.quoteProviderRequestId,
1713
- )
1714
- logger.console.log(
1715
- "[trails-sdk] waitForRelayDestinationTx completed",
1716
- {
1717
- txHash,
1718
- quoteProviderRequestId:
1719
- intent.payloads.quote.quoteProviderRequestId,
1720
- },
1721
- )
1722
- if (txHash) {
1723
- logger.console.log(
1724
- "[trails-sdk] Fetching transaction receipt for relay destination",
1725
- {
1726
- txHash,
1727
- chainId: destinationChainId,
1728
- },
1729
- )
1730
-
1731
- const destinationTxnReceipt =
1732
- await destinationPublicClient.getTransactionReceipt({
1733
- hash: txHash as `0x${string}`,
1734
- })
1735
- logger.console.log(
1736
- "[trails-sdk] relay destinationTxnReceipt received",
1737
- {
1738
- txHash,
1739
- blockNumber: destinationTxnReceipt?.blockNumber,
1740
- status: destinationTxnReceipt?.status,
1741
- gasUsed: destinationTxnReceipt?.gasUsed,
1742
- },
1743
- )
1744
- if (transactionStates[2]) {
1745
- transactionStates[2] = getTransactionStateFromReceipt(
1746
- destinationTxnReceipt,
1747
- destinationChainId,
1748
- transactionStates[2]?.label,
1749
- )
1750
- }
1751
- onTransactionStateChange(transactionStates)
1752
- return destinationTxnReceipt
1753
- } else {
1754
- logger.console.warn(
1755
- "[trails-sdk] No txHash returned from waitForRelayDestinationTx",
1756
- {
1757
- quoteProviderRequestId:
1758
- intent.payloads.quote.quoteProviderRequestId,
1759
- },
1760
- )
1761
- }
1762
- } catch (error: unknown) {
1763
- logger.console.error(
1764
- "[trails-sdk] Error waiting for relay destination tx",
1765
- {
1766
- error:
1767
- error instanceof Error ? error.message : String(error),
1768
- quoteProviderRequestId:
1769
- intent.payloads.quote.quoteProviderRequestId,
1770
- },
1771
- )
1772
- if (transactionStates?.[2]) {
1773
- transactionStates[2].state = "failed"
1774
- onTransactionStateChange(transactionStates)
1775
- }
1776
- throw error
1777
- }
1778
- return null
1779
- }
1780
- } else {
1781
- logger.console.log(
1782
- "[trails-sdk] Setting up destination meta transaction promise (non-relay)",
1783
- {
1784
- quoteProvider: intent.payloads.quote.quoteProvider,
1785
- hasMetaTxn1: !!intent.payloads.metaTxns[1],
1786
- hasPrecondition1: !!intent.payloads.preconditions[1],
1787
- },
1788
- )
1789
-
1790
- if (
1791
- intent.payloads.metaTxns[1] &&
1792
- intent.payloads.preconditions[1]
1793
- ) {
1794
- // Extract fee quote from intent response using metatxnid as key
1795
- const metaTxnId = intent.payloads.metaTxns[1].id
1796
- const feeQuote = intent.payloads.feeQuotes?.[metaTxnId]
1797
- logger.console.log(
1798
- "[trails-sdk] Extracted fee quote for destination meta txn",
1799
- {
1800
- metaTxnId,
1801
- feeQuote,
1802
- hasFeeQuote: !!feeQuote,
1803
- },
1804
- )
1805
-
1806
- logger.console.log(
1807
- "[trails-sdk] Calling sendMetaTxAndWaitForReceipt for destination",
1808
- {
1809
- metaTxnId,
1810
- chainId: intent.payloads.metaTxns[1].chainId,
1811
- walletAddress: intent.payloads.metaTxns[1].walletAddress,
1812
- contract: intent.payloads.metaTxns[1].contract,
1813
- },
1814
- )
1815
-
1816
- const { waitForReceipt } = await sendMetaTxAndWaitForReceipt({
1817
- metaTx: intent.payloads.metaTxns[1] as MetaTxn,
1818
- relayer: destinationRelayer,
1819
- precondition: intent.payloads
1820
- .preconditions[1] as IntentPrecondition,
1821
- feeQuote: feeQuote,
1822
- })
1823
-
1824
- logger.console.log(
1825
- "[trails-sdk] Destination meta transaction sent successfully",
1826
- {
1827
- metaTxnId,
1828
- },
1829
- )
1830
-
1831
- // Store the waitForReceipt function for later use
1832
- destinationMetaTxnReceiptPromise = waitForReceipt
1833
- } else {
1834
- logger.console.warn(
1835
- "[trails-sdk] Skipping destination sendMetaTxn - missing metaTxn or precondition",
1836
- {
1837
- hasMetaTxn: !!intent.payloads.metaTxns[1],
1838
- hasPrecondition: !!intent.payloads.preconditions[1],
1839
- },
1840
- )
1841
- }
1842
- // } else if (intent.payloads.destinationIntentAddress) {
1843
- // destinationMetaTxnReceiptPromise = checkForDestinationDepositTx
1844
- // }
1845
- }
1846
- }
1847
-
1848
- let queueCctpPromise: (() => Promise<void>) | null = null
1849
-
1850
- const isCctp = intent.payloads.quote.quoteProvider === "cctp"
1851
- if (isCctp) {
1852
- queueCctpPromise = async () => {
1853
- while (true) {
1854
- const originMetaTxnHash = originMetaTxnReceipt?.txnHash
1855
- if (originMetaTxnHash) {
1856
- await queueCCTPTransfer({
1857
- trailsClient,
1858
- sourceTxHash: originMetaTxnHash,
1859
- sourceChainId: originChainId,
1860
- destinationChainId: destinationChainId,
1861
- })
1862
- break
1863
- }
1864
- await new Promise((resolve) => setTimeout(resolve, 1000))
1865
- }
1866
- }
1867
- } else {
1868
- queueCctpPromise = () => Promise.resolve()
1869
- }
1870
-
1871
- checkForDepositTx().catch((error) => {
1872
- logger.console.error("Error checking for deposit tx", error)
1873
- })
1874
-
1875
- // Phase 1: Send meta transactions and queue CCTP
1876
- logger.console.log(
1877
- "[trails-sdk] Starting Phase 1: Sending meta transactions and queuing CCTP",
1878
- )
1879
-
1880
- await Promise.all([
1881
- originSendMetaTxnPromise(),
1882
- destinationSendMetaTxnPromise(),
1883
- ])
1884
-
1885
- logger.console.log("[trails-sdk] Phase 1 completed successfully")
1886
-
1887
- // Phase 2: Wait for receipts and execute deposit
1888
- logger.console.log(
1889
- "[trails-sdk] Starting Phase 2: Waiting for receipts and executing deposit",
1890
- )
1891
-
1892
- const waitForOriginMetaTxnReceiptPromise = async () => {
1893
- logger.console.log(
1894
- "[trails-sdk] Waiting for origin meta transaction receipt",
1895
- )
1896
- if (originMetaTxnReceiptPromise) {
1897
- try {
1898
- originMetaTxnReceipt = await originMetaTxnReceiptPromise()
1899
-
1900
- if (originMetaTxnReceipt && transactionStates[1]) {
1901
- transactionStates[1] = getTransactionStateFromReceipt(
1902
- originMetaTxnReceipt,
1903
- originChainId,
1904
- transactionStates[1]?.label,
1905
- )
1906
- onTransactionStateChange(transactionStates)
1907
-
1908
- try {
1909
- const receipt = await publicClient.getTransactionReceipt({
1910
- hash: originMetaTxnReceipt.txnHash as `0x${string}`,
1911
- })
1912
- transactionStates[1].decodedTrailsTokenSweeperEvents =
1913
- decodeTrailsTokenSweeperEvents(receipt)
1914
- transactionStates[1].decodedGuestModuleEvents =
1915
- decodeGuestModuleEvents(receipt)
1916
- transactionStates[1].refunded =
1917
- transactionStates[1].decodedTrailsTokenSweeperEvents.findIndex(
1918
- (event) =>
1919
- event.type === "Refund" ||
1920
- event.type === "RefundAndSweep",
1921
- ) !== -1
1922
- logger.console.log("[trails-sdk] Origin meta-tx events", {
1923
- chainId: originChainId,
1924
- callFailed: (
1925
- transactionStates[1].decodedGuestModuleEvents || []
1926
- ).filter((e: any) => e?.type === "CallFailed").length,
1927
- sweeperEvents: (
1928
- transactionStates[1].decodedTrailsTokenSweeperEvents || []
1929
- ).length,
1930
- refunded: transactionStates[1].refunded,
1931
- })
1932
-
1933
- // Check for transaction failure or refund
1934
- const hasCallFailed = (
1935
- transactionStates[1].decodedGuestModuleEvents || []
1936
- ).some((e: any) => e?.type === "CallFailed")
1937
-
1938
- if (transactionStates[1].refunded || hasCallFailed) {
1939
- const errorMessage = transactionStates[1].refunded
1940
- ? "Origin transaction was refunded"
1941
- : "Origin transaction call failed"
1942
-
1943
- logger.console.error("[trails-sdk] Origin meta-tx failed", {
1944
- refunded: transactionStates[1].refunded,
1945
- callFailed: hasCallFailed,
1946
- })
1947
-
1948
- // Call onCheckoutError callback if provided
1949
- if (checkoutOnHandlers?.triggerCheckoutError) {
1950
- checkoutOnHandlers.triggerCheckoutError(errorMessage)
1951
- }
1952
- }
1953
-
1954
- onTransactionStateChange(transactionStates)
1955
- } catch (error) {
1956
- logger.console.error("Error decoding origin tx events", error)
1957
- }
1958
- }
1959
- } catch (error) {
1960
- logger.console.error(
1961
- "[trails-sdk] Error waiting for origin receipt:",
1962
- error,
1963
- )
1964
- }
1965
- } else {
1966
- logger.console.log(
1967
- "[trails-sdk] No origin meta transaction receipt promise to wait for",
1968
- )
1969
- }
1970
- }
1971
-
1972
- const waitForDestinationMetaTxnReceiptPromise = async () => {
1973
- logger.console.log(
1974
- "[trails-sdk] Waiting for destination meta transaction receipt",
1975
- )
1976
- if (destinationMetaTxnReceiptPromise) {
1977
- try {
1978
- // Create abort controller for cancelling destination polling
1979
- const abortController = new AbortController()
1980
-
1981
- // Race between destination receipt and failure polling
1982
- const destinationReceiptPromise = destinationMetaTxnReceiptPromise
1983
- ? destinationMetaTxnReceiptPromise(abortController.signal)
1984
- : Promise.resolve(null)
1985
-
1986
- const failurePollingPromise = new Promise<null>((resolve) => {
1987
- const pollForFailures = () => {
1988
- const isPreviousTxCallFailed =
1989
- transactionStates?.some((tx) => tx.state === "failed") ||
1990
- transactionStates?.some((tx) =>
1991
- tx?.decodedGuestModuleEvents?.some(
1992
- (event) => event.type === "CallFailed",
1993
- ),
1994
- )
1995
-
1996
- if (isPreviousTxCallFailed) {
1997
- logger.console.log(
1998
- "[trails-sdk] Aborting destination meta transaction due to previous transaction failure",
1999
- )
2000
- abortController.abort()
2001
- resolve(null)
2002
- } else {
2003
- // Continue polling every 1 second
2004
- setTimeout(pollForFailures, 1000)
2005
- }
2006
- }
2007
- pollForFailures()
2008
- })
2009
-
2010
- destinationMetaTxnReceipt = (await Promise.race([
2011
- destinationReceiptPromise,
2012
- failurePollingPromise,
2013
- ])) as MetaTxnReceipt
2014
-
2015
- logger.console.log(
2016
- "[trails-sdk] destinationMetaTxnReceipt",
2017
- destinationMetaTxnReceipt,
2018
- )
2019
-
2020
- if (destinationMetaTxnReceipt && transactionStates[2]) {
2021
- transactionStates[2] = getTransactionStateFromReceipt(
2022
- destinationMetaTxnReceipt,
2023
- destinationChainId,
2024
- transactionStates[2]?.label,
2025
- )
2026
- onTransactionStateChange(transactionStates)
2027
-
2028
- try {
2029
- const receipt =
2030
- await destinationPublicClient.getTransactionReceipt({
2031
- hash: destinationMetaTxnReceipt.txnHash as `0x${string}`,
2032
- })
2033
- transactionStates[2].decodedTrailsTokenSweeperEvents =
2034
- decodeTrailsTokenSweeperEvents(receipt)
2035
- transactionStates[2].decodedGuestModuleEvents =
2036
- decodeGuestModuleEvents(receipt)
2037
- transactionStates[2].refunded =
2038
- transactionStates[2].decodedTrailsTokenSweeperEvents.findIndex(
2039
- (event) =>
2040
- event.type === "Refund" ||
2041
- event.type === "RefundAndSweep",
2042
- ) !== -1 ||
2043
- (transactionStates[2].decodedTrailsTokenSweeperEvents.findIndex(
2044
- (event) => event.type === "Sweep",
2045
- ) !== -1 &&
2046
- transactionStates[2].decodedGuestModuleEvents.findIndex(
2047
- (event) => event.type === "CallFailed",
2048
- ) !== -1)
2049
- logger.console.log(
2050
- "[trails-sdk] Destination meta-tx events",
2051
- {
2052
- chainId: destinationChainId,
2053
- callFailed: (
2054
- transactionStates[2].decodedGuestModuleEvents || []
2055
- ).filter((e: any) => e?.type === "CallFailed").length,
2056
- sweeperEvents: (
2057
- transactionStates[2].decodedTrailsTokenSweeperEvents ||
2058
- []
2059
- ).length,
2060
- refunded: transactionStates[2].refunded,
2061
- },
2062
- )
2063
-
2064
- // Check for transaction failure or refund
2065
- const hasCallFailed = (
2066
- transactionStates[2].decodedGuestModuleEvents || []
2067
- ).some((e: any) => e?.type === "CallFailed")
2068
-
2069
- if (transactionStates[2].refunded || hasCallFailed) {
2070
- const errorMessage = transactionStates[2].refunded
2071
- ? "Destination transaction was refunded"
2072
- : "Destination transaction call failed"
2073
-
2074
- logger.console.error(
2075
- "[trails-sdk] Destination meta-tx failed",
2076
- {
2077
- refunded: transactionStates[2].refunded,
2078
- callFailed: hasCallFailed,
2079
- },
2080
- )
2081
-
2082
- // Call onCheckoutError callback if provided
2083
- if (checkoutOnHandlers?.triggerCheckoutError) {
2084
- checkoutOnHandlers.triggerCheckoutError(errorMessage)
2085
- }
2086
- }
2087
-
2088
- onTransactionStateChange(transactionStates)
2089
- } catch (error) {
2090
- logger.console.error(
2091
- "Error decoding destination tx events",
2092
- error,
2093
- )
2094
- }
2095
- }
2096
- } catch (error) {
2097
- logger.console.error(
2098
- "[trails-sdk] Error waiting for destination receipt:",
2099
- error,
2100
- )
2101
- // For relay transactions, this might be expected if still waiting
2102
- if (intent.payloads.quote.quoteProvider === "relay") {
2103
- logger.console.log(
2104
- "[trails-sdk] Relay transaction still waiting, this is normal",
2105
- )
2106
- }
2107
- }
2108
- } else {
2109
- logger.console.log(
2110
- "[trails-sdk] No destination meta transaction receipt promise to wait for",
2111
- )
2112
- }
2113
- }
2114
-
2115
- logger.console.log(
2116
- "[trails-sdk] Executing Phase 2 Promise.all with deposit",
2117
- )
2118
- logger.console.log(
2119
- "[trails-sdk] About to call depositPromise - fundMethod is:",
2120
- fundMethod,
2121
- )
2122
-
2123
- await Promise.all([
2124
- depositPromise(),
2125
- waitForOriginMetaTxnReceiptPromise(),
2126
- waitForDestinationMetaTxnReceiptPromise(),
2127
- queueCctpPromise(),
2128
- ])
2129
- logger.console.log("[trails-sdk] Phase 2 completed successfully")
2130
-
2131
- // Track payment completion for different chain and different token
2132
- if (originUserTxReceipt && destinationMetaTxnReceipt) {
2133
- // Check if any transaction failed or was refunded
2134
- const hasAnyCallFailed = transactionStates.some((tx) =>
2135
- tx?.decodedGuestModuleEvents?.some(
2136
- (e: any) => e?.type === "CallFailed",
2137
- ),
2138
- )
2139
- const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
2140
- const txStatus =
2141
- !hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
2142
-
2143
- // Always track payment completion regardless of success/failure
2144
- trackPaymentCompleted({
2145
- userAddress: account.address,
2146
- originIntentAddress,
2147
- originTxHash: (originUserTxReceipt as TransactionReceipt)
2148
- .transactionHash,
2149
- destinationTxHash: (destinationMetaTxnReceipt as MetaTxnReceipt)
2150
- ?.txnHash,
2151
- originChainId,
2152
- destinationChainId,
2153
- mode,
2154
- fundMethod,
2155
- originTokenSymbol,
2156
- originTokenAddress,
2157
- destinationTokenAddress,
2158
- destinationTokenSymbol,
2159
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2160
- destinationTokenAmountUsd:
2161
- effectiveDestinationTokenAmountUsd?.toString(),
2162
- })
2163
-
2164
- // Call onCheckoutComplete callback with transaction status
2165
- if (checkoutOnHandlers?.triggerCheckoutComplete) {
2166
- checkoutOnHandlers.triggerCheckoutComplete(txStatus)
2167
- }
2168
- } else {
2169
- if (
2170
- transactionStates[1] &&
2171
- transactionStates[1]?.transactionHash === "" &&
2172
- transactionStates[1]?.state === "pending"
2173
- ) {
2174
- transactionStates[1].state = "aborted"
2175
- onTransactionStateChange(transactionStates)
2176
- }
2177
- if (
2178
- transactionStates[2] &&
2179
- transactionStates[2]?.transactionHash === "" &&
2180
- transactionStates[2]?.state === "pending"
2181
- ) {
2182
- transactionStates[2].state = "aborted"
2183
- onTransactionStateChange(transactionStates)
2184
- }
2185
-
2186
- // Track payment error if transactions didn't complete successfully
2187
- trackPaymentError({
2188
- error:
2189
- "Payment transactions possibly did not complete successfully. Was not able to receive both origin and destination meta transaction receipts. May be an API error.",
2190
- userAddress: account.address,
2191
- originIntentAddress,
2192
- mode,
2193
- fundMethod,
2194
- originChainId,
2195
- destinationChainId,
2196
- originTokenSymbol,
2197
- originTokenAddress,
2198
- destinationTokenAddress,
2199
- destinationTokenSymbol,
2200
- })
2201
-
2202
- // Call onCheckoutError callback if provided
2203
- if (checkoutOnHandlers?.triggerCheckoutError) {
2204
- checkoutOnHandlers.triggerCheckoutError(
2205
- "Payment transactions did not complete successfully",
2206
- )
2207
- }
2208
- }
2209
-
2210
- return {
2211
- originUserTxReceipt,
2212
- originMetaTxnReceipt,
2213
- destinationMetaTxnReceipt,
2214
- totalCompletionSeconds: await getTxTimeDiff(
2215
- transactionStates[0],
2216
- transactionStates[2],
2217
- ),
2218
- }
2219
- } catch (error) {
2220
- const errorMessage =
2221
- error instanceof Error
2222
- ? error.message
2223
- : "Unknown error occurred during transaction"
2224
- logger.console.error(
2225
- "[trails-sdk] Error in sendHandlerForDifferentChainDifferentToken:",
2226
- error,
2227
- )
2228
-
2229
- // Track payment error
2230
- trackPaymentError({
2231
- error: errorMessage,
2232
- userAddress: account.address,
2233
- originIntentAddress,
2234
- mode,
2235
- fundMethod,
2236
- originChainId,
2237
- destinationChainId,
2238
- originTokenSymbol,
2239
- originTokenAddress,
2240
- destinationTokenAddress,
2241
- destinationTokenSymbol,
2242
- })
2243
-
2244
- // Call onCheckoutError callback if provided
2245
- if (checkoutOnHandlers?.triggerCheckoutError) {
2246
- checkoutOnHandlers.triggerCheckoutError(errorMessage)
2247
- }
2248
-
2249
- // Re-throw the error so caller can handle if needed
2250
- throw error
2251
- }
2252
- },
2253
- }
2254
- }
2255
-
2256
- async function sendHandlerForSameChainSameToken({
2257
- originTokenAddress,
2258
- originTokenDecimals,
2259
- swapAmount,
2260
- destinationCalldata,
2261
- recipient,
2262
- walletClient,
2263
- onTransactionStateChange,
2264
- dryMode,
2265
- account,
2266
- chain,
2267
- transactionStates,
2268
- sourceTokenPriceUsd,
2269
- destinationTokenPriceUsd,
2270
- originNativeTokenPriceUsd,
2271
- slippageTolerance,
2272
- checkoutOnHandlers,
2273
- mode,
2274
- fundMethod,
2275
- gasless,
2276
- paymasterUrl,
2277
- selectedFeeToken,
2278
- trailsClient,
2279
- originRelayer,
2280
- }: {
2281
- originTokenAddress: string
2282
- originTokenDecimals: number
2283
- swapAmount: string
2284
- destinationCalldata?: string
2285
- recipient: string
2286
- originChainId: number
2287
- walletClient: WalletClient
2288
- publicClient: PublicClient
2289
- onTransactionStateChange: (transactionStates: TransactionState[]) => void
2290
- dryMode: boolean
2291
- account: Account
2292
- chain: Chain
2293
- transactionStates: TransactionState[]
2294
- sourceTokenPriceUsd?: number | null
2295
- destinationTokenPriceUsd?: number | null
2296
- originNativeTokenPriceUsd?: number | null
2297
- slippageTolerance?: string
2298
- checkoutOnHandlers?: CheckoutOnHandlers
2299
- mode?: "pay" | "fund" | "earn" | "swap" | "receive"
2300
- fundMethod?: string
2301
- gasless?: boolean
2302
- paymasterUrl?: string
2303
- selectedFeeToken?: any
2304
- trailsClient: TrailsAPIClient
2305
- originRelayer: Relayer.RpcRelayer
2306
- }): Promise<PrepareSendReturn> {
2307
- logger.console.log("[trails-sdk] isToSameToken && isToSameChain")
2308
- const testnet = isTestnetDebugMode()
2309
- const effectiveOriginChain = testnet ? getTestnetChainInfo(chain)! : chain
2310
- const effectiveOriginChainId = effectiveOriginChain.id
2311
- const effectiveOriginTokenAddress = testnet
2312
- ? getTestnetOriginTokenAddress(effectiveOriginChainId)
2313
- : originTokenAddress
2314
- const effectivePublicClient = createPublicClient({
2315
- chain: effectiveOriginChain,
2316
- transport: http(),
2317
- })
2318
-
2319
- let noSufficientBalance = false
2320
-
2321
- const { hasEnoughBalance } = await checkAccountBalance({
2322
- account,
2323
- tokenAddress: originTokenAddress,
2324
- depositAmount: swapAmount,
2325
- publicClient: effectivePublicClient,
2326
- })
2327
-
2328
- if (!hasEnoughBalance) {
2329
- noSufficientBalance = true
2330
- }
2331
-
2332
- // Build origin call params and estimate gas limit for the quote
2333
- const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
2334
- const originCallParamsBase = buildSameChainTransactionParams({
2335
- hasCustomCalldata,
2336
- recipient,
2337
- effectiveOriginTokenAddress,
2338
- destinationCalldata,
2339
- swapAmount,
2340
- effectiveOriginChainId,
2341
- effectiveOriginChain,
2342
- })
2343
-
2344
- logger.console.log(
2345
- "[trails-sdk][gas-estimation] About to estimate gas limit for quote with params:",
2346
- {
2347
- account: account.address,
2348
- to: originCallParamsBase.to,
2349
- data: originCallParamsBase.data,
2350
- value: originCallParamsBase.value,
2351
- },
2352
- )
2353
-
2354
- const estimatedGasLimitForQuote = await estimateGasLimit(
2355
- effectivePublicClient,
2356
- {
2357
- account: account.address,
2358
- to: originCallParamsBase.to,
2359
- data: originCallParamsBase.data,
2360
- value: BigInt(originCallParamsBase.value),
2361
- },
2362
- "quote",
2363
- )
2364
-
2365
- logger.console.log(
2366
- "[trails-sdk][gas-estimation] Estimated gas limit for quote:",
2367
- estimatedGasLimitForQuote,
2368
- )
2369
-
2370
- const quote = await getNormalizedQuoteObject({
2371
- originDepositAddress: recipient,
2372
- destinationDepositAddress: recipient,
2373
- destinationAddress: recipient,
2374
- destinationCalldata,
2375
- originAmount: swapAmount, // fromAmount is same as toAmount for same chain same token
2376
- destinationAmount: swapAmount,
2377
- originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
2378
- destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
2379
- originTokenAddress: effectiveOriginTokenAddress,
2380
- destinationTokenAddress: effectiveOriginTokenAddress,
2381
- transactionStates,
2382
- originChainId: effectiveOriginChainId,
2383
- destinationChainId: effectiveOriginChainId,
2384
- originNativeTokenPriceUsd,
2385
- slippageTolerance,
2386
- quoteProvider: "",
2387
- noSufficientBalance,
2388
- estimatedGasLimit: estimatedGasLimitForQuote,
2389
- })
2390
-
2391
- // Call onCheckoutQuote callback if provided
2392
- if (checkoutOnHandlers?.triggerCheckoutQuote) {
2393
- checkoutOnHandlers.triggerCheckoutQuote(quote)
2394
- }
2395
-
2396
- return {
2397
- quote,
2398
- send: async ({
2399
- onOriginSend,
2400
- }: {
2401
- onOriginSend?: () => void
2402
- }): Promise<SendReturn> => {
2403
- try {
2404
- const { hasEnoughBalance, balanceError } = await checkAccountBalance({
2405
- account,
2406
- tokenAddress: effectiveOriginTokenAddress,
2407
- depositAmount: swapAmount,
2408
- publicClient: effectivePublicClient,
2409
- })
2410
-
2411
- if (!hasEnoughBalance) {
2412
- throw balanceError
2413
- }
2414
-
2415
- const depositAmountFormatted = Number(
2416
- formatUnits(BigInt(swapAmount), originTokenDecimals),
2417
- )
2418
- const depositAmountUsd = calcAmountUsdPrice({
2419
- amount: depositAmountFormatted,
2420
- usdPrice: sourceTokenPriceUsd,
2421
- })
2422
-
2423
- const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
2424
-
2425
- // Build origin call params (reusing the same logic as quote estimation)
2426
- const originCallParamsBase = buildSameChainTransactionParams({
2427
- hasCustomCalldata,
2428
- recipient,
2429
- effectiveOriginTokenAddress,
2430
- destinationCalldata,
2431
- swapAmount,
2432
- effectiveOriginChainId,
2433
- effectiveOriginChain,
2434
- })
2435
-
2436
- // Estimate gas limit for consistency with actual transaction
2437
- const gasLimit = await estimateGasLimit(
2438
- effectivePublicClient,
2439
- {
2440
- account: account.address,
2441
- to: originCallParamsBase.to,
2442
- data: originCallParamsBase.data,
2443
- value: BigInt(originCallParamsBase.value),
2444
- },
2445
- "send",
2446
- )
2447
-
2448
- const originCallParams = {
2449
- ...originCallParamsBase,
2450
- gasLimit,
2451
- }
2452
-
2453
- logger.console.log("[trails-sdk] origin call params", originCallParams)
2454
-
2455
- let originUserTxReceipt: TransactionReceipt | null = null
2456
- const originMetaTxnReceipt: MetaTxnReceipt | null = null
2457
- const destinationMetaTxnReceipt: MetaTxnReceipt | null = null
2458
-
2459
- await attemptSwitchChain({
2460
- walletClient,
2461
- desiredChainId: effectiveOriginChainId,
2462
- })
2463
- if (!dryMode) {
2464
- try {
2465
- onTransactionStateChange([
2466
- {
2467
- transactionHash: "",
2468
- explorerUrl: "",
2469
- chainId: effectiveOriginChainId,
2470
- state: "pending",
2471
- label: "Execute",
2472
- },
2473
- ])
2474
- } catch (error) {
2475
- logger.console.error(
2476
- "[trails-sdk] Error calling onTransactionStateChange:",
2477
- error,
2478
- )
2479
- }
2480
-
2481
- // For gasless transactions with custom calldata, use the gasless deposit flow
2482
- if (gasless && hasCustomCalldata) {
2483
- logger.console.log(
2484
- "[trails-sdk] Using gasless intent entrypoint flow for same-chain transaction with custom calldata",
2485
- )
2486
-
2487
- // Fetch fee options for gasless deposit
2488
- let feeOptions: any = null
2489
- try {
2490
- feeOptions = await getIntentEntrypointFeeOptions({
2491
- trailsClient,
2492
- userAddress: account.address as `0x${string}`,
2493
- tokenAddress: effectiveOriginTokenAddress as `0x${string}`,
2494
- amount: swapAmount,
2495
- intentAddress: recipient as `0x${string}`,
2496
- chainId: effectiveOriginChainId,
2497
- })
2498
- logger.console.log(
2499
- "[trails-sdk] [GASLESS-FLOW] Fetched intent entrypoint fee options for same-chain transaction:",
2500
- feeOptions,
2501
- )
2502
- } catch (error) {
2503
- logger.console.error(
2504
- "[trails-sdk] [GASLESS-FLOW] Error fetching fee options for same-chain gasless transaction:",
2505
- error,
2506
- )
2507
- }
2508
-
2509
- const receipt = await attemptGaslessDeposit({
2510
- account,
2511
- walletClient,
2512
- chain: effectiveOriginChain,
2513
- depositTokenAddress: effectiveOriginTokenAddress,
2514
- depositTokenAmount: swapAmount,
2515
- depositRecipient: recipient,
2516
- onOriginSend: () => {}, // No-op callback
2517
- feeOptions,
2518
- paymasterUrl,
2519
- selectedFeeToken,
2520
- trailsClient,
2521
- originRelayer,
2522
- })
2523
-
2524
- if (receipt) {
2525
- originUserTxReceipt = receipt
2526
-
2527
- // Track the confirmed transaction
2528
- trackTransactionConfirmed({
2529
- transactionHash: receipt.transactionHash,
2530
- chainId: effectiveOriginChainId,
2531
- userAddress: account.address,
2532
- blockNumber: Number(receipt.blockNumber),
2533
- originTokenAddress,
2534
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2535
- })
2536
-
2537
- // Toast will be shown after transaction state analysis
2538
-
2539
- // Update transaction state
2540
- try {
2541
- onTransactionStateChange([
2542
- getTransactionStateFromReceipt(
2543
- receipt,
2544
- effectiveOriginChainId,
2545
- transactionStates[0]?.label,
2546
- ),
2547
- ])
2548
- } catch (error) {
2549
- logger.console.error(
2550
- "[trails-sdk] Error calling onTransactionStateChange:",
2551
- error,
2552
- )
2553
- }
2554
- }
2555
- } else {
2556
- // Non-gasless flow: handle approval if needed
2557
- if (hasCustomCalldata) {
2558
- try {
2559
- const needsApproval = await getNeedsApproval({
2560
- publicClient: effectivePublicClient,
2561
- token: effectiveOriginTokenAddress,
2562
- account: account.address,
2563
- spender: recipient,
2564
- amount: maxUint256,
2565
- })
2566
-
2567
- if (needsApproval) {
2568
- const txHash = await approveERC20({
2569
- walletClient,
2570
- tokenAddress: effectiveOriginTokenAddress,
2571
- spender: recipient,
2572
- amount: maxUint256,
2573
- chain: effectiveOriginChain,
2574
- })
2575
-
2576
- logger.console.log("waiting for approve", txHash)
2577
- await effectivePublicClient.waitForTransactionReceipt({
2578
- hash: txHash,
2579
- })
2580
- logger.console.log("approve done")
2581
- }
2582
- } catch (error) {
2583
- logger.console.error(
2584
- "[trails-sdk] Error approving ERC20",
2585
- error,
2586
- )
2587
- }
2588
- }
2589
-
2590
- // Show persistent toast for checkout flow
2591
- updatePersistentToast(
2592
- "Payment Started",
2593
- "Waiting for wallet confirmation...",
2594
- "info",
2595
- )
2596
-
2597
- logger.console.log(
2598
- "[trails-sdk] origin call params",
2599
- originCallParams,
2600
- )
2601
- const txHash = await sendOriginTransaction(
2602
- account,
2603
- walletClient,
2604
- originCallParams as any,
2605
- {
2606
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2607
- },
2608
- ) // TODO: Add proper type
2609
-
2610
- logger.console.log("[trails-sdk] origin tx", txHash)
2611
-
2612
- if (onOriginSend) {
2613
- onOriginSend()
2614
- }
2615
-
2616
- // Wait for transaction receipt
2617
- const receipt =
2618
- await effectivePublicClient.waitForTransactionReceipt({
2619
- hash: txHash,
2620
- })
2621
- logger.console.log("[trails-sdk] receipt", receipt)
2622
- originUserTxReceipt = receipt
2623
-
2624
- trackTransactionConfirmed({
2625
- transactionHash: txHash,
2626
- chainId: effectiveOriginChainId,
2627
- userAddress: account.address,
2628
- blockNumber: Number(receipt.blockNumber),
2629
- originTokenAddress,
2630
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2631
- })
2632
-
2633
- // Toast will be shown after transaction state analysis
2634
-
2635
- try {
2636
- onTransactionStateChange([
2637
- getTransactionStateFromReceipt(
2638
- originUserTxReceipt,
2639
- effectiveOriginChainId,
2640
- transactionStates[0]?.label,
2641
- ),
2642
- ])
2643
- } catch (error) {
2644
- logger.console.error(
2645
- "[trails-sdk] Error calling onTransactionStateChange:",
2646
- error,
2647
- )
2648
- }
2649
- }
2650
-
2651
- // Show conditional toast based on transaction status
2652
- const chainInfo = getChainInfo(effectiveOriginChainId)
2653
- if (originUserTxReceipt) {
2654
- if (originUserTxReceipt?.status === "success") {
2655
- updatePersistentToast(
2656
- "Transfer Confirmed",
2657
- `Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
2658
- "info",
2659
- )
2660
- } else {
2661
- updatePersistentToast(
2662
- "Transfer Failed",
2663
- `Your transaction on ${chainInfo?.name || "chain"} failed`,
2664
- "error",
2665
- )
2666
- }
2667
- }
2668
-
2669
- // Track payment completion for same-chain same-token transaction
2670
- if (originUserTxReceipt && originUserTxReceipt.status === "success") {
2671
- // Check if any transaction failed or was refunded
2672
- const hasAnyCallFailed = transactionStates.some((tx) =>
2673
- tx?.decodedGuestModuleEvents?.some(
2674
- (e: any) => e?.type === "CallFailed",
2675
- ),
2676
- )
2677
- const hasAnyRefunded = transactionStates.some((tx) => tx?.refunded)
2678
- const txStatus =
2679
- !hasAnyCallFailed && !hasAnyRefunded ? "success" : "fail"
2680
-
2681
- // Always track payment completion regardless of success/failure
2682
- trackPaymentCompleted({
2683
- userAddress: account.address,
2684
- originTxHash: originUserTxReceipt.transactionHash,
2685
- originChainId: effectiveOriginChainId, // Same chain
2686
- mode,
2687
- fundMethod,
2688
- originTokenAddress,
2689
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2690
- destinationTokenAmountUsd: depositAmountUsd?.toString(), // same as deposit amount
2691
- })
2692
-
2693
- // Call onCheckoutComplete callback with transaction status
2694
- if (checkoutOnHandlers?.triggerCheckoutComplete) {
2695
- checkoutOnHandlers.triggerCheckoutComplete(txStatus)
2696
- }
2697
- } else if (originUserTxReceipt) {
2698
- trackPaymentError({
2699
- error: "Transaction failed",
2700
- userAddress: account.address,
2701
- mode,
2702
- fundMethod,
2703
- originTokenAddress,
2704
- })
2705
-
2706
- // Call onCheckoutError callback if provided
2707
- if (checkoutOnHandlers?.triggerCheckoutError) {
2708
- checkoutOnHandlers.triggerCheckoutError("Transaction failed")
2709
- }
2710
- }
2711
- }
2712
-
2713
- return {
2714
- originUserTxReceipt,
2715
- originMetaTxnReceipt,
2716
- destinationMetaTxnReceipt,
2717
- totalCompletionSeconds: 0,
2718
- }
2719
- } catch (error) {
2720
- const errorMessage =
2721
- error instanceof Error
2722
- ? error.message
2723
- : "Unknown error occurred during transaction"
2724
- logger.console.error(
2725
- "[trails-sdk] Error in sendHandlerForSameChainSameToken:",
2726
- error,
2727
- )
2728
-
2729
- // Track payment error
2730
- trackPaymentError({
2731
- error: errorMessage,
2732
- userAddress: account.address,
2733
- mode,
2734
- fundMethod,
2735
- originTokenAddress,
2736
- })
2737
-
2738
- // Call onCheckoutError callback if provided
2739
- if (checkoutOnHandlers?.triggerCheckoutError) {
2740
- checkoutOnHandlers.triggerCheckoutError(errorMessage)
2741
- }
2742
-
2743
- // Re-throw the error so caller can handle if needed
2744
- throw error
2745
- }
2746
- },
2747
- }
2748
- }
2749
-
2750
- // This handler uses relay sdk directly
2751
- async function _sendHandlerForSameChainDifferentToken({
2752
- originTokenAddress,
2753
- originTokenDecimals,
2754
- swapAmount,
2755
- destinationTokenAddress,
2756
- destinationTokenDecimals,
2757
- destinationCalldata,
2758
- recipient,
2759
- originChainId,
2760
- walletClient,
2761
- publicClient,
2762
- account,
2763
- tradeType = TradeType.EXACT_OUTPUT,
2764
- slippageTolerance,
2765
- onTransactionStateChange,
2766
- transactionStates,
2767
- sourceTokenPriceUsd,
2768
- destinationTokenPriceUsd,
2769
- originNativeTokenPriceUsd,
2770
- mode,
2771
- fundMethod,
2772
- originTokenSymbol,
2773
- destinationTokenSymbol,
2774
- }: {
2775
- originTokenAddress: string
2776
- originTokenDecimals: number
2777
- swapAmount: string
2778
- destinationTokenAddress: string
2779
- destinationTokenDecimals: number
2780
- destinationCalldata?: string
2781
- recipient: string
2782
- originChainId: number
2783
- walletClient: WalletClient
2784
- publicClient: PublicClient
2785
- account: Account
2786
- tradeType?: TradeType
2787
- slippageTolerance?: string
2788
- onTransactionStateChange: (transactionStates: TransactionState[]) => void
2789
- transactionStates: TransactionState[]
2790
- sourceTokenPriceUsd?: number | null
2791
- destinationTokenPriceUsd?: number | null
2792
- originNativeTokenPriceUsd?: number | null
2793
- mode?: "pay" | "fund" | "earn" | "swap" | "receive"
2794
- fundMethod?: string
2795
- originTokenSymbol: string
2796
- destinationTokenSymbol: string
2797
- }): Promise<PrepareSendReturn> {
2798
- const destinationTxs: { to: string; value: string; data: string }[] = []
2799
- const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
2800
- if (hasCustomCalldata && tradeType === TradeType.EXACT_OUTPUT) {
2801
- destinationTxs.push({
2802
- to: recipient,
2803
- value: destinationTokenAddress === zeroAddress ? swapAmount : "0",
2804
- data: destinationCalldata as `0x${string}`,
2805
- })
2806
- } else if (hasCustomCalldata && tradeType === TradeType.EXACT_INPUT) {
2807
- destinationTxs.push({
2808
- to: recipient,
2809
- value: "0",
2810
- data: destinationCalldata as `0x${string}`,
2811
- })
2812
- }
2813
-
2814
- const quote = await getRelaySDKQuote({
2815
- wallet: walletClient,
2816
- chainId: originChainId,
2817
- amount: swapAmount,
2818
- currency: originTokenAddress,
2819
- toCurrency: destinationTokenAddress,
2820
- txs: destinationTxs,
2821
- tradeType: tradeType as unknown as RelayTradeType,
2822
- slippageTolerance: slippageTolerance,
2823
- recipient: hasCustomCalldata ? recipient : undefined,
2824
- })
2825
-
2826
- logger.console.log("[trails-sdk] relaysdk quote", quote)
2827
- let depositAmount = "0"
2828
- let destinationTokenAmount = "0"
2829
-
2830
- if (tradeType === TradeType.EXACT_INPUT) {
2831
- depositAmount = swapAmount
2832
- destinationTokenAmount = quote?.details?.currencyOut?.amount?.toString()
2833
- } else {
2834
- try {
2835
- destinationTokenAmount = swapAmount
2836
- depositAmount = quote.steps?.[0]?.items?.[0]?.data?.value
2837
- if (originTokenAddress !== zeroAddress) {
2838
- const decoded = decodeFunctionData({
2839
- abi: erc20Abi,
2840
- data: quote.steps?.[0]?.items?.[0]?.data?.data,
2841
- })
2842
- if (decoded.functionName === "approve") {
2843
- depositAmount = decoded.args[1].toString()
2844
- }
2845
- if (decoded.functionName === "transfer") {
2846
- depositAmount = decoded.args[1].toString()
2847
- }
2848
- }
2849
- } catch (error) {
2850
- logger.console.error("[trails-sdk] Error decoding function data:", error)
2851
- }
2852
- }
2853
-
2854
- const depositOriginAddress =
2855
- quote?.steps?.[quote?.steps?.length - 1]?.items?.[
2856
- quote?.steps?.[quote?.steps?.length - 1]?.items?.length - 1
2857
- ]?.data?.to
2858
-
2859
- if (
2860
- quote?.details?.currencyIn?.amountFormatted &&
2861
- quote?.details?.currencyIn?.amountUsd
2862
- ) {
2863
- const quoteProviderSourceTokenPriceUsd =
2864
- Number(quote.details.currencyIn.amountUsd) /
2865
- Number(quote.details.currencyIn.amountFormatted)
2866
- if (quoteProviderSourceTokenPriceUsd) {
2867
- sourceTokenPriceUsd = quoteProviderSourceTokenPriceUsd
2868
- }
2869
- }
2870
-
2871
- if (
2872
- quote?.details?.currencyOut?.amountFormatted &&
2873
- quote?.details?.currencyOut?.amountUsd
2874
- ) {
2875
- const quoteProviderDestinationTokenPriceUsd =
2876
- Number(quote.details.currencyOut.amountUsd) /
2877
- Number(quote.details.currencyOut.amountFormatted)
2878
- if (quoteProviderDestinationTokenPriceUsd) {
2879
- destinationTokenPriceUsd = quoteProviderDestinationTokenPriceUsd
2880
- }
2881
- }
2882
-
2883
- const normalizedQuote = await getNormalizedQuoteObject({
2884
- originDepositAddress: depositOriginAddress,
2885
- destinationDepositAddress: recipient,
2886
- destinationAddress: recipient,
2887
- destinationCalldata,
2888
- originAmount: depositAmount,
2889
- destinationAmount: destinationTokenAmount,
2890
- originTokenAddress: originTokenAddress,
2891
- destinationTokenAddress: destinationTokenAddress,
2892
- originTokenPriceUsd: sourceTokenPriceUsd?.toString() || null,
2893
- destinationTokenPriceUsd: destinationTokenPriceUsd?.toString() || null,
2894
- fees: getFeesFromRelaySdkQuote(quote),
2895
- slippageTolerance: getSlippageToleranceFromRelaySdkQuote(quote),
2896
- priceImpact: getPriceImpactFromRelaySdkQuote(quote),
2897
- priceImpactUsd: getPriceImpactUsdFromRelaySdkQuote(quote),
2898
- transactionStates,
2899
- originChainId,
2900
- destinationChainId: originChainId,
2901
- originNativeTokenPriceUsd,
2902
- quoteProvider: "relay",
2903
- })
2904
-
2905
- return {
2906
- quote: normalizedQuote,
2907
- send: async ({
2908
- onOriginSend,
2909
- }: {
2910
- onOriginSend?: () => void
2911
- }): Promise<SendReturn> => {
2912
- const { hasEnoughBalance, balanceError } = await checkAccountBalance({
2913
- account,
2914
- tokenAddress: originTokenAddress,
2915
- depositAmount,
2916
- publicClient,
2917
- })
2918
-
2919
- if (!hasEnoughBalance) {
2920
- throw balanceError
2921
- }
2922
-
2923
- await attemptSwitchChain({
2924
- walletClient,
2925
- desiredChainId: originChainId,
2926
- })
2927
-
2928
- const result = await executeSimpleRelayTransaction(quote, walletClient)
2929
- logger.console.log("[trails-sdk] relaysdk result", result)
2930
-
2931
- const txHash = getTxHashFromRelayResult(result)
2932
-
2933
- if (onOriginSend) {
2934
- onOriginSend()
2935
- }
2936
-
2937
- const originUserTxReceipt = await publicClient.waitForTransactionReceipt({
2938
- hash: txHash as `0x${string}`,
2939
- })
2940
-
2941
- transactionStates[0] = getTransactionStateFromReceipt(
2942
- originUserTxReceipt,
2943
- originChainId,
2944
- transactionStates[0]?.label,
2945
- )
2946
-
2947
- try {
2948
- onTransactionStateChange(transactionStates)
2949
- } catch (error) {
2950
- logger.console.error(
2951
- "[trails-sdk] Error calling onTransactionStateChange:",
2952
- error,
2953
- )
2954
- }
2955
-
2956
- const depositAmountFormatted = Number(
2957
- formatUnits(BigInt(depositAmount), originTokenDecimals),
2958
- )
2959
-
2960
- const depositAmountUsd = calcAmountUsdPrice({
2961
- amount: depositAmountFormatted,
2962
- usdPrice: sourceTokenPriceUsd,
2963
- })
2964
-
2965
- const destinationTokenAmountFormatted = Number(
2966
- formatUnits(BigInt(destinationTokenAmount), destinationTokenDecimals),
2967
- )
2968
-
2969
- const destinationTokenAmountUsd = calcAmountUsdPrice({
2970
- amount: Number(destinationTokenAmountFormatted),
2971
- usdPrice: destinationTokenPriceUsd,
2972
- })
2973
-
2974
- // Track payment completion for same-chain different-token transaction
2975
- if (originUserTxReceipt && originUserTxReceipt.status === "success") {
2976
- trackPaymentCompleted({
2977
- userAddress: account.address,
2978
- originTxHash: originUserTxReceipt.transactionHash,
2979
- originChainId,
2980
- destinationChainId: originChainId, // Same chain
2981
- mode,
2982
- fundMethod,
2983
- originTokenAddress,
2984
- originTokenSymbol,
2985
- destinationTokenAddress,
2986
- destinationTokenSymbol,
2987
- depositTokenAmountUsd: depositAmountUsd?.toString(),
2988
- destinationTokenAmountUsd: destinationTokenAmountUsd?.toString(),
2989
- })
2990
- } else if (originUserTxReceipt) {
2991
- trackPaymentError({
2992
- error: "Relay transaction failed",
2993
- userAddress: account.address,
2994
- mode,
2995
- fundMethod,
2996
- originTokenAddress,
2997
- originTokenSymbol,
2998
- destinationTokenAddress,
2999
- destinationTokenSymbol,
3000
- })
3001
- }
3002
-
3003
- return {
3004
- originUserTxReceipt: originUserTxReceipt,
3005
- originMetaTxnReceipt: null,
3006
- destinationMetaTxnReceipt: null,
3007
- totalCompletionSeconds: 0,
3008
- }
3009
- },
3010
- }
3011
- }
3012
-
3013
- async function attemptGaslessDeposit({
3014
- paymasterUrl,
3015
- depositTokenAddress,
3016
- depositTokenAmount,
3017
- depositRecipient,
3018
- onOriginSend,
3019
- walletClient,
3020
- chain,
3021
- account,
3022
- trailsClient,
3023
- originRelayer,
3024
- feeOptions,
3025
- selectedFeeToken,
3026
- }: {
3027
- paymasterUrl?: string
3028
- depositTokenAddress: string
3029
- depositTokenAmount: string
3030
- depositRecipient: string
3031
- onOriginSend?: () => void
3032
- walletClient: WalletClient
3033
- chain: Chain
3034
- account: Account
3035
- trailsClient: TrailsAPIClient
3036
- originRelayer: Relayer.RpcRelayer
3037
- feeOptions: any
3038
- selectedFeeToken?: any
3039
- }): Promise<TransactionReceipt | null> {
3040
- let originUserTxReceipt: TransactionReceipt | null = null
3041
- const originChainId = chain.id
3042
-
3043
- logger.console.log(
3044
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] attemptGaslessDeposit called with:",
3045
- {
3046
- originChainId,
3047
- depositTokenAddress,
3048
- depositTokenAmount,
3049
- depositRecipient,
3050
- hasFeeOptions: !!feeOptions,
3051
- feeOptionsLength: feeOptions?.feeOptions?.length,
3052
- selectedFeeToken,
3053
- hasSelectedFeeToken: !!selectedFeeToken,
3054
- paymasterUrl,
3055
- },
3056
- )
3057
-
3058
- const publicClient = createPublicClient({
3059
- chain,
3060
- transport: http(),
3061
- })
3062
-
3063
- logger.console.log("[trails-sdk] [GASLESS-FLOW] Intent entrypoint check:", {
3064
- chainId: chain.id,
3065
- chainName: chain.name,
3066
- intentEntrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3067
- })
3068
-
3069
- // NEW FLOW: Use Intent Entrypoint API with permit2 support
3070
- logger.console.log(
3071
- "[trails-sdk] Using Intent Entrypoint API flow with permit2 support for gasless deposit",
3072
- )
3073
-
3074
- // Switch to correct chain before requesting signatures
3075
- logger.console.log(
3076
- "[trails-sdk] [GASLESS-FLOW] Switching to chain before permit/intent signatures",
3077
- { originChainId },
3078
- )
3079
- await attemptSwitchChain({
3080
- walletClient,
3081
- desiredChainId: originChainId,
3082
- })
3083
-
3084
- try {
3085
- if (paymasterUrl) {
3086
- logger.console.log(
3087
- "[trails-sdk] [GASLESS-FLOW] doing gasless with paymaster",
3088
- )
3089
-
3090
- // Switch to correct chain before requesting signatures
3091
- logger.console.log(
3092
- "[trails-sdk] [GASLESS-FLOW] Switching chain for paymaster flow",
3093
- )
3094
- await attemptSwitchChain({
3095
- walletClient,
3096
- desiredChainId: originChainId,
3097
- })
3098
-
3099
- const delegatorSmartAccount = await getDelegatorSmartAccount({
3100
- publicClient,
3101
- })
3102
-
3103
- const calls: Array<{
3104
- to: string
3105
- data: string
3106
- value: string
3107
- }> = await getPaymasterGaslessTransaction({
3108
- walletClient,
3109
- chain,
3110
- tokenAddress: depositTokenAddress as `0x${string}`,
3111
- amount: BigInt(depositTokenAmount),
3112
- recipient: depositRecipient as `0x${string}`,
3113
- delegatorSmartAccount,
3114
- })
3115
-
3116
- logger.console.log("[trails-sdk] calls", calls)
3117
-
3118
- const txHash = await sendPaymasterGaslessTransaction({
3119
- walletClient,
3120
- publicClient,
3121
- chain,
3122
- paymasterUrl,
3123
- delegatorSmartAccount,
3124
- calls,
3125
- })
3126
-
3127
- if (onOriginSend) {
3128
- onOriginSend()
3129
- }
3130
-
3131
- const receipt = await publicClient.waitForTransactionReceipt({
3132
- hash: txHash as `0x${string}`,
3133
- })
3134
- logger.console.log("[trails-sdk] receipt", receipt)
3135
- return receipt
3136
- }
3137
-
3138
- const deadline = Math.floor(Date.now() / 1000) + 3600 // 1 hour from now
3139
- const hasFeeOptions = Boolean(
3140
- feeOptions && feeOptions.feeOptions?.length > 0,
3141
- )
3142
-
3143
- // 1. Check if we need approval - check if we have enough allowance for this transaction (deposit + fee if same token)
3144
- let requiredAmount = BigInt(depositTokenAmount)
3145
-
3146
- // Match selectedFeeToken by tokenAddress to get the latest fee amount from feeOptions
3147
- let selectedFeeOption = null
3148
- if (selectedFeeToken && hasFeeOptions) {
3149
- // Find matching fee option by tokenAddress to get latest amount
3150
- selectedFeeOption = feeOptions.feeOptions.find(
3151
- (opt: any) =>
3152
- opt.tokenAddress?.toLowerCase() ===
3153
- selectedFeeToken.tokenAddress?.toLowerCase(),
3154
- )
3155
- logger.console.log(
3156
- "[trails-sdk] Matched selectedFeeToken to latest fee option:",
3157
- {
3158
- selectedFeeToken,
3159
- matchedOption: selectedFeeOption,
3160
- },
3161
- )
3162
- }
3163
-
3164
- // Fallback to first fee option if no match or no selectedFeeToken
3165
- if (!selectedFeeOption && hasFeeOptions) {
3166
- selectedFeeOption = feeOptions.feeOptions[0]
3167
- logger.console.log(
3168
- "[trails-sdk] Using first fee option as fallback:",
3169
- selectedFeeOption,
3170
- )
3171
- }
3172
-
3173
- // Only include fee in required amount if the fee token is the same as the deposit token
3174
- if (selectedFeeOption?.amount && selectedFeeOption?.tokenAddress) {
3175
- const feeTokenIsSameAsDepositToken =
3176
- selectedFeeOption.tokenAddress.toLowerCase() ===
3177
- depositTokenAddress.toLowerCase()
3178
-
3179
- if (feeTokenIsSameAsDepositToken) {
3180
- requiredAmount = requiredAmount + BigInt(selectedFeeOption.amount)
3181
- logger.console.log(
3182
- "[trails-sdk] Fee token matches deposit token, including fee in required approval amount:",
3183
- {
3184
- depositAmount: depositTokenAmount,
3185
- feeAmount: selectedFeeOption.amount,
3186
- feeTokenAddress: selectedFeeOption.tokenAddress,
3187
- depositTokenAddress,
3188
- totalRequired: requiredAmount.toString(),
3189
- },
3190
- )
3191
- } else {
3192
- logger.console.log(
3193
- "[trails-sdk] Fee token differs from deposit token, separate approval will be needed:",
3194
- {
3195
- depositAmount: depositTokenAmount,
3196
- depositTokenAddress,
3197
- feeAmount: selectedFeeOption.amount,
3198
- feeTokenAddress: selectedFeeOption.tokenAddress,
3199
- depositTokenRequired: requiredAmount.toString(),
3200
- },
3201
- )
3202
- }
3203
- }
3204
-
3205
- const needsApproval = await getNeedsIntentEntrypointApproval({
3206
- client: publicClient,
3207
- token: depositTokenAddress as `0x${string}`,
3208
- account: account.address,
3209
- entrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3210
- amount: requiredAmount, // Check if we have enough allowance for this specific transaction
3211
- })
3212
-
3213
- logger.console.log(
3214
- "[trails-sdk] [GASLESS-FLOW] Checking permit requirements",
3215
- {
3216
- userAddress: account.address,
3217
- tokenAddress: depositTokenAddress,
3218
- depositAmount: depositTokenAmount,
3219
- requiredAmount: requiredAmount.toString(),
3220
- intentAddress: depositRecipient,
3221
- chainID: originChainId,
3222
- deadline,
3223
- needsApproval,
3224
- },
3225
- )
3226
-
3227
- // 2. Get permit signature if approval needed for deposit token
3228
- // Note: Fee payment is handled by the backend/relayer, we only need permit for the deposit token
3229
- let permitSignature: string | undefined
3230
- let permitDeadline: number | undefined
3231
-
3232
- if (needsApproval) {
3233
- logger.console.log(
3234
- "[trails-sdk] Getting permit signature for deposit token infinite approval",
3235
- )
3236
-
3237
- // Use infinite approval (maxUint256) so user doesn't need to approve again
3238
- const permitAmount = maxUint256
3239
- logger.console.log(
3240
- "[trails-sdk] Using infinite approval for gasless deposits",
3241
- {
3242
- depositAmount: depositTokenAmount,
3243
- depositTokenAddress,
3244
- permitAmount: permitAmount.toString(),
3245
- },
3246
- )
3247
-
3248
- const permitSig = await getPermitSignature({
3249
- publicClient,
3250
- walletClient,
3251
- signer: account.address,
3252
- spender: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3253
- tokenAddress: depositTokenAddress as `0x${string}`,
3254
- amount: permitAmount, // Infinite approval
3255
- chain,
3256
- deadline: BigInt(deadline),
3257
- })
3258
- permitSignature = permitSig.signature
3259
- permitDeadline = Number(permitSig.deadline)
3260
- logger.console.log(
3261
- "[trails-sdk] Deposit token permit signature obtained for infinite approval",
3262
- )
3263
- }
3264
-
3265
- // 3. Get current nonce for the user
3266
- logger.console.log("[trails-sdk] Getting user nonce")
3267
- const nonce = await getUserNonce({
3268
- publicClient,
3269
- userAddress: account.address,
3270
- intentEntrypoint: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3271
- })
3272
- logger.console.log("[trails-sdk] User nonce:", nonce.toString())
3273
-
3274
- // 4. Get intent signature
3275
- logger.console.log("[trails-sdk] Requesting intent signature via EIP-712")
3276
- // Get fee collector address from fee options response, or use selected fee option's collector
3277
- const feeCollectorAddress = (selectedFeeOption?.feeCollector ||
3278
- feeOptions?.feeCollector) as `0x${string}` | undefined
3279
-
3280
- // Validate that we have a valid fee collector address
3281
- if (!feeCollectorAddress || feeCollectorAddress === zeroAddress) {
3282
- throw new Error(
3283
- "[trails-sdk] Fee collector address not provided by API. Cannot proceed with gasless deposit. " +
3284
- "Please ensure the API is returning feeCollector in the fee options response.",
3285
- )
3286
- }
3287
-
3288
- logger.console.log(
3289
- "[trails-sdk] Using fee collector address:",
3290
- feeCollectorAddress,
3291
- )
3292
-
3293
- const { signature: intentSignature } = await signIntent({
3294
- client: walletClient,
3295
- intentParams: {
3296
- user: account.address,
3297
- token: depositTokenAddress as `0x${string}`,
3298
- amount: BigInt(depositTokenAmount),
3299
- intentAddress: depositRecipient as `0x${string}`,
3300
- deadline: BigInt(deadline),
3301
- chainId: originChainId,
3302
- contractAddress: TRAILS_INTENT_ENTRYPOINT_ADDRESS,
3303
- nonce,
3304
- feeAmount: BigInt(selectedFeeOption?.amount || "0"),
3305
- feeCollector: feeCollectorAddress,
3306
- },
3307
- })
3308
- logger.console.log("[trails-sdk] Intent signature received")
3309
-
3310
- // 5. Call the deposit endpoint with permit support and optional fee
3311
- logger.console.log(
3312
- "[trails-sdk] Calling getIntentEntrypointDeposit with permit enabled",
3313
- { usePermit: needsApproval, hasFee: !!feeOptions },
3314
- )
3315
-
3316
- // selectedFeeOption was already determined at the start of the try block
3317
-
3318
- logger.console.log(
3319
- "[trails-sdk] Calling getIntentEntrypointDeposit with params:",
3320
- {
3321
- userAddress: account.address,
3322
- tokenAddress: depositTokenAddress,
3323
- amount: depositTokenAmount,
3324
- intentAddress: depositRecipient,
3325
- chainID: originChainId,
3326
- deadline,
3327
- usePermit: needsApproval,
3328
- hasPermitSignature: !!permitSignature,
3329
- feeAmount: selectedFeeOption?.amount,
3330
- feeTokenSymbol: selectedFeeOption?.tokenSymbol,
3331
- selectedFeeToken,
3332
- usingSelectedFeeToken: !!selectedFeeToken,
3333
- },
3334
- )
3335
-
3336
- const depositDataResponse = await trailsClient.getIntentEntrypointDeposit({
3337
- params: {
3338
- userAddress: account.address,
3339
- tokenAddress: depositTokenAddress,
3340
- amount: depositTokenAmount,
3341
- intentAddress: depositRecipient,
3342
- chainID: originChainId,
3343
- deadline,
3344
- intentSignature,
3345
- usePermit: needsApproval, // Use permit if approval needed
3346
- permitAmount: needsApproval ? maxUint256.toString() : undefined, // Pass infinite approval amount
3347
- permitSignature,
3348
- permitDeadline,
3349
- feeAmount: selectedFeeOption?.amount || undefined,
3350
- },
3351
- })
3352
-
3353
- const depositData = depositDataResponse.result
3354
- logger.console.log("[trails-sdk] Deposit data received:", {
3355
- depositWalletAddress: depositData.depositWalletAddress,
3356
- entrypointAddress: depositData.entrypointAddress,
3357
- metaTxnId: depositData.metaTxn?.id,
3358
- usedPermit2: needsApproval,
3359
- })
3360
-
3361
- // 5. Send meta transaction via relayer
3362
- logger.console.log("[trails-sdk] Sending meta transaction to relayer")
3363
- const feeQuoteObj = depositData.feeQuote
3364
- ? ({
3365
- _tag: "FeeQuote",
3366
- _quote: { toJSON: () => depositData.feeQuote },
3367
- } as any)
3368
- : undefined
3369
-
3370
- const opHash = await relayerSendMetaTx(
3371
- originRelayer,
3372
- depositData.metaTxn as any,
3373
- [], // No preconditions for gasless deposit
3374
- feeQuoteObj,
3375
- )
3376
- logger.console.log("[trails-sdk] Meta transaction sent, opHash:", opHash)
3377
-
3378
- if (onOriginSend) {
3379
- onOriginSend()
3380
- }
3381
-
3382
- // 6. Wait for transaction receipt
3383
- logger.console.log("[trails-sdk] Waiting for transaction receipt")
3384
- // eslint-disable-next-line no-constant-condition
3385
- while (true) {
3386
- const receipt: any = await getMetaTxStatus(
3387
- originRelayer,
3388
- depositData.metaTxn.id,
3389
- Number(depositData.metaTxn.chainId),
3390
- )
3391
- logger.console.log("[trails-sdk] Meta transaction status:", receipt)
3392
-
3393
- if (receipt?.transactionHash) {
3394
- const metaTxnReceipt = receipt.data?.receipt
3395
- if (!metaTxnReceipt) {
3396
- throw new Error("No meta txn receipt found")
3397
- }
3398
-
3399
- // Get the full transaction receipt
3400
- const txReceipt = await publicClient.getTransactionReceipt({
3401
- hash: receipt.transactionHash as `0x${string}`,
3402
- })
3403
- logger.console.log("[trails-sdk] Transaction receipt:", txReceipt)
3404
- originUserTxReceipt = txReceipt
3405
- break
3406
- }
3407
-
3408
- await new Promise((resolve) => setTimeout(resolve, 1000))
3409
- }
3410
- } catch (error) {
3411
- logger.console.error(
3412
- "[trails-sdk] Error in Intent Entrypoint gasless deposit with permit2:",
3413
- error,
3414
- )
3415
- throw error
3416
- }
3417
-
3418
- return originUserTxReceipt
3419
- }
3420
-
3421
- export async function attemptNonGaslessUserDeposit({
3422
- originTokenAddress,
3423
- firstPreconditionMin,
3424
- onOriginSend,
3425
- publicClient,
3426
- walletClient,
3427
- originChainId,
3428
- chain,
3429
- account,
3430
- fee,
3431
- dryMode,
3432
- sourceTokenPriceUsd,
3433
- destinationTokenPriceUsd,
3434
- swapAmount,
3435
- destinationTokenDecimals,
3436
- sourceTokenDecimals,
3437
- originIntentAddress,
3438
- onTransactionStateChange,
3439
- transactionStates,
3440
- originTokenSymbol,
3441
- destinationTokenSymbol,
3442
- depositAmountUsd,
3443
- }: {
3444
- originTokenAddress: string
3445
- firstPreconditionMin: string
3446
- onOriginSend?: () => void
3447
- publicClient: PublicClient
3448
- walletClient: WalletClient
3449
- originChainId: number
3450
- chain: Chain
3451
- account: Account
3452
- fee: string
3453
- dryMode: boolean
3454
- sourceTokenPriceUsd?: number | null
3455
- destinationTokenPriceUsd?: number | null
3456
- swapAmount: string
3457
- destinationTokenDecimals: number
3458
- sourceTokenDecimals: number
3459
- originIntentAddress: string
3460
- onTransactionStateChange: (transactionStates: TransactionState[]) => void
3461
- transactionStates: TransactionState[]
3462
- originTokenSymbol: string
3463
- destinationTokenSymbol: string
3464
- depositAmountUsd: number
3465
- }): Promise<TransactionReceipt | null> {
3466
- let originUserTxReceipt: TransactionReceipt | null = null
3467
- const usingLIfi = false
3468
- let needsNativeFee = false
3469
-
3470
- if (usingLIfi) {
3471
- needsNativeFee = await getNeedsLifiNativeFee({
3472
- originTokenAddress,
3473
- destinationTokenAmount: swapAmount,
3474
- destinationTokenDecimals,
3475
- sourceTokenDecimals,
3476
- sourceTokenPriceUsd: sourceTokenPriceUsd ?? null,
3477
- destinationTokenPriceUsd: destinationTokenPriceUsd ?? null,
3478
- depositAmount: firstPreconditionMin,
3479
- })
3480
- }
3481
- let nativeFee = parseUnits("0.00005", 18).toString()
3482
- if (originChainId === 137) {
3483
- nativeFee = parseUnits("1.5", 18).toString()
3484
- }
3485
-
3486
- logger.console.log("[trails-sdk] needsNativeFee", needsNativeFee)
3487
-
3488
- // Build origin call params
3489
- const originCallParamsBase = buildCrossChainDepositParams({
3490
- originTokenAddress,
3491
- originIntentAddress,
3492
- depositAmount: firstPreconditionMin,
3493
- fee,
3494
- originChainId,
3495
- chain,
3496
- })
3497
-
3498
- // Estimate gas limit for consistency with actual transaction
3499
- const gasLimit = await estimateGasLimit(
3500
- publicClient,
3501
- {
3502
- account: account.address,
3503
- to: originCallParamsBase.to,
3504
- data: originCallParamsBase.data,
3505
- value: BigInt(originCallParamsBase.value),
3506
- },
3507
- "deposit",
3508
- )
3509
-
3510
- const originCallParams = {
3511
- ...originCallParamsBase,
3512
- gasLimit,
3513
- }
3514
-
3515
- await attemptSwitchChain({
3516
- walletClient,
3517
- desiredChainId: originChainId,
3518
- })
3519
-
3520
- let useSendCalls = false
3521
- const moreThan1Tx = needsNativeFee
3522
-
3523
- if (moreThan1Tx) {
3524
- try {
3525
- // the reason for the timeout is some users experience this call to hang indefinitely on metamask on all chains.
3526
- // not sure why this is happening, but it happens.
3527
- const capabilities = await requestWithTimeout<Record<string, any>>(
3528
- walletClient,
3529
- [
3530
- {
3531
- method: "wallet_getCapabilities",
3532
- params: [account.address],
3533
- },
3534
- ],
3535
- 10000,
3536
- )
3537
-
3538
- logger.console.log("[trails-sdk] capabilities", capabilities)
3539
-
3540
- // Check if the chain supports atomic transactions
3541
- const chainHex = `0x${originChainId.toString(16)}` as const
3542
- const chainCapabilities = capabilities[chainHex]
3543
- useSendCalls = chainCapabilities?.atomic?.status === "supported"
3544
- } catch (error) {
3545
- logger.console.error("[trails-sdk] Error getting capabilities", error)
3546
- }
3547
- }
3548
-
3549
- if (dryMode) {
3550
- logger.console.log("[trails-sdk] dry mode, skipping send calls")
3551
- }
3552
-
3553
- if (useSendCalls) {
3554
- logger.console.log("[trails-sdk] using sendCalls")
3555
- } else {
3556
- logger.console.log("[trails-sdk] using sendTransaction")
3557
- }
3558
-
3559
- if (useSendCalls) {
3560
- if (!dryMode) {
3561
- const calls: Array<{
3562
- to: `0x${string}`
3563
- data: `0x${string}`
3564
- value?: `0x${string}`
3565
- }> = []
3566
- if (needsNativeFee) {
3567
- calls.push({
3568
- to: originIntentAddress as `0x${string}`,
3569
- data: "0x00",
3570
- value: `0x${BigInt(nativeFee).toString(16)}` as `0x${string}`,
3571
- })
3572
- }
3573
-
3574
- // Add the origin call
3575
- calls.push({
3576
- to: originCallParams.to as `0x${string}`,
3577
- data: originCallParams.data as `0x${string}`,
3578
- value: originCallParams.value
3579
- ? `0x${BigInt(originCallParams.value).toString(16)}`
3580
- : "0x0",
3581
- })
3582
-
3583
- // Update persistent toast before wallet interaction
3584
- updatePersistentToast(
3585
- "Waiting for Confirmation",
3586
- "Please confirm the transaction in your wallet...",
3587
- "info",
3588
- )
3589
-
3590
- // Send the batched call via EIP-7702
3591
- const result = (await walletClient.request({
3592
- method: "wallet_sendCalls",
3593
- params: [
3594
- {
3595
- version: "2.0.0",
3596
- chainId: `0x${originChainId.toString(16)}`,
3597
- atomicRequired: true,
3598
- calls,
3599
- },
3600
- ],
3601
- })) as { requestId: `0x${string}` }
3602
-
3603
- logger.console.log("[trails-sdk] sendCalls result", result)
3604
- const requestId = result.requestId || (result as any).id
3605
-
3606
- // Poll to check if the tx has been submitted
3607
- let txHash: `0x${string}` | undefined
3608
- while (!txHash) {
3609
- const status = (await walletClient.request({
3610
- method: "wallet_getCallsStatus",
3611
- params: [requestId],
3612
- })) as {
3613
- status: "pending" | "submitted" | "failed"
3614
- transactionHash?: `0x${string}`
3615
- error?: string
3616
- }
3617
-
3618
- logger.console.log("[trails-sdk] getCallsStatus result", status)
3619
- const receipt = (status as any)?.receipts?.[0]
3620
-
3621
- if ((status as any).status === 200 && receipt?.transactionHash) {
3622
- txHash = receipt.transactionHash
3623
- break
3624
- } else if ((status as any).status === 500) {
3625
- throw new Error(`Transaction failed: ${status.error}`)
3626
- }
3627
-
3628
- // wait a bit before polling again
3629
- await new Promise((r) => setTimeout(r, 2000))
3630
- }
3631
-
3632
- if (onOriginSend) {
3633
- onOriginSend()
3634
- }
3635
-
3636
- // Update persistent toast after transaction sent
3637
- const chainInfo = getChainInfo(originChainId)
3638
- updatePersistentToast(
3639
- "Transaction Submitted",
3640
- `Waiting for confirmation on ${chainInfo?.name || "chain"}...`,
3641
- "info",
3642
- )
3643
-
3644
- const receipt = await publicClient.waitForTransactionReceipt({
3645
- hash: txHash as `0x${string}`,
3646
- })
3647
- logger.console.log("[trails-sdk] receipt", receipt)
3648
- originUserTxReceipt = receipt
3649
- }
3650
- } else {
3651
- if (!dryMode) {
3652
- if (needsNativeFee) {
3653
- const txHashNativeFee = await sendOriginTransaction(
3654
- account,
3655
- walletClient,
3656
- {
3657
- to: originIntentAddress,
3658
- data: "0x00",
3659
- value: nativeFee,
3660
- chainId: originChainId,
3661
- chain,
3662
- } as any,
3663
- {
3664
- depositTokenAmountUsd: depositAmountUsd?.toString(),
3665
- },
3666
- ) // TODO: Add proper type
3667
- logger.console.log("[trails-sdk] origin tx native fee", txHashNativeFee)
3668
- // Wait for transaction receipt
3669
- const feeReceipt = await publicClient.waitForTransactionReceipt({
3670
- hash: txHashNativeFee,
3671
- })
3672
- logger.console.log("[trails-sdk] nativeFeeReceipt", feeReceipt)
3673
- }
3674
-
3675
- // Show persistent toast for checkout flow
3676
- updatePersistentToast(
3677
- "Payment Started",
3678
- "Waiting for wallet confirmation...",
3679
- "info",
3680
- )
3681
-
3682
- const txHash = await sendOriginTransaction(
3683
- account,
3684
- walletClient,
3685
- originCallParams as any,
3686
- {
3687
- depositTokenAmountUsd: depositAmountUsd?.toString(),
3688
- },
3689
- ) // TODO: Add proper type
3690
- logger.console.log("[trails-sdk] origin tx", txHash)
3691
-
3692
- if (onOriginSend) {
3693
- onOriginSend()
3694
- }
3695
-
3696
- if (transactionStates[0]) {
3697
- transactionStates[0].state = "pending"
3698
-
3699
- try {
3700
- onTransactionStateChange(transactionStates)
3701
- } catch (error) {
3702
- logger.console.error(
3703
- "[trails-sdk] Error calling onTransactionStateChange:",
3704
- error,
3705
- )
3706
- }
3707
- }
3708
-
3709
- // Wait for transaction receipt
3710
- const receipt = await publicClient.waitForTransactionReceipt({
3711
- hash: txHash,
3712
- })
3713
-
3714
- const chainInfo = getChainInfo(originChainId)
3715
- updatePersistentToast(
3716
- "Transfer Confirmed",
3717
- `Your transaction on ${chainInfo?.name || "chain"} has been confirmed`,
3718
- "info",
3719
- )
3720
-
3721
- trackTransactionConfirmed({
3722
- transactionHash: txHash,
3723
- chainId: originChainId,
3724
- userAddress: account.address,
3725
- blockNumber: Number(receipt.blockNumber),
3726
- originIntentAddress,
3727
- originTokenSymbol,
3728
- destinationTokenSymbol,
3729
- depositTokenAmountUsd: depositAmountUsd?.toString(),
3730
- })
3731
-
3732
- logger.console.log("[trails-sdk] receipt", receipt)
3733
- originUserTxReceipt = receipt
3734
- }
3735
- }
3736
-
3737
- return originUserTxReceipt
3738
- }
3739
-
3740
- async function attemptUserDepositTx({
3741
- originTokenAddress,
3742
- gasless,
3743
- paymasterUrl,
3744
- chain,
3745
- account,
3746
- originRelayer,
3747
- firstPreconditionMin,
3748
- originIntentAddress,
3749
- onOriginSend,
3750
- publicClient,
3751
- walletClient,
3752
- destinationTokenDecimals,
3753
- sourceTokenDecimals,
3754
- fee,
3755
- dryMode,
3756
- sourceTokenPriceUsd,
3757
- destinationTokenPriceUsd,
3758
- swapAmount,
3759
- onTransactionStateChange,
3760
- transactionStates,
3761
- fundMethod,
3762
- originTokenSymbol,
3763
- destinationTokenSymbol,
3764
- depositAmountUsd,
3765
- feeOptions,
3766
- trailsClient,
3767
- selectedFeeToken,
3768
- walletId,
3769
- }: {
3770
- originTokenAddress: string
3771
- gasless: boolean
3772
- paymasterUrl?: string
3773
- chain: Chain
3774
- account: Account
3775
- originRelayer: Relayer.RpcRelayer
3776
- firstPreconditionMin: string
3777
- originIntentAddress: string
3778
- onOriginSend?: () => void
3779
- publicClient: PublicClient
3780
- walletClient: WalletClient
3781
- destinationTokenDecimals: number
3782
- sourceTokenDecimals: number
3783
- swapAmount: string
3784
- dryMode: boolean
3785
- sourceTokenPriceUsd: number | null
3786
- destinationTokenPriceUsd: number | null
3787
- fee: string
3788
- onTransactionStateChange: (transactionStates: TransactionState[]) => void
3789
- transactionStates: TransactionState[]
3790
- fundMethod?: string
3791
- originTokenSymbol: string
3792
- destinationTokenSymbol: string
3793
- depositAmountUsd: number
3794
- feeOptions?: any
3795
- trailsClient: TrailsAPIClient
3796
- selectedFeeToken?: any
3797
- walletId?: string
3798
- }): Promise<TransactionReceipt | null> {
3799
- let originUserTxReceipt: TransactionReceipt | null = null
3800
- const originChainId = chain.id
3801
-
3802
- logger.console.log(
3803
- "[trails-sdk] attemptUserDepositTx called with fundMethod:",
3804
- fundMethod,
3805
- )
3806
-
3807
- // Skip wallet deposit if fund method is qr-code
3808
- if (fundMethod === "qr-code" || fundMethod === "exchange") {
3809
- logger.console.log(
3810
- "[trails-sdk] Skipping wallet deposit for QR code mode - fundMethod is:",
3811
- fundMethod,
3812
- )
3813
- return null
3814
- }
3815
-
3816
- const doGasless = getDoGasless(
3817
- originTokenAddress,
3818
- gasless,
3819
- feeOptions,
3820
- selectedFeeToken,
3821
- walletId,
3822
- )
3823
- logger.console.log(
3824
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] doGasless check results:",
3825
- {
3826
- doGasless,
3827
- paymasterUrl,
3828
- hasFeeOptions: !!feeOptions,
3829
- feeOptionsCount: feeOptions?.feeOptions?.length || 0,
3830
- selectedFeeToken,
3831
- selectedFeeTokenSet:
3832
- selectedFeeToken !== null && selectedFeeToken !== undefined,
3833
- originTokenAddress,
3834
- gasless,
3835
- isNotNative: originTokenAddress !== zeroAddress,
3836
- },
3837
- )
3838
- if (doGasless || paymasterUrl) {
3839
- logger.console.log(
3840
- "[trails-sdk] [GASLESS-FLOW] Entering gasless deposit flow",
3841
- {
3842
- doGasless,
3843
- hasPaymasterUrl: !!paymasterUrl,
3844
- paymasterUrl,
3845
- },
3846
- )
3847
- try {
3848
- originUserTxReceipt = await attemptGaslessDeposit({
3849
- paymasterUrl,
3850
- depositTokenAddress: originTokenAddress,
3851
- depositTokenAmount: firstPreconditionMin,
3852
- depositRecipient: originIntentAddress,
3853
- onOriginSend,
3854
- walletClient,
3855
- chain,
3856
- account,
3857
- trailsClient,
3858
- originRelayer,
3859
- feeOptions: feeOptions,
3860
- selectedFeeToken: selectedFeeToken,
3861
- })
3862
- } catch (error) {
3863
- logger.console.log("[trails-sdk] gassless attempt failed", error)
3864
- // In strict gasless mode, re-throw error instead of falling back
3865
- if (gasless) {
3866
- throw error
3867
- }
3868
- }
3869
- }
3870
-
3871
- // If gasless attempt failed, try to send a regular transaction
3872
- if (!originUserTxReceipt && !gasless) {
3873
- originUserTxReceipt = await attemptNonGaslessUserDeposit({
3874
- originTokenAddress,
3875
- firstPreconditionMin,
3876
- originIntentAddress,
3877
- onOriginSend,
3878
- publicClient,
3879
- walletClient,
3880
- originChainId,
3881
- chain,
3882
- account,
3883
- fee,
3884
- dryMode,
3885
- sourceTokenPriceUsd,
3886
- destinationTokenPriceUsd,
3887
- swapAmount,
3888
- destinationTokenDecimals,
3889
- sourceTokenDecimals,
3890
- onTransactionStateChange,
3891
- transactionStates,
3892
- originTokenSymbol,
3893
- destinationTokenSymbol,
3894
- depositAmountUsd,
3895
- })
3896
- }
3897
-
3898
- return originUserTxReceipt
3899
- }
3900
-
3901
- export function getDoGasless(
3902
- originTokenAddress: string,
3903
- gasless: boolean,
3904
- feeOptions?: any,
3905
- selectedFeeToken?: any,
3906
- walletId?: string,
3907
- ): boolean {
3908
- // Don't use gasless flow for sequence-waas wallet
3909
- if (walletId === "sequence-waas") {
3910
- logger.console.log(
3911
- "[trails-sdk] [GASLESS-FLOW] getDoGasless: Skipping gasless flow for sequence-waas wallet",
3912
- )
3913
- return false
3914
- }
3915
-
3916
- const hasFeeOptions = Boolean(feeOptions && feeOptions.feeOptions?.length > 0)
3917
-
3918
- // Important: The UI passes selectedFeeToken in these states:
3919
- // - null: User explicitly chose "Pay with native gas"
3920
- // - {object}: User selected a fee token OR it was auto-selected
3921
- // - undefined: Should not happen (initial state auto-selects if options exist)
3922
-
3923
- // If selectedFeeToken is null, user explicitly chose native gas
3924
- if (selectedFeeToken === null) {
3925
- logger.console.log(
3926
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: User explicitly selected native gas (null)",
3927
- )
3928
- return false
3929
- }
3930
-
3931
- // If selectedFeeToken is undefined and no fee options, can't do gasless
3932
- if (!selectedFeeToken && !hasFeeOptions) {
3933
- logger.console.log(
3934
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: No fee token selected and no fee options available",
3935
- )
3936
- return false
3937
- }
3938
-
3939
- // If selectedFeeToken is undefined but fee options exist, use first ERC20 option
3940
- let effectiveFeeToken = selectedFeeToken
3941
- if (!effectiveFeeToken && hasFeeOptions) {
3942
- const firstFeeOption = feeOptions.feeOptions[0]
3943
-
3944
- // Check if first option is native gas
3945
- const isFirstOptionNative =
3946
- firstFeeOption?.tokenAddress === zeroAddress ||
3947
- firstFeeOption?.tokenAddress?.toLowerCase() === zeroAddress ||
3948
- firstFeeOption?.isNative === true
3949
-
3950
- if (!isFirstOptionNative && firstFeeOption?.tokenAddress) {
3951
- // First fee option is ERC20, use it
3952
- effectiveFeeToken = firstFeeOption
3953
- logger.console.log(
3954
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: No fee token selected, using first ERC20 fee option",
3955
- {
3956
- feeOption: effectiveFeeToken,
3957
- },
3958
- )
3959
- } else {
3960
- logger.console.log(
3961
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: First fee option is native gas, skipping gasless",
3962
- )
3963
- return false
3964
- }
3965
- }
3966
-
3967
- // Check if the effective fee token is native gas
3968
- const isNativeGasFee =
3969
- !effectiveFeeToken ||
3970
- effectiveFeeToken.tokenAddress === zeroAddress ||
3971
- effectiveFeeToken.tokenAddress?.toLowerCase() === zeroAddress ||
3972
- effectiveFeeToken.isNative === true
3973
-
3974
- // Don't use gasless if origin token is native (sending ETH)
3975
- if (originTokenAddress === zeroAddress) {
3976
- logger.console.log(
3977
- "[trails-sdk] [GASLESS-FLOW] getDoGasless: Origin token is native, skipping gasless",
3978
- )
3979
- return false
3980
- }
3981
-
3982
- // Don't use gasless if fee token is native
3983
- if (isNativeGasFee) {
3984
- logger.console.log(
3985
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless: Fee token is native gas, skipping gasless",
3986
- {
3987
- effectiveFeeToken,
3988
- },
3989
- )
3990
- return false
3991
- }
3992
-
3993
- // Don't use gasless if disabled
3994
- if (!gasless) {
3995
- logger.console.log(
3996
- "[trails-sdk] [GASLESS-FLOW] getDoGasless: Gasless disabled",
3997
- )
3998
- return false
3999
- }
4000
-
4001
- // All conditions met, use gasless with ERC20 fee token
4002
- logger.console.log(
4003
- "[trails-sdk] [GASLESS-FLOW] [FEE-SELECT] getDoGasless decision: Using gasless",
4004
- {
4005
- originTokenAddress,
4006
- gasless,
4007
- hasFeeOptions,
4008
- selectedFeeToken,
4009
- effectiveFeeToken,
4010
- feeOptionsCount: feeOptions?.feeOptions?.length || 0,
4011
- },
4012
- )
4013
-
4014
- return true
4015
- }
4016
-
4017
- function getTransactionStateFromReceipt(
4018
- receipt: TransactionReceipt | MetaTxnReceipt,
4019
- chainId: number,
4020
- label: string = "Transaction",
4021
- ): TransactionState {
4022
- let txHash: string = ""
4023
- let state: TransactionStateStatus = "pending"
4024
- let blockNumber: number = 0
4025
- if ("transactionHash" in receipt) {
4026
- txHash = receipt.transactionHash
4027
- state = receipt.status === "success" ? "confirmed" : "failed"
4028
- blockNumber = Number(receipt.blockNumber)
4029
- } else if ("txnHash" in receipt) {
4030
- txHash = receipt.txnHash
4031
- state = receipt.status === "SUCCEEDED" ? "confirmed" : "failed"
4032
- blockNumber = Number(receipt.blockNumber)
4033
- }
4034
-
4035
- return {
4036
- transactionHash: txHash,
4037
- explorerUrl: getExplorerUrl({ txHash, chainId }),
4038
- chainId,
4039
- blockNumber,
4040
- state,
4041
- label,
4042
- }
4043
- }
4044
-
4045
- async function sendMetaTxAndWaitForReceipt({
4046
- metaTx,
4047
- relayer,
4048
- precondition,
4049
- feeQuote,
4050
- }: {
4051
- metaTx: MetaTxn
4052
- relayer: Relayer.RpcRelayer
4053
- precondition: IntentPrecondition | null
4054
- feeQuote?: string
4055
- }): Promise<{
4056
- waitForReceipt: (abortSignal?: AbortSignal) => Promise<MetaTxnReceipt | null>
4057
- }> {
4058
- try {
4059
- logger.console.log("[trails-sdk] metaTx", metaTx)
4060
- trackRelayerCallStarted({
4061
- walletAddress: metaTx.walletAddress as `0x${string}`,
4062
- contractAddress: metaTx.contract as `0x${string}`,
4063
- chainId: Number(metaTx.chainId),
4064
- })
4065
- const feeQuoteObj = feeQuote
4066
- ? ({ _tag: "FeeQuote", _quote: { toJSON: () => feeQuote } } as any)
4067
- : undefined
4068
- logger.console.log("[trails-sdk] feeQuote", feeQuoteObj)
4069
- const opHash = await relayerSendMetaTx(
4070
- relayer,
4071
- metaTx,
4072
- precondition ? [precondition] : ([] as IntentPrecondition[]),
4073
- feeQuoteObj,
4074
- )
4075
- logger.console.log("[trails-sdk] opHash", opHash)
4076
-
4077
- trackRelayerCallCompleted({
4078
- walletAddress: metaTx.walletAddress as `0x${string}`,
4079
- contractAddress: metaTx.contract as `0x${string}`,
4080
- chainId: Number(metaTx.chainId),
4081
- })
4082
- } catch (error) {
4083
- trackRelayerCallError({
4084
- walletAddress: metaTx.walletAddress as `0x${string}`,
4085
- contractAddress: metaTx.contract as `0x${string}`,
4086
- chainId: Number(metaTx.chainId),
4087
- error: error instanceof Error ? error.message : "Unknown error",
4088
- })
4089
- throw error
4090
- }
4091
-
4092
- return {
4093
- waitForReceipt: async (abortSignal?: AbortSignal) => {
4094
- let originMetaTxnReceipt: MetaTxnReceipt | null = null
4095
-
4096
- // eslint-disable-next-line no-constant-condition
4097
- while (true) {
4098
- // Check if we should abort
4099
- if (abortSignal?.aborted) {
4100
- logger.console.log(
4101
- "[trails-sdk] Aborting meta transaction polling due to abort signal",
4102
- )
4103
- return null
4104
- }
4105
-
4106
- logger.console.log(
4107
- "[trails-sdk] polling status",
4108
- metaTx.id as `0x${string}`,
4109
- metaTx.chainId.toString(),
4110
- )
4111
- const receipt: any = await getMetaTxStatus(
4112
- relayer,
4113
- metaTx.id,
4114
- Number(metaTx.chainId),
4115
- )
4116
- logger.console.log("[trails-sdk] status", receipt)
4117
- if (receipt?.transactionHash) {
4118
- originMetaTxnReceipt = receipt.data?.receipt
4119
- if (!originMetaTxnReceipt) {
4120
- throw new Error("No meta txn receipt found")
4121
- }
4122
- break
4123
- }
4124
- await new Promise((resolve) => setTimeout(resolve, 1000))
4125
- }
4126
-
4127
- return originMetaTxnReceipt
4128
- },
4129
- }
4130
- }
4131
-
4132
- /**
4133
- * Check if the account has enough balance for the deposit amount
4134
- */
4135
- async function checkAccountBalance({
4136
- account,
4137
- tokenAddress,
4138
- depositAmount,
4139
- publicClient,
4140
- }: {
4141
- account: Account
4142
- tokenAddress: string
4143
- depositAmount: string
4144
- publicClient: PublicClient
4145
- }): Promise<{
4146
- hasEnoughBalance: boolean
4147
- balance: bigint
4148
- requiredAmount: bigint
4149
- balanceFormatted: string
4150
- requiredAmountFormatted: string
4151
- balanceError: Error | null
4152
- }> {
4153
- try {
4154
- let balance: bigint
4155
-
4156
- if (tokenAddress === zeroAddress) {
4157
- // Native token balance
4158
- balance = await publicClient.getBalance({ address: account.address })
4159
- } else {
4160
- // ERC20 token balance
4161
- balance = await publicClient.readContract({
4162
- address: tokenAddress as `0x${string}`,
4163
- abi: erc20Abi,
4164
- functionName: "balanceOf",
4165
- args: [account.address],
4166
- })
4167
- }
4168
-
4169
- const requiredAmount = BigInt(depositAmount)
4170
-
4171
- logger.console.log("[trails-sdk] balance", balance)
4172
- logger.console.log("[trails-sdk] requiredAmount", requiredAmount)
4173
- const hasEnoughBalance = balance >= requiredAmount
4174
- logger.console.log("[trails-sdk] hasEnoughBalance", hasEnoughBalance)
4175
-
4176
- let balanceFormatted = ""
4177
- let requiredAmountFormatted = ""
4178
- if (tokenAddress === zeroAddress) {
4179
- balanceFormatted = formatUnits(balance, 18)
4180
- requiredAmountFormatted = formatUnits(requiredAmount, 18)
4181
- } else {
4182
- // ERC20 token balance
4183
- const decimals = await publicClient.readContract({
4184
- address: tokenAddress as `0x${string}`,
4185
- abi: erc20Abi,
4186
- functionName: "decimals",
4187
- })
4188
- if (!decimals) {
4189
- throw new Error("Decimals not found")
4190
- }
4191
- balanceFormatted = formatUnits(balance, decimals)
4192
- requiredAmountFormatted = formatUnits(requiredAmount, decimals)
4193
- }
4194
-
4195
- let balanceError = null
4196
- if (!hasEnoughBalance) {
4197
- balanceError = new InsufficientBalanceError(
4198
- `Insufficient balance: Need ${formatAmount(requiredAmountFormatted)} ${tokenAddress} for ${account.address} on ${publicClient.chain?.name} but only have ${formatAmount(balanceFormatted)} ${tokenAddress}`,
4199
- )
4200
- }
4201
-
4202
- return {
4203
- hasEnoughBalance,
4204
- balance,
4205
- balanceFormatted,
4206
- requiredAmount,
4207
- requiredAmountFormatted,
4208
- balanceError,
4209
- }
4210
- } catch (error) {
4211
- logger.console.error("[trails-sdk] Error checking account balance:", error)
4212
- return {
4213
- hasEnoughBalance: false,
4214
- balance: BigInt(0),
4215
- balanceFormatted: "0",
4216
- requiredAmount: BigInt(0),
4217
- requiredAmountFormatted: "0",
4218
- balanceError: error instanceof Error ? error : null,
4219
- }
4220
- }
4221
- }
4222
-
4223
- // ETH fee required by some bridges for low token amounts
4224
- // TODO: update backend API to return the native fee requirement, if any
4225
- function getNeedsLifiNativeFee({
4226
- originTokenAddress,
4227
- destinationTokenAmount,
4228
- destinationTokenDecimals,
4229
- sourceTokenDecimals,
4230
- sourceTokenPriceUsd,
4231
- destinationTokenPriceUsd,
4232
- depositAmount,
4233
- }: {
4234
- originTokenAddress: string
4235
- destinationTokenAmount: string
4236
- destinationTokenDecimals: number
4237
- sourceTokenDecimals: number
4238
- sourceTokenPriceUsd: number | null
4239
- destinationTokenPriceUsd: number | null
4240
- depositAmount: string
4241
- }): boolean {
4242
- let needsNativeFee = false
4243
- if (
4244
- originTokenAddress !== zeroAddress &&
4245
- sourceTokenPriceUsd &&
4246
- destinationTokenPriceUsd &&
4247
- depositAmount &&
4248
- destinationTokenDecimals !== undefined &&
4249
- sourceTokenDecimals !== undefined
4250
- ) {
4251
- // Convert from wei to token units using formatUnits
4252
- const destinationAmount = Number(
4253
- formatUnits(BigInt(destinationTokenAmount), destinationTokenDecimals),
4254
- )
4255
- const depositAmountFormatted = Number(
4256
- formatUnits(BigInt(depositAmount), sourceTokenDecimals),
4257
- )
4258
- logger.console.log("[trails-sdk] destinationAmount", destinationAmount)
4259
- logger.console.log(
4260
- "[trails-sdk] depositAmountFormatted",
4261
- depositAmountFormatted,
4262
- )
4263
- const destinationAmountUsd = calcAmountUsdPrice({
4264
- amount: destinationAmount,
4265
- usdPrice: destinationTokenPriceUsd,
4266
- })
4267
- const depositAmountUsd = calcAmountUsdPrice({
4268
- amount: depositAmountFormatted,
4269
- usdPrice: sourceTokenPriceUsd,
4270
- })
4271
- const diff = depositAmountUsd - destinationAmountUsd
4272
- logger.console.log(
4273
- "[trails-sdk] destinationAmountUsd",
4274
- destinationAmountUsd,
4275
- "[trails-sdk] depositAmountUsd",
4276
- depositAmountUsd,
4277
- "[trails-sdk] diff",
4278
- diff,
4279
- )
4280
- if (diff >= 0 && diff <= 0.02) {
4281
- needsNativeFee = true
4282
- }
4283
- }
4284
-
4285
- return needsNativeFee
4286
- }
4287
-
4288
- export type UseQuoteProps = {
4289
- walletClient?: any // TODO: fix this, has to do with viem/wagmi versions
4290
- fromTokenAddress?: string | null
4291
- fromChainId?: number | null
4292
- toTokenAddress?: string | null
4293
- toChainId?: number | null
4294
- toCalldata?: string | null
4295
- swapAmount?: string | bigint
4296
- toRecipient?: string | null
4297
- tradeType?: TradeType | null
4298
- slippageTolerance?: string | number | null
4299
- onStatusUpdate?: ((transactionStates: TransactionState[]) => void) | null
4300
- quoteProvider?: string | null
4301
- gasless?: boolean
4302
- paymasterUrl?: string
4303
- selectedFeeToken?: {
4304
- tokenAddress: string
4305
- tokenSymbol?: string
4306
- } | null
4307
- }
4308
-
4309
- export type SwapReturn = {
4310
- originTransaction: {
4311
- transactionHash?: string | null
4312
- explorerUrl?: string | null
4313
- receipt: TransactionReceipt | MetaTxnReceipt | null
4314
- }
4315
- destinationTransaction: {
4316
- transactionHash?: string | null
4317
- explorerUrl?: string | null
4318
- receipt: MetaTxnReceipt | null
4319
- }
4320
- totalCompletionSeconds?: number
4321
- }
4322
-
4323
- export type QuoteProviderInfo = {
4324
- id: string
4325
- name: string
4326
- url: string
4327
- }
4328
-
4329
- export type Quote = {
4330
- fromAmount: string
4331
- fromAmountMin: string
4332
- toAmount: string
4333
- toAmountMin: string
4334
- originToken: SupportedToken
4335
- destinationToken: SupportedToken
4336
- originChain: Chain
4337
- destinationChain: Chain
4338
- fees: PrepareSendFees
4339
- slippageTolerance: string
4340
- priceImpact: string
4341
- completionEstimateSeconds: number
4342
- transactionStates?: TransactionState[]
4343
- originTokenRate?: string
4344
- quoteProvider?: QuoteProviderInfo | null
4345
- destinationTokenRate?: string
4346
- fromAmountUsdDisplay?: string
4347
- toAmountUsdDisplay?: string
4348
- gasCostUsd?: number
4349
- gasCostUsdDisplay?: string
4350
- gasCost?: string
4351
- gasCostFormatted?: string
4352
- }
4353
-
4354
- export type UseQuoteReturn = {
4355
- quote: Quote | null
4356
- swap: (() => Promise<SwapReturn | null>) | null
4357
- isLoadingQuote: boolean
4358
- quoteError: unknown
4359
- refetchQuote: () => void
4360
- }
4361
-
4362
- export function useQuote({
4363
- walletClient,
4364
- fromTokenAddress,
4365
- fromChainId,
4366
- toTokenAddress,
4367
- toChainId,
4368
- swapAmount,
4369
- tradeType,
4370
- toRecipient,
4371
- toCalldata,
4372
- slippageTolerance,
4373
- onStatusUpdate,
4374
- quoteProvider,
4375
- gasless,
4376
- paymasterUrl,
4377
- selectedFeeToken,
4378
- relayerEnv,
4379
- nodeGatewayEnv,
4380
- }: Partial<
4381
- UseQuoteProps & { relayerEnv?: RelayerEnv; nodeGatewayEnv?: SequenceEnv }
4382
- > = {}): UseQuoteReturn {
4383
- // Set node gateway environment override for this quote session
4384
- if (nodeGatewayEnv) {
4385
- ;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
4386
- }
4387
-
4388
- const apiClient = useAPIClient()
4389
- const trailsClient = useTrailsClient()
4390
- const { getRelayer } = useRelayers({
4391
- env: relayerEnv || (getSequenceEnv() as RelayerEnv),
4392
- })
4393
- const indexerGatewayClient = useIndexerGatewayClient()
4394
-
4395
- const { supportedTokens } = useSupportedTokens()
4396
-
4397
- const { data, isLoading, error, refetch } = useQuery({
4398
- queryKey: [
4399
- "quote",
4400
- fromTokenAddress,
4401
- fromChainId,
4402
- toTokenAddress,
4403
- toChainId,
4404
- swapAmount?.toString(),
4405
- toRecipient,
4406
- toCalldata,
4407
- tradeType,
4408
- slippageTolerance,
4409
- quoteProvider,
4410
- ],
4411
- queryFn: async () => {
4412
- try {
4413
- if (
4414
- !walletClient ||
4415
- !apiClient ||
4416
- !trailsClient ||
4417
- !fromTokenAddress ||
4418
- !toTokenAddress ||
4419
- !swapAmount ||
4420
- !toRecipient ||
4421
- !fromChainId ||
4422
- !toChainId ||
4423
- !indexerGatewayClient
4424
- ) {
4425
- return null
4426
- }
4427
-
4428
- // Get token balance using async method
4429
- const { balances } = await getTokenBalancesWithPrices({
4430
- account: walletClient.account!.address,
4431
- indexerGatewayClient,
4432
- apiClient,
4433
- })
4434
-
4435
- const originTokenBalance = balances.find(
4436
- (b) =>
4437
- b.chainId === fromChainId &&
4438
- (b.contractAddress?.toLowerCase() ===
4439
- fromTokenAddress.toLowerCase() ||
4440
- (!b.contractAddress && fromTokenAddress === zeroAddress)),
4441
- )
4442
-
4443
- const originTokenBalanceAmount = originTokenBalance?.balance ?? "0"
4444
- const destinationRelayer = getRelayer(toChainId)
4445
- const originRelayer = getRelayer(fromChainId)
4446
-
4447
- // Note: Disable this check for now to allow fetching a quote even when the origin balance is zero
4448
- // if (originTokenBalanceAmount === "0") {
4449
- // return null
4450
- // }
4451
-
4452
- // logger.console.log("supportedTokens", supportedTokens)
4453
-
4454
- const originToken = supportedTokens?.find(
4455
- (token) =>
4456
- token.contractAddress?.toLowerCase() ===
4457
- fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
4458
- )
4459
- const destinationToken = supportedTokens?.find(
4460
- (token) =>
4461
- token.contractAddress?.toLowerCase() ===
4462
- toTokenAddress?.toLowerCase() && token.chainId === toChainId,
4463
- )
4464
-
4465
- const sourceTokenDecimals = originToken?.decimals
4466
- if (!sourceTokenDecimals) {
4467
- logger.console.error(
4468
- "[trails-sdk] [useQuote] Missing source token decimals:",
4469
- {
4470
- originToken,
4471
- fromTokenAddress,
4472
- fromChainId,
4473
- },
4474
- )
4475
- throw new Error("Source token decimals not found")
4476
- }
4477
- const destinationTokenDecimals = destinationToken?.decimals
4478
- if (!destinationTokenDecimals) {
4479
- logger.console.error(
4480
- "[trails-sdk] Missing destination token decimals:",
4481
- {
4482
- destinationToken,
4483
- toTokenAddress,
4484
- toChainId,
4485
- },
4486
- )
4487
- throw new Error("Destination token decimals not found")
4488
- }
4489
- const destinationTokenSymbol = destinationToken?.symbol ?? ""
4490
- const originTokenSymbol = originToken?.symbol ?? ""
4491
-
4492
- const options = {
4493
- account: walletClient.account!,
4494
- originTokenAddress: fromTokenAddress,
4495
- originChainId: fromChainId,
4496
- originTokenBalance: originTokenBalanceAmount,
4497
- destinationChainId: toChainId,
4498
- recipient: toRecipient,
4499
- destinationTokenAddress: toTokenAddress,
4500
- swapAmount: swapAmount.toString(),
4501
- tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
4502
- originTokenSymbol: originTokenSymbol,
4503
- destinationTokenSymbol: destinationTokenSymbol,
4504
- destinationCalldata: toCalldata as string,
4505
- client: walletClient,
4506
- apiClient,
4507
- trailsClient,
4508
- originRelayer,
4509
- destinationRelayer,
4510
- sourceTokenDecimals,
4511
- destinationTokenDecimals,
4512
- fee: "0",
4513
- dryMode: false,
4514
- onTransactionStateChange: onStatusUpdate ?? (() => {}),
4515
- slippageTolerance: slippageTolerance?.toString(),
4516
- quoteProvider: quoteProvider,
4517
- gasless: gasless ?? false,
4518
- paymasterUrl: paymasterUrl,
4519
- selectedFeeToken: selectedFeeToken ?? undefined,
4520
- }
4521
-
4522
- logger.console.log("[trails-sdk] options", options)
4523
-
4524
- const { quote: prepareSendQuote, send } = await prepareSend(options)
4525
-
4526
- const quote = {
4527
- fromAmount: prepareSendQuote.originAmount,
4528
- toAmount: prepareSendQuote.destinationAmount,
4529
- fromAmountMin: prepareSendQuote.originAmountMin,
4530
- toAmountMin: prepareSendQuote.destinationAmountMin,
4531
- originToken: prepareSendQuote.originToken,
4532
- destinationToken: prepareSendQuote.destinationToken,
4533
- originChain: prepareSendQuote.originChain,
4534
- destinationChain: prepareSendQuote.destinationChain,
4535
- fees: prepareSendQuote.fees,
4536
- priceImpact: prepareSendQuote.priceImpact,
4537
- completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
4538
- slippageTolerance: prepareSendQuote.slippageTolerance,
4539
- transactionStates: prepareSendQuote.transactionStates,
4540
- originTokenRate: prepareSendQuote.originTokenRate,
4541
- destinationTokenRate: prepareSendQuote.destinationTokenRate,
4542
- quoteProvider: prepareSendQuote.quoteProvider,
4543
- fromAmountUsdDisplay:
4544
- prepareSendQuote.originAmountUsdDisplay ?? undefined,
4545
- toAmountUsdDisplay:
4546
- prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
4547
- gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
4548
- gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
4549
- gasCost: prepareSendQuote.gasCost ?? undefined,
4550
- gasCostFormatted: prepareSendQuote.gasCostFormatted ?? undefined,
4551
- }
4552
-
4553
- const swap = async (): Promise<SwapReturn> => {
4554
- const {
4555
- originUserTxReceipt,
4556
- destinationMetaTxnReceipt,
4557
- totalCompletionSeconds,
4558
- } = await send({
4559
- selectedFeeToken: selectedFeeToken ?? undefined,
4560
- })
4561
-
4562
- return {
4563
- originTransaction: {
4564
- transactionHash: originUserTxReceipt?.transactionHash,
4565
- explorerUrl: getExplorerUrl({
4566
- txHash: originUserTxReceipt?.transactionHash as string,
4567
- chainId: fromChainId,
4568
- }),
4569
- receipt: originUserTxReceipt,
4570
- },
4571
- destinationTransaction: {
4572
- transactionHash: destinationMetaTxnReceipt?.txnHash,
4573
- explorerUrl: getExplorerUrl({
4574
- txHash: destinationMetaTxnReceipt?.txnHash as string,
4575
- chainId: toChainId,
4576
- }),
4577
- receipt: destinationMetaTxnReceipt,
4578
- },
4579
- totalCompletionSeconds,
4580
- }
4581
- }
4582
-
4583
- return {
4584
- quote,
4585
- swap,
4586
- }
4587
- } catch (error) {
4588
- logger.console.error(
4589
- "[trails-sdk] [useQuote] Error getting quote:",
4590
- error,
4591
- )
4592
- throw getFullErrorMessage(error)
4593
- }
4594
- },
4595
- // Prevent unnecessary refetching
4596
- enabled: Boolean(
4597
- walletClient &&
4598
- apiClient &&
4599
- fromTokenAddress &&
4600
- toTokenAddress &&
4601
- swapAmount &&
4602
- toRecipient &&
4603
- fromChainId &&
4604
- toChainId &&
4605
- indexerGatewayClient,
4606
- ),
4607
- staleTime: 30 * 1000, // Consider data fresh for 30 seconds
4608
- refetchOnWindowFocus: false, // Don't refetch when window regains focus
4609
- refetchOnMount: false, // Don't refetch on component remount if data exists
4610
- refetchInterval: false, // Disable automatic polling
4611
- retry: 2, // Limit retry attempts
4612
- refetchOnReconnect: true, // Refetch when network reconnects
4613
- })
4614
-
4615
- return {
4616
- quote: data?.quote || null,
4617
- swap: data?.swap || null,
4618
- isLoadingQuote: isLoading,
4619
- quoteError: error,
4620
- refetchQuote: () => refetch(),
4621
- }
4622
- }
4623
-
4624
- export function getFeesFromIntent(
4625
- intent: GetIntentCallsPayloadsReturn,
4626
- {
4627
- tradeType,
4628
- fromAmountUsd,
4629
- toAmountUsd,
4630
- }: { tradeType: TradeType; fromAmountUsd: number; toAmountUsd: number },
4631
- ): PrepareSendFees {
4632
- const totalFeeAmountUsd = intent.payloads?.trailsFee?.totalFeeUSD ?? 0
4633
- const totalFeeAmountUsdDisplay = formatUsdAmountDisplay(totalFeeAmountUsd)
4634
-
4635
- logger.console.log("[trails-sdk] getFeesFromIntent", {
4636
- tradeType,
4637
- fromAmountUsd,
4638
- toAmountUsd,
4639
- totalFeeAmountUsd,
4640
- totalFeeAmountUsdDisplay,
4641
- })
4642
-
4643
- return {
4644
- feeTokenAddress: intent.payloads.trailsFee?.feeToken ?? zeroAddress,
4645
- totalFeeAmount: intent.payloads.trailsFee?.totalFeeAmount ?? "0",
4646
- totalFeeAmountUsd: totalFeeAmountUsd.toString(),
4647
- totalFeeAmountUsdDisplay,
4648
- }
4649
- }
4650
-
4651
- export function getSlippageToleranceFromIntent(
4652
- intent: GetIntentCallsPayloadsReturn,
4653
- ): string {
4654
- return intent.payloads.quote?.maxSlippage?.toString() ?? "0"
4655
- }
4656
-
4657
- export function getPriceImpactFromIntent(
4658
- intent: GetIntentCallsPayloadsReturn,
4659
- ): string {
4660
- return intent.payloads?.quote?.priceImpact?.toString() ?? "0"
4661
- }
4662
-
4663
- export function getPriceImpactUsdFromIntent(
4664
- intent: GetIntentCallsPayloadsReturn,
4665
- ): string {
4666
- // Temporary type assertion until API types are regenerated
4667
- return (intent.payloads?.quote as any)?.priceImpactUsd?.toString() ?? "0"
4668
- }
4669
-
4670
- export function getFeesFromRelaySdkQuote(quote: RelayQuote): PrepareSendFees {
4671
- const totalFeeAmountUsd = quote?.fees?.relayer?.amount ?? 0
4672
- const totalFeeAmountUsdDisplay = formatUsdAmountDisplay(totalFeeAmountUsd)
4673
- return {
4674
- feeTokenAddress: quote?.fees?.relayer?.currency?.address ?? zeroAddress,
4675
- totalFeeAmount: quote?.fees?.relayer?.amount ?? "0",
4676
- totalFeeAmountUsd: totalFeeAmountUsd.toString(),
4677
- totalFeeAmountUsdDisplay,
4678
- }
4679
- }
4680
-
4681
- export function getSlippageToleranceFromRelaySdkQuote(
4682
- quote: RelayQuote,
4683
- ): string {
4684
- return (
4685
- Number(quote?.details?.slippageTolerance?.origin?.percent ?? "0") / 100
4686
- ).toString()
4687
- }
4688
-
4689
- export function getPriceImpactFromRelaySdkQuote(quote: RelayQuote): string {
4690
- return (quote?.details?.swapImpact?.percent ?? 0).toString()
4691
- }
4692
-
4693
- export function getPriceImpactUsdFromRelaySdkQuote(quote: RelayQuote): string {
4694
- return (quote?.details?.swapImpact?.usd ?? 0).toString()
4695
- }
4696
-
4697
- export function getZeroFees(): PrepareSendFees {
4698
- return {
4699
- feeTokenAddress: zeroAddress,
4700
- totalFeeAmount: "0",
4701
- totalFeeAmountUsd: "0",
4702
- totalFeeAmountUsdDisplay: formatUsdAmountDisplay(0),
4703
- }
4704
- }
4705
-
4706
- // TODO: make this dyanamic
4707
- export function getCompletionEstimateSeconds({
4708
- originChainId,
4709
- destinationChainId,
4710
- }: {
4711
- originChainId: number
4712
- destinationChainId: number
4713
- }): number {
4714
- if (originChainId === mainnet.id && destinationChainId === mainnet.id) {
4715
- return 60
4716
- }
4717
-
4718
- if (originChainId === mainnet.id || destinationChainId === mainnet.id) {
4719
- return 45
4720
- }
4721
-
4722
- return 15
4723
- }
4724
-
4725
- export async function getNormalizedQuoteObject({
4726
- originDepositAddress,
4727
- destinationDepositAddress,
4728
- destinationAddress,
4729
- destinationCalldata,
4730
- originChainId,
4731
- destinationChainId,
4732
- originAmount,
4733
- originAmountMin,
4734
- destinationAmount,
4735
- destinationAmountMin,
4736
- originTokenAddress,
4737
- destinationTokenAddress,
4738
- originTokenPriceUsd,
4739
- destinationTokenPriceUsd,
4740
- transactionStates,
4741
- fees,
4742
- slippageTolerance,
4743
- priceImpact,
4744
- priceImpactUsd,
4745
- originNativeTokenPriceUsd,
4746
- quoteProvider,
4747
- noSufficientBalance,
4748
- estimatedGasLimit,
4749
- }: {
4750
- originDepositAddress?: string
4751
- destinationDepositAddress?: string
4752
- destinationAddress?: string
4753
- destinationCalldata?: string
4754
- originChainId: number
4755
- destinationChainId?: number
4756
- originAmount: string
4757
- originAmountMin?: string
4758
- destinationAmount: string
4759
- destinationAmountMin?: string
4760
- originTokenAddress: string
4761
- destinationTokenAddress?: string
4762
- originTokenPriceUsd?: string | null
4763
- destinationTokenPriceUsd?: string | null
4764
- transactionStates?: TransactionState[]
4765
- fees?: PrepareSendFees
4766
- slippageTolerance?: string
4767
- priceImpact?: string
4768
- priceImpactUsd?: string
4769
- originNativeTokenPriceUsd?: number | null
4770
- quoteProvider?: string
4771
- noSufficientBalance?: boolean
4772
- estimatedGasLimit?: bigint
4773
- }): Promise<PrepareSendQuote> {
4774
- if (!destinationChainId) {
4775
- throw new Error("Destination chain id is required")
4776
- }
4777
-
4778
- if (!destinationTokenAddress && originChainId === destinationChainId) {
4779
- destinationTokenAddress = originTokenAddress
4780
- }
4781
-
4782
- if (!destinationTokenAddress && originChainId === destinationChainId) {
4783
- destinationTokenAddress = originTokenAddress
4784
- }
4785
-
4786
- if (!destinationTokenAddress) {
4787
- throw new Error("Destination token address is required")
4788
- }
4789
-
4790
- const originToken = await getTokenInfo(originChainId, originTokenAddress)
4791
- const destinationToken = await getTokenInfo(
4792
- destinationChainId,
4793
- destinationTokenAddress,
4794
- )
4795
-
4796
- const originChain = getChainInfo(originChainId)
4797
- const destinationChain = getChainInfo(destinationChainId)
4798
-
4799
- if (!originToken || !destinationToken || !originChain || !destinationChain) {
4800
- logger.console.error("[trails-sdk] Token or chain not found", {
4801
- originToken,
4802
- destinationToken,
4803
- originChain,
4804
- destinationChain,
4805
- })
4806
- throw new Error("Token or chain not found")
4807
- }
4808
-
4809
- const originAmountMinFormatted = formatRawAmount(
4810
- originAmountMin || "0",
4811
- originToken.decimals,
4812
- )
4813
- const originAmountMinUsdFormatted = formatAmount(
4814
- calcAmountUsdPrice({
4815
- amount: originAmountMinFormatted,
4816
- usdPrice: originTokenPriceUsd,
4817
- }),
4818
- )
4819
- const originAmountMinUsdDisplay = formatUsdAmountDisplay(
4820
- originAmountMinUsdFormatted,
4821
- )
4822
-
4823
- const destinationAmountMinFormatted = formatRawAmount(
4824
- destinationAmountMin || "0",
4825
- destinationToken.decimals,
4826
- )
4827
- const destinationAmountMinUsdFormatted = formatAmount(
4828
- calcAmountUsdPrice({
4829
- amount: destinationAmountMinFormatted,
4830
- usdPrice: destinationTokenPriceUsd,
4831
- }),
4832
- )
4833
- const destinationAmountMinUsdDisplay = formatUsdAmountDisplay(
4834
- destinationAmountMinUsdFormatted,
4835
- )
4836
-
4837
- const originAmountFormatted = formatRawAmount(
4838
- originAmount,
4839
- originToken.decimals,
4840
- )
4841
- const originAmountUsdFormatted = formatAmount(
4842
- calcAmountUsdPrice({
4843
- amount: originAmountFormatted,
4844
- usdPrice: originTokenPriceUsd,
4845
- }),
4846
- )
4847
- const originAmountUsdDisplay = formatUsdAmountDisplay(
4848
- originAmountUsdFormatted,
4849
- )
4850
-
4851
- const destinationAmountFormatted = formatRawAmount(
4852
- destinationAmount,
4853
- destinationToken.decimals,
4854
- )
4855
- const destinationAmountUsdFormatted = formatAmount(
4856
- calcAmountUsdPrice({
4857
- amount: destinationAmountFormatted,
4858
- usdPrice: destinationTokenPriceUsd,
4859
- }),
4860
- )
4861
- const destinationAmountUsdDisplay = formatUsdAmountDisplay(
4862
- destinationAmountUsdFormatted,
4863
- )
4864
-
4865
- const hasCustomCalldata = getIsCustomCalldata(destinationCalldata)
4866
-
4867
- const publicClient = createPublicClient({
4868
- chain: originChain,
4869
- transport: http(),
4870
- })
4871
-
4872
- let gasCostUsd: number = 0
4873
- let gasCostUsdDisplay: string = "0"
4874
- let gasCost: string = "0"
4875
- let gasCostFormatted: string = "0"
4876
- try {
4877
- if (originNativeTokenPriceUsd) {
4878
- // Use the actual estimated gas limit if provided, otherwise use default
4879
- // This ensures the quote gas cost matches what will be used in the actual transaction
4880
- const gasLimitForCost = estimatedGasLimit || DEFAULT_MIN_GASLIMIT
4881
-
4882
- logger.console.log(
4883
- "[trails-sdk][gas-estimation] Calculating gas cost for quote with gasLimit:",
4884
- gasLimitForCost,
4885
- "(estimated:",
4886
- estimatedGasLimit,
4887
- "default:",
4888
- DEFAULT_MIN_GASLIMIT,
4889
- ")",
4890
- )
4891
-
4892
- const gasCostWei = await estimateGasCost(publicClient, gasLimitForCost)
4893
- gasCost = gasCostWei.toString()
4894
- gasCostFormatted = formatUnits(gasCostWei, 18) // Native tokens always use 18 decimals
4895
-
4896
- // Calculate USD value
4897
- gasCostUsd = await estimateGasCostUsd(
4898
- publicClient,
4899
- originNativeTokenPriceUsd,
4900
- gasLimitForCost,
4901
- )
4902
- gasCostUsdDisplay = formatUsdAmountDisplay(gasCostUsd)
4903
-
4904
- logger.console.log(
4905
- "[trails-sdk][gas-estimation] Quote gas cost calculated:",
4906
- {
4907
- gasCostWei,
4908
- gasCost,
4909
- gasCostFormatted,
4910
- gasCostUsd,
4911
- gasCostUsdDisplay,
4912
- },
4913
- )
4914
- }
4915
- } catch (error) {
4916
- logger.console.error(
4917
- "[trails-sdk][gas-estimation] Error estimating gas cost for quote display",
4918
- error,
4919
- )
4920
- }
4921
-
4922
- // Calculate exchange rates
4923
- const originTokenPrice = originTokenPriceUsd ? Number(originTokenPriceUsd) : 0
4924
- const destinationTokenPrice = destinationTokenPriceUsd
4925
- ? Number(destinationTokenPriceUsd)
4926
- : 0
4927
- const exchangeRates = getTokenExchangeRates(
4928
- originTokenPrice,
4929
- destinationTokenPrice,
4930
- originToken.symbol,
4931
- destinationToken.symbol,
4932
- )
4933
-
4934
- let quoteProviderUrl = ""
4935
- if (quoteProvider === "cctp") {
4936
- quoteProviderUrl = "https://www.circle.com/"
4937
- } else if (quoteProvider === "relay") {
4938
- quoteProviderUrl = "https://relay.link/"
4939
- } else if (quoteProvider === "lifi") {
4940
- quoteProviderUrl = "https://li.fi/"
4941
- }
4942
-
4943
- const quoteProviderNames = {
4944
- cctp: "Circle CCTP",
4945
- relay: "Relay",
4946
- lifi: "LiFi",
4947
- }
4948
-
4949
- const quoteProviderName =
4950
- quoteProviderNames[quoteProvider as keyof typeof quoteProviderNames] ||
4951
- quoteProvider ||
4952
- ""
4953
- const quoteProviderInfo: QuoteProviderInfo = {
4954
- id: quoteProvider || "",
4955
- name: quoteProviderName || "",
4956
- url: quoteProviderUrl || "",
4957
- }
4958
-
4959
- const priceImpactUsdDisplay = formatUsdAmountDisplay(priceImpactUsd)
4960
-
4961
- return {
4962
- originDepositAddress: originDepositAddress || "",
4963
- destinationDepositAddress: destinationDepositAddress || "",
4964
- destinationAddress: destinationAddress || "",
4965
- destinationCalldata: hasCustomCalldata ? destinationCalldata || "" : "",
4966
- originAmount: originAmount || "0",
4967
- originAmountMin: originAmountMin || originAmount || "0",
4968
- originAmountMinUsdFormatted: originAmountMinUsdFormatted || "0",
4969
- originAmountMinUsdDisplay: originAmountMinUsdDisplay || "0",
4970
- destinationAmount: destinationAmount || "0",
4971
- destinationAmountMin: destinationAmountMin || destinationAmount || "0",
4972
- destinationAmountMinUsdFormatted: destinationAmountMinUsdFormatted || "0",
4973
- destinationAmountMinUsdDisplay: destinationAmountMinUsdDisplay || "0",
4974
- originAmountFormatted,
4975
- originAmountUsdFormatted,
4976
- originAmountUsdDisplay,
4977
- destinationAmountFormatted,
4978
- destinationAmountUsdFormatted,
4979
- destinationAmountUsdDisplay,
4980
- originToken,
4981
- destinationToken,
4982
- fees: fees || getZeroFees(),
4983
- slippageTolerance: slippageTolerance || "0",
4984
- priceImpact: priceImpact || "0",
4985
- priceImpactUsdDisplay: priceImpactUsdDisplay || "0",
4986
- originChain,
4987
- destinationChain,
4988
- completionEstimateSeconds: getCompletionEstimateSeconds({
4989
- originChainId,
4990
- destinationChainId,
4991
- }),
4992
- transactionStates: transactionStates || [],
4993
- gasCostUsd,
4994
- gasCostUsdDisplay,
4995
- gasCost,
4996
- gasCostFormatted,
4997
- originTokenRate: exchangeRates.originTokenRate,
4998
- destinationTokenRate: exchangeRates.destinationTokenRate,
4999
- originAmountDisplay: formatAmountDisplay(originAmountFormatted),
5000
- destinationAmountDisplay: formatAmountDisplay(destinationAmountFormatted),
5001
- originAmountMinDisplay: formatAmountDisplay(originAmountMinFormatted),
5002
- destinationAmountMinDisplay: formatAmountDisplay(
5003
- destinationAmountMinFormatted,
5004
- ),
5005
- quoteProvider: quoteProviderInfo,
5006
- noSufficientBalance: noSufficientBalance || false,
5007
- }
5008
- }
5009
-
5010
- function getTokenExchangeRates(
5011
- originTokenPrice: number,
5012
- destinationTokenPrice: number,
5013
- originTokenSymbol: string,
5014
- destinationTokenSymbol: string,
5015
- ): { originTokenRate: string; destinationTokenRate: string } {
5016
- if (originTokenPrice === 0 || destinationTokenPrice === 0) {
5017
- return {
5018
- originTokenRate: "0",
5019
- destinationTokenRate: "0",
5020
- }
5021
- }
5022
-
5023
- const originToDestination = originTokenPrice / destinationTokenPrice
5024
- const destinationToOrigin = destinationTokenPrice / originTokenPrice
5025
-
5026
- // Format the rates with appropriate precision
5027
- const originTokenRate = formatAmountDisplay(originToDestination, {
5028
- maxFractionDigits: destinationTokenSymbol === "USDC" ? 2 : 7,
5029
- })
5030
- const destinationTokenRate = formatAmountDisplay(destinationToOrigin, {
5031
- maxFractionDigits: originTokenSymbol === "USDC" ? 2 : 7,
5032
- })
5033
-
5034
- return {
5035
- originTokenRate: `1 ${originTokenSymbol} = ${originTokenRate} ${destinationTokenSymbol}`,
5036
- destinationTokenRate: `1 ${destinationTokenSymbol} = ${destinationTokenRate} ${originTokenSymbol}`,
5037
- }
5038
- }
408
+ // Re-export useQuote types and hook from the new location
409
+ export type {
410
+ UseQuoteProps,
411
+ Quote,
412
+ UseQuoteReturn,
413
+ SwapReturn,
414
+ QuoteProviderInfo,
415
+ } from "./widget/hooks/useQuote.js"
416
+ export { useQuote } from "./widget/hooks/useQuote.js"
417
+
418
+ // Re-export quote and fee utilities from transactionIntent for backward compatibility
419
+ export {
420
+ getFeesFromIntent,
421
+ getSlippageToleranceFromIntent,
422
+ getPriceImpactFromIntent,
423
+ getPriceImpactUsdFromIntent,
424
+ getFeesFromRelaySdkQuote,
425
+ getSlippageToleranceFromRelaySdkQuote,
426
+ getPriceImpactFromRelaySdkQuote,
427
+ getPriceImpactUsdFromRelaySdkQuote,
428
+ getZeroFees,
429
+ getCompletionEstimateSeconds,
430
+ getNormalizedQuoteObject,
431
+ } from "./transactionIntent/index.js"