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
@@ -462,28 +462,34 @@ function useDefaultTokenSelectionInternal(): UseDefaultTokenSelectionReturn {
462
462
 
463
463
  const decimals = destToken?.decimals
464
464
  if (!decimals) {
465
- throw new Error("Decimals not found")
466
- }
467
-
468
- destinationToken = destToken
469
- ? {
470
- id: destToken.chainId,
471
- name: destToken.name,
472
- symbol: destToken.symbol,
473
- balance: "0", // Destination token doesn't have balance yet
474
- imageUrl: destToken.imageUrl || "",
475
- chainId: destToken.chainId,
476
- contractAddress: destToken.contractAddress || zeroAddress,
477
- balanceUsdFormatted: "0",
478
- tokenPriceUsd: 0,
479
- decimals, // Top-level decimals
480
- contractInfo: {
481
- decimals,
482
- symbol: destToken.symbol,
465
+ logger.console.warn(
466
+ "[trails-sdk] Missing decimals for destination token, skipping:",
467
+ {
468
+ token: destToken,
469
+ },
470
+ )
471
+ destinationToken = null
472
+ } else {
473
+ destinationToken = destToken
474
+ ? {
475
+ id: destToken.chainId,
483
476
  name: destToken.name,
484
- },
485
- }
486
- : null
477
+ symbol: destToken.symbol,
478
+ balance: "0", // Destination token doesn't have balance yet
479
+ imageUrl: destToken.imageUrl || "",
480
+ chainId: destToken.chainId,
481
+ contractAddress: destToken.contractAddress || zeroAddress,
482
+ balanceUsdFormatted: "0",
483
+ tokenPriceUsd: 0,
484
+ decimals, // Top-level decimals
485
+ contractInfo: {
486
+ decimals,
487
+ symbol: destToken.symbol,
488
+ name: destToken.name,
489
+ },
490
+ }
491
+ : null
492
+ }
487
493
  }
488
494
 
489
495
  logger.console.log("[trails-sdk] Default token selection:", {
@@ -0,0 +1,466 @@
1
+ import { useQuery } from "@tanstack/react-query"
2
+ import { useRef } from "react"
3
+ import type { TransactionReceipt } from "viem"
4
+ import { zeroAddress } from "viem"
5
+ import { useIndexerGatewayClient } from "../../indexerClient.js"
6
+ import { getTokenBalancesWithPrices } from "../../tokenBalances.js"
7
+ import { logger } from "../../logger.js"
8
+ import { useSupportedTokens } from "../../tokens.js"
9
+ import { useTrailsClient } from "../../trailsClient.js"
10
+ import { getExplorerUrl } from "../../explorer.js"
11
+ import { getFullErrorMessage, getPrettifiedErrorMessage } from "../../error.js"
12
+ import { prepareSend } from "../../prepareSend.js"
13
+ import { abortControllerRegistry } from "../../abortController.js"
14
+ import { TradeType } from "../../prepareSend.js"
15
+ import { getChainInfo } from "../../chains.js"
16
+ import type { MetaTxnReceipt } from "../../transactionIntent/types.js"
17
+ import type { Chain } from "../../chains.js"
18
+ import type { SupportedToken } from "../../tokens.js"
19
+ import type { TransactionState } from "../../transactions.js"
20
+ import type { PrepareSendFees } from "../../prepareSend.js"
21
+ import type { SequenceEnv } from "../../config.js"
22
+ import { getTokenPrice } from "../../prices.js"
23
+ import { useCommitIntent, useExecuteIntent } from "../../mutations.js"
24
+
25
+ export type UseQuoteProps = {
26
+ walletClient?: any // TODO: fix this, has to do with viem/wagmi versions
27
+ fromTokenAddress?: string | null
28
+ fromChainId?: number | null
29
+ toTokenAddress?: string | null
30
+ toChainId?: number | null
31
+ toCalldata?: string | null
32
+ swapAmount?: string | bigint
33
+ toRecipient?: string | null
34
+ tradeType?: TradeType | null
35
+ slippageTolerance?: string | number | null
36
+ onStatusUpdate?: ((transactionStates: TransactionState[]) => void) | null
37
+ quoteProvider?: string | null
38
+ paymasterUrl?: string
39
+ selectedFeeToken?: {
40
+ tokenAddress: string
41
+ tokenSymbol?: string
42
+ } | null
43
+ abortSignal?: AbortSignal
44
+ }
45
+
46
+ export type SwapReturn = {
47
+ originTransaction: {
48
+ transactionHash?: string | null
49
+ explorerUrl?: string | null
50
+ receipt: TransactionReceipt | MetaTxnReceipt | null
51
+ }
52
+ destinationTransaction: {
53
+ transactionHash?: string | null
54
+ explorerUrl?: string | null
55
+ receipt: MetaTxnReceipt | null
56
+ }
57
+ totalCompletionSeconds?: number
58
+ }
59
+
60
+ export type QuoteProviderInfo = {
61
+ id: string
62
+ name: string
63
+ url: string
64
+ }
65
+
66
+ export type Quote = {
67
+ fromAmount: string
68
+ fromAmountMin: string
69
+ toAmount: string
70
+ toAmountMin: string
71
+ originToken: SupportedToken
72
+ destinationToken: SupportedToken
73
+ originChain: Chain
74
+ destinationChain: Chain
75
+ fees: PrepareSendFees
76
+ slippageTolerance: string
77
+ priceImpact: string
78
+ completionEstimateSeconds: number
79
+ transactionStates?: TransactionState[]
80
+ originTokenRate?: string
81
+ quoteProvider?: QuoteProviderInfo | null
82
+ destinationTokenRate?: string
83
+ fromAmountUsdDisplay?: string
84
+ toAmountUsdDisplay?: string
85
+ gasCostUsd?: number
86
+ gasCostUsdDisplay?: string
87
+ gasCost?: string
88
+ gasCostFormatted?: string
89
+ }
90
+
91
+ export type UseQuoteReturn = {
92
+ quote: Quote | null
93
+ swap: (() => Promise<SwapReturn | null>) | null
94
+ isLoadingQuote: boolean
95
+ quoteError: unknown
96
+ quoteErrorPrettified: string
97
+ refetchQuote: () => void
98
+ abort: () => void
99
+ }
100
+
101
+ // TODO: consolidate useSendForm, prepareSend, and useQuote hooks into a single hook
102
+ export function useQuote({
103
+ walletClient,
104
+ fromTokenAddress,
105
+ fromChainId,
106
+ toTokenAddress,
107
+ toChainId,
108
+ swapAmount,
109
+ tradeType,
110
+ toRecipient,
111
+ toCalldata,
112
+ slippageTolerance,
113
+ onStatusUpdate,
114
+ quoteProvider,
115
+ paymasterUrl,
116
+ selectedFeeToken,
117
+ nodeGatewayEnv,
118
+ abortSignal: externalAbortSignal,
119
+ }: Partial<
120
+ UseQuoteProps & { nodeGatewayEnv?: SequenceEnv }
121
+ > = {}): UseQuoteReturn {
122
+ // Set node gateway environment override for this quote session
123
+ if (nodeGatewayEnv) {
124
+ ;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
125
+ }
126
+
127
+ // Create abort controller for this hook instance
128
+ const abortControllerRef = useRef<AbortController>(new AbortController())
129
+
130
+ // Combine external abort signal with internal abort controller
131
+ const combinedAbortSignal = externalAbortSignal
132
+ ? (() => {
133
+ const controller = new AbortController()
134
+
135
+ // Listen to external abort signal
136
+ externalAbortSignal.addEventListener("abort", () => {
137
+ controller.abort()
138
+ abortControllerRef.current?.abort()
139
+ })
140
+
141
+ // Listen to internal abort controller
142
+ abortControllerRef.current?.signal.addEventListener("abort", () => {
143
+ controller.abort()
144
+ })
145
+
146
+ return controller.signal
147
+ })()
148
+ : abortControllerRef.current.signal
149
+
150
+ const trailsClient = useTrailsClient()
151
+ const indexerGatewayClient = useIndexerGatewayClient()
152
+
153
+ const { supportedTokens } = useSupportedTokens()
154
+
155
+ // Get mutation hooks for passing to prepareSend
156
+ const commitIntentMutation = useCommitIntent()
157
+ const executeIntentMutation = useExecuteIntent()
158
+
159
+ const { data, isLoading, error, refetch } = useQuery({
160
+ queryKey: [
161
+ "prepareSend",
162
+ fromTokenAddress,
163
+ fromChainId,
164
+ toTokenAddress,
165
+ toChainId,
166
+ swapAmount?.toString(),
167
+ toRecipient,
168
+ toCalldata,
169
+ tradeType,
170
+ slippageTolerance,
171
+ quoteProvider,
172
+ ],
173
+ queryFn: async () => {
174
+ try {
175
+ // Reset the abort controller for new queries to ensure fresh state
176
+ if (abortControllerRef.current.signal.aborted) {
177
+ logger.console.log(
178
+ "[trails-sdk] Resetting aborted controller for new query",
179
+ )
180
+ abortControllerRef.current = new AbortController()
181
+ }
182
+
183
+ if (
184
+ !walletClient ||
185
+ !trailsClient ||
186
+ !fromTokenAddress ||
187
+ !toTokenAddress ||
188
+ !swapAmount ||
189
+ !toRecipient ||
190
+ !fromChainId ||
191
+ !toChainId ||
192
+ !indexerGatewayClient
193
+ ) {
194
+ return null
195
+ }
196
+
197
+ // Get token balance using async method
198
+ let balances: any[] = []
199
+ try {
200
+ const result = await getTokenBalancesWithPrices({
201
+ account: walletClient.account!.address,
202
+ indexerGatewayClient,
203
+ trailsClient,
204
+ })
205
+ balances = result.balances
206
+ } catch (balanceError) {
207
+ const balanceErrorMessage = getFullErrorMessage(balanceError)
208
+ const isCorsError =
209
+ balanceErrorMessage.includes("Cross-Origin") ||
210
+ balanceErrorMessage.includes("CORS") ||
211
+ balanceErrorMessage.includes("Same Origin Policy")
212
+ const isNetworkError =
213
+ balanceErrorMessage.includes("fetch failed") ||
214
+ balanceErrorMessage.includes("network")
215
+
216
+ if (isCorsError || isNetworkError) {
217
+ logger.console.warn(
218
+ "[trails-sdk] [useQuote] Network or CORS error fetching balances, proceeding with quote using zero balance:",
219
+ {
220
+ error: balanceErrorMessage,
221
+ account: walletClient.account!.address,
222
+ },
223
+ )
224
+ // Continue with empty balances instead of crashing
225
+ balances = []
226
+ } else {
227
+ throw balanceError
228
+ }
229
+ }
230
+
231
+ const originTokenBalance = balances.find(
232
+ (b) =>
233
+ b.chainId === fromChainId &&
234
+ (b.contractAddress?.toLowerCase() ===
235
+ fromTokenAddress.toLowerCase() ||
236
+ (!b.contractAddress && fromTokenAddress === zeroAddress)),
237
+ )
238
+
239
+ const originTokenBalanceAmount = originTokenBalance?.balance ?? "0"
240
+
241
+ // Note: Disable this check for now to allow fetching a quote even when the origin balance is zero
242
+ // if (originTokenBalanceAmount === "0") {
243
+ // return null
244
+ // }
245
+
246
+ // logger.console.log("supportedTokens", supportedTokens)
247
+
248
+ // Get native token price for origin chain (for gas cost calculation)
249
+ let originNativeTokenPriceUsd = 0
250
+ try {
251
+ const originChain = getChainInfo(fromChainId)
252
+ const nativeTokenSymbol = originChain?.nativeCurrency?.symbol ?? ""
253
+ if (nativeTokenSymbol) {
254
+ const nativePrice = await getTokenPrice(trailsClient, {
255
+ tokenSymbol: nativeTokenSymbol,
256
+ tokenAddress: zeroAddress,
257
+ chainId: fromChainId,
258
+ })
259
+ originNativeTokenPriceUsd = nativePrice?.priceUsd ?? 0
260
+ logger.console.log(
261
+ "[trails-sdk] [useQuote] Origin native token price:",
262
+ {
263
+ chainId: fromChainId,
264
+ symbol: nativeTokenSymbol,
265
+ priceUsd: originNativeTokenPriceUsd,
266
+ },
267
+ )
268
+ }
269
+ } catch (error) {
270
+ logger.console.error(
271
+ "[trails-sdk] [useQuote] Error getting origin native token price:",
272
+ error,
273
+ )
274
+ }
275
+
276
+ const originToken = supportedTokens?.find(
277
+ (token) =>
278
+ token.contractAddress?.toLowerCase() ===
279
+ fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
280
+ )
281
+ const destinationToken = supportedTokens?.find(
282
+ (token) =>
283
+ token.contractAddress?.toLowerCase() ===
284
+ toTokenAddress?.toLowerCase() && token.chainId === toChainId,
285
+ )
286
+
287
+ const sourceTokenDecimals = originToken?.decimals
288
+ if (!sourceTokenDecimals) {
289
+ logger.console.error(
290
+ "[trails-sdk] [useQuote] Missing source token decimals:",
291
+ {
292
+ originToken,
293
+ fromTokenAddress,
294
+ fromChainId,
295
+ },
296
+ )
297
+ throw new Error("Source token decimals not found")
298
+ }
299
+ const destinationTokenDecimals = destinationToken?.decimals
300
+ if (!destinationTokenDecimals) {
301
+ logger.console.error(
302
+ "[trails-sdk] Missing destination token decimals:",
303
+ {
304
+ destinationToken,
305
+ toTokenAddress,
306
+ toChainId,
307
+ },
308
+ )
309
+ throw new Error("Destination token decimals not found")
310
+ }
311
+ const destinationTokenSymbol = destinationToken?.symbol ?? ""
312
+ const originTokenSymbol = originToken?.symbol ?? ""
313
+
314
+ const options = {
315
+ account: walletClient.account!,
316
+ originTokenAddress: fromTokenAddress,
317
+ originChainId: fromChainId,
318
+ originTokenBalance: originTokenBalanceAmount,
319
+ destinationChainId: toChainId,
320
+ recipient: toRecipient,
321
+ destinationTokenAddress: toTokenAddress,
322
+ swapAmount: swapAmount.toString(),
323
+ tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
324
+ originTokenSymbol: originTokenSymbol,
325
+ destinationTokenSymbol: destinationTokenSymbol,
326
+ destinationCalldata: toCalldata as string,
327
+ client: walletClient,
328
+ trailsClient,
329
+ sourceTokenDecimals,
330
+ destinationTokenDecimals,
331
+ fee: "0",
332
+ dryMode: false,
333
+ onTransactionStateChange: onStatusUpdate ?? (() => {}),
334
+ slippageTolerance: slippageTolerance?.toString(),
335
+ quoteProvider: quoteProvider,
336
+ paymasterUrl: paymasterUrl,
337
+ selectedFeeToken: selectedFeeToken ?? undefined,
338
+ abortSignal: combinedAbortSignal,
339
+ originNativeTokenPriceUsd: originNativeTokenPriceUsd,
340
+ commitIntentFn: commitIntentMutation.mutateAsync,
341
+ executeIntentFn: executeIntentMutation.mutateAsync,
342
+ }
343
+
344
+ logger.console.log("[trails-sdk] options", options)
345
+
346
+ const { quote: prepareSendQuote, send } = await prepareSend(options)
347
+
348
+ const quote = {
349
+ fromAmount: prepareSendQuote.originAmount,
350
+ toAmount: prepareSendQuote.destinationAmount,
351
+ fromAmountMin: prepareSendQuote.originAmountMin,
352
+ toAmountMin: prepareSendQuote.destinationAmountMin,
353
+ originToken: prepareSendQuote.originToken,
354
+ destinationToken: prepareSendQuote.destinationToken,
355
+ originChain: prepareSendQuote.originChain,
356
+ destinationChain: prepareSendQuote.destinationChain,
357
+ fees: prepareSendQuote.fees,
358
+ priceImpact: prepareSendQuote.priceImpact,
359
+ completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
360
+ slippageTolerance: prepareSendQuote.slippageTolerance,
361
+ transactionStates: prepareSendQuote.transactionStates,
362
+ originTokenRate: prepareSendQuote.originTokenRate,
363
+ destinationTokenRate: prepareSendQuote.destinationTokenRate,
364
+ quoteProvider: prepareSendQuote.quoteProvider,
365
+ fromAmountUsdDisplay:
366
+ prepareSendQuote.originAmountUsdDisplay ?? undefined,
367
+ toAmountUsdDisplay:
368
+ prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
369
+ gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
370
+ gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
371
+ gasCost: prepareSendQuote.gasCost ?? undefined,
372
+ gasCostFormatted: prepareSendQuote.gasCostFormatted ?? undefined,
373
+ }
374
+
375
+ const swap = async (): Promise<SwapReturn> => {
376
+ const {
377
+ depositUserTxnReceipt,
378
+ destinationMetaTxnReceipt,
379
+ totalCompletionSeconds,
380
+ } = await send({
381
+ selectedFeeToken: selectedFeeToken ?? undefined,
382
+ })
383
+
384
+ return {
385
+ originTransaction: {
386
+ transactionHash: depositUserTxnReceipt?.transactionHash,
387
+ explorerUrl: getExplorerUrl({
388
+ txHash: depositUserTxnReceipt?.transactionHash as string,
389
+ chainId: fromChainId,
390
+ }),
391
+ receipt: depositUserTxnReceipt,
392
+ },
393
+ destinationTransaction: {
394
+ transactionHash: destinationMetaTxnReceipt?.txnHash,
395
+ explorerUrl: getExplorerUrl({
396
+ txHash: destinationMetaTxnReceipt?.txnHash as string,
397
+ chainId: toChainId,
398
+ }),
399
+ receipt: destinationMetaTxnReceipt,
400
+ },
401
+ totalCompletionSeconds,
402
+ }
403
+ }
404
+
405
+ return {
406
+ quote,
407
+ swap,
408
+ }
409
+ } catch (error) {
410
+ logger.console.error(
411
+ "[trails-sdk] [useQuote] Error getting quote:",
412
+ error,
413
+ )
414
+ throw getFullErrorMessage(error)
415
+ }
416
+ },
417
+ // Prevent unnecessary refetching
418
+ enabled: Boolean(
419
+ walletClient &&
420
+ trailsClient &&
421
+ fromTokenAddress &&
422
+ toTokenAddress &&
423
+ swapAmount &&
424
+ toRecipient &&
425
+ fromChainId &&
426
+ toChainId &&
427
+ indexerGatewayClient &&
428
+ // !isLoadingTokens &&
429
+ supportedTokens &&
430
+ supportedTokens.length > 0,
431
+ ),
432
+ staleTime: 30 * 1000, // Consider data fresh for 30 seconds
433
+ refetchOnWindowFocus: false, // Don't refetch when window regains focus
434
+ refetchOnMount: false, // Don't refetch on component remount if data exists
435
+ refetchInterval: false, // Disable automatic polling
436
+ retry: 2, // Limit retry attempts
437
+ refetchOnReconnect: true, // Refetch when network reconnects
438
+ })
439
+
440
+ return {
441
+ quote: data?.quote || null,
442
+ swap: data?.swap || null,
443
+ isLoadingQuote: isLoading,
444
+ quoteError: error,
445
+ quoteErrorPrettified: getPrettifiedErrorMessage(error),
446
+ refetchQuote: () => refetch(),
447
+ abort: () => {
448
+ logger.console.log("[trails-sdk] useQuote abort() called")
449
+ logger.console.log(
450
+ "[trails-sdk] Active operations before abort:",
451
+ abortControllerRegistry.getAll(),
452
+ )
453
+
454
+ // Abort the internal abort controller
455
+ abortControllerRef.current.abort()
456
+
457
+ // Also abort all active polling operations in prepareSend
458
+ abortControllerRegistry.abortAll()
459
+
460
+ logger.console.log(
461
+ "[trails-sdk] Abort completed, active operations:",
462
+ abortControllerRegistry.getAll(),
463
+ )
464
+ },
465
+ }
466
+ }
@@ -8,7 +8,19 @@ import React, {
8
8
  type ReactNode,
9
9
  } from "react"
10
10
  import { logger } from "../../logger.js"
11
- import { zeroAddress } from "viem"
11
+ import { ethAddress, zeroAddress } from "viem"
12
+ import { formatUsdAmountDisplay } from "../../tokenBalances.js"
13
+
14
+ const ZERO_ADDRESS = zeroAddress.toLowerCase()
15
+ const ETH_ADDRESS = ethAddress.toLowerCase()
16
+
17
+ const normalizeAddress = (address?: string | null): string =>
18
+ (address ?? "").toLowerCase()
19
+
20
+ const isNativeTokenAddress = (address?: string | null): boolean => {
21
+ const normalized = normalizeAddress(address)
22
+ return normalized === ZERO_ADDRESS || normalized === ETH_ADDRESS
23
+ }
12
24
 
13
25
  // Define the FeeOption interface with balance check properties
14
26
  export interface FeeOption {
@@ -20,6 +32,7 @@ export interface FeeOption {
20
32
  notEnoughBalance?: boolean
21
33
  tokenImageUrl?: string
22
34
  chainId?: number
35
+ amountUsdDisplay?: string // Formatted USD display like "≈ $0.39"
23
36
  }
24
37
 
25
38
  // Token type with balance information
@@ -113,13 +126,14 @@ export const SelectedFeeTokenProvider: React.FC<
113
126
 
114
127
  // Add balance check and additional properties to each fee option
115
128
  const feeOptionsWithBalanceCheck = rawFeeOptions.map((option: any) => {
116
- // For native token (zero address), check if user has enough native balance
117
- if (option.tokenAddress === zeroAddress) {
129
+ // For native token (zero/ETH address), check if user has enough native balance
130
+ if (isNativeTokenAddress(option.tokenAddress)) {
118
131
  // Find the native token balance for the origin chain
119
132
  const nativeTokenInfo = availableTokens.find(
120
133
  (token) =>
121
- token.contractAddress?.toLowerCase() ===
122
- zeroAddress.toLowerCase() && token.chainId === originTokenChainId,
134
+ token.chainId === originTokenChainId &&
135
+ (isNativeTokenAddress(token.contractAddress) ||
136
+ !token.contractAddress),
123
137
  )
124
138
 
125
139
  const nativeBalance = nativeTokenInfo?.balance || "0"
@@ -131,6 +145,8 @@ export const SelectedFeeTokenProvider: React.FC<
131
145
  notEnoughBalance,
132
146
  tokenImageUrl: nativeTokenInfo?.imageUrl,
133
147
  chainId: originTokenChainId, // Native token is on the same chain as origin
148
+ amountUsdDisplay: formatUsdAmountDisplay(option.amountUsd),
149
+ amountUSD: option.amountUsd, // Normalize to uppercase for consistency
134
150
  }
135
151
  }
136
152
 
@@ -152,6 +168,8 @@ export const SelectedFeeTokenProvider: React.FC<
152
168
  notEnoughBalance,
153
169
  tokenImageUrl: userTokenBalance.imageUrl,
154
170
  chainId: userTokenBalance.chainId, // Use the actual token's chain ID
171
+ amountUsdDisplay: formatUsdAmountDisplay(option.amountUsd),
172
+ amountUSD: option.amountUsd, // Normalize to uppercase for consistency
155
173
  }
156
174
  }
157
175
 
@@ -161,6 +179,8 @@ export const SelectedFeeTokenProvider: React.FC<
161
179
  notEnoughBalance: true,
162
180
  tokenImageUrl: undefined,
163
181
  chainId: originTokenChainId, // Fallback to origin chain
182
+ amountUsdDisplay: formatUsdAmountDisplay(option.amountUsd),
183
+ amountUSD: option.amountUsd, // Normalize to uppercase for consistency
164
184
  }
165
185
  })
166
186
 
@@ -184,36 +204,17 @@ export const SelectedFeeTokenProvider: React.FC<
184
204
  return sortedFeeOptions
185
205
  }, [rawFeeOptions, originTokenChainId, availableTokens])
186
206
 
187
- // Auto-select first fee option with sufficient balance (non-native token)
188
- // Only auto-select on initial load, not when user explicitly chooses native gas
189
- // If no ERC20 fee options are available, selectedFeeToken stays null (native gas)
207
+ // Auto-select first ERC20 fee option with sufficient balance
208
+ // Only auto-select if user hasn't made an explicit selection yet
190
209
  useEffect(() => {
191
- logger.console.log(
192
- "[trails-sdk] [FEE-SELECT] Auto-selection useEffect triggered:",
193
- {
194
- processedFeeOptionsLength: processedFeeOptions.length,
195
- hasUserSelectedFeeOption,
196
- selectedFeeToken,
197
- willAutoSelect:
198
- processedFeeOptions.length > 0 &&
199
- !hasUserSelectedFeeOption &&
200
- !selectedFeeToken,
201
- },
202
- )
203
-
204
- // Only auto-select if:
205
- // 1. We have fee options
206
- // 2. User hasn't made an explicit selection yet
207
- // 3. No fee token is currently selected
208
210
  if (
209
211
  processedFeeOptions.length > 0 &&
210
212
  !hasUserSelectedFeeOption &&
211
213
  !selectedFeeToken
212
214
  ) {
213
- // Find first non-native token option with sufficient balance
215
+ // Find first token option with sufficient balance
214
216
  const firstValidOption = processedFeeOptions.find(
215
- (option: FeeOption) =>
216
- option.tokenAddress !== zeroAddress && !option.notEnoughBalance,
217
+ (option: FeeOption) => !option.notEnoughBalance,
217
218
  )
218
219
 
219
220
  if (firstValidOption) {
@@ -221,20 +222,14 @@ export const SelectedFeeTokenProvider: React.FC<
221
222
  "[trails-sdk] [FEE-SELECT] Auto-selecting first valid fee option:",
222
223
  firstValidOption,
223
224
  )
224
- // Strip extra fields to match the format expected by prepareSend
225
- const cleanOption: FeeOption = {
225
+ // Use internal setter to avoid marking as user selection
226
+ setSelectedFeeTokenInternal({
226
227
  tokenAddress: firstValidOption.tokenAddress,
227
228
  tokenSymbol: firstValidOption.tokenSymbol,
228
229
  tokenDecimals: firstValidOption.tokenDecimals,
229
230
  amount: firstValidOption.amount,
230
231
  amountUSD: firstValidOption.amountUSD,
231
- }
232
- // Use internal setter to avoid marking as user selection
233
- setSelectedFeeTokenInternal(cleanOption)
234
- } else {
235
- logger.console.log(
236
- "[trails-sdk] [FEE-SELECT] No valid ERC20 fee options available, will use native gas",
237
- )
232
+ })
238
233
  }
239
234
  }
240
235
  }, [