0xtrails 0.8.2 → 0.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (68) hide show
  1. package/dist/aave.d.ts.map +1 -1
  2. package/dist/{ccip-ru_Yzdas.js → ccip-Bs-QcZXm.js} +13 -13
  3. package/dist/constants.d.ts +2 -0
  4. package/dist/constants.d.ts.map +1 -1
  5. package/dist/fees.d.ts +11 -17
  6. package/dist/fees.d.ts.map +1 -1
  7. package/dist/{index-Si7cO9V7.js → index-C_EsqqSn.js} +20320 -20063
  8. package/dist/index.js +425 -847
  9. package/dist/intents.d.ts +1 -2
  10. package/dist/intents.d.ts.map +1 -1
  11. package/dist/prepareSend.d.ts.map +1 -1
  12. package/dist/recover.d.ts +8 -9
  13. package/dist/recover.d.ts.map +1 -1
  14. package/dist/tokenBalances.d.ts +51 -0
  15. package/dist/tokenBalances.d.ts.map +1 -1
  16. package/dist/trailsRouter.d.ts +15 -0
  17. package/dist/trailsRouter.d.ts.map +1 -1
  18. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts +1 -3
  19. package/dist/transactionIntent/deposits/depositOrchestrator.d.ts.map +1 -1
  20. package/dist/transactionIntent/deposits/standardDeposit.d.ts +1 -3
  21. package/dist/transactionIntent/deposits/standardDeposit.d.ts.map +1 -1
  22. package/dist/transactionIntent/handlers/crossChain.d.ts +2 -4
  23. package/dist/transactionIntent/handlers/crossChain.d.ts.map +1 -1
  24. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts +5 -4
  25. package/dist/transactionIntent/handlers/sameChainSameToken.d.ts.map +1 -1
  26. package/dist/transactionIntent/quote/normalizeQuote.d.ts +1 -1
  27. package/dist/transactionIntent/quote/normalizeQuote.d.ts.map +1 -1
  28. package/dist/transactionIntent/quote/quoteHelpers.d.ts +1 -1
  29. package/dist/transactionIntent/quote/quoteHelpers.d.ts.map +1 -1
  30. package/dist/transactionIntent/types.d.ts +11 -18
  31. package/dist/transactionIntent/types.d.ts.map +1 -1
  32. package/dist/widget/components/AccountIntentTransactionHistory.d.ts.map +1 -1
  33. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  34. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  35. package/dist/widget/components/SlippageToleranceSettings.d.ts +2 -1
  36. package/dist/widget/components/SlippageToleranceSettings.d.ts.map +1 -1
  37. package/dist/widget/css/compiled.css +1 -1
  38. package/dist/widget/hooks/useQuote.d.ts +94 -35
  39. package/dist/widget/hooks/useQuote.d.ts.map +1 -1
  40. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  41. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  42. package/dist/widget/hooks/useTrailsSendTransaction.d.ts.map +1 -1
  43. package/dist/widget/index.js +1 -1
  44. package/package.json +2 -2
  45. package/src/aave.ts +4 -0
  46. package/src/constants.ts +4 -0
  47. package/src/fees.ts +47 -72
  48. package/src/intents.ts +1 -3
  49. package/src/morpho.ts +1 -1
  50. package/src/prepareSend.ts +42 -6
  51. package/src/recover.ts +116 -172
  52. package/src/tokenBalances.ts +301 -1
  53. package/src/trailsRouter.ts +77 -0
  54. package/src/transactionIntent/deposits/depositOrchestrator.ts +0 -6
  55. package/src/transactionIntent/deposits/standardDeposit.ts +167 -184
  56. package/src/transactionIntent/handlers/crossChain.ts +8 -11
  57. package/src/transactionIntent/handlers/sameChainSameToken.ts +619 -608
  58. package/src/transactionIntent/quote/normalizeQuote.ts +32 -46
  59. package/src/transactionIntent/quote/quoteHelpers.ts +4 -2
  60. package/src/transactionIntent/types.ts +11 -18
  61. package/src/widget/compiled.css +1 -1
  62. package/src/widget/components/AccountIntentTransactionHistory.tsx +50 -18
  63. package/src/widget/components/ClassicSwap.tsx +25 -30
  64. package/src/widget/components/QuoteDetails.tsx +18 -27
  65. package/src/widget/components/SlippageToleranceSettings.tsx +55 -25
  66. package/src/widget/hooks/useQuote.ts +317 -79
  67. package/src/widget/hooks/useSendForm.ts +123 -764
  68. package/src/widget/hooks/useTrailsSendTransaction.ts +0 -2
@@ -1,7 +1,8 @@
1
1
  import { useQuery } from "@tanstack/react-query"
2
- import { useRef } from "react"
2
+ import { useRef, useMemo, useState, useEffect } from "react"
3
3
  import type { TransactionReceipt } from "viem"
4
- import { zeroAddress, erc20Abi } from "viem"
4
+ import { zeroAddress, erc20Abi, parseUnits } from "viem"
5
+ import { etherlink } from "viem/chains"
5
6
  import { useIndexerGatewayClient } from "../../indexerClient.js"
6
7
  import { getTokenBalancesWithPrices } from "../../tokenBalances.js"
7
8
  import { logger } from "../../logger.js"
@@ -14,12 +15,14 @@ import { prepareSend } from "../../prepareSend.js"
14
15
  import { abortControllerRegistry } from "../../abortController.js"
15
16
  import { TradeType } from "../../prepareSend.js"
16
17
  import { getChainInfo, useChainRpcClient } from "../../chains.js"
18
+ import { getIsContract } from "../../contractUtils.js"
17
19
  import type { IntentTransaction } from "@0xtrails/api"
18
20
  import type { PrepareSendOptions } from "../../transactionIntent/types.js"
19
21
  import type { Chain } from "../../chains.js"
20
22
  import type { Token } from "../../tokens.js"
21
23
  import type { TransactionState } from "../../transactions.js"
22
24
  import type { PrepareSendFees } from "../../prepareSend.js"
25
+ import type { TrailsFeeBreakdown } from "../../fees.js"
23
26
  import { getTokenPrice } from "../../prices.js"
24
27
  import { useCommitIntent, useExecuteIntent } from "../../mutations.js"
25
28
  import type { CheckoutOnHandlers } from "./useCheckout.js"
@@ -30,7 +33,7 @@ import type { FeeOption, RouteProvider } from "@0xtrails/api"
30
33
  *
31
34
  * All parameters are optional, but the hook will not fetch a quote until all required
32
35
  * parameters are provided (walletClient, fromTokenAddress, fromChainId, toTokenAddress,
33
- * toChainId, swapAmount, toRecipient).
36
+ * toChainId, swapAmount, toAddress).
34
37
  */
35
38
  export type UseQuoteProps = {
36
39
  /** The wallet client from wagmi (e.g., from `useWalletClient()`). Required for quote fetching. */
@@ -48,6 +51,10 @@ export type UseQuoteProps = {
48
51
  /** The amount to swap, as a string or bigint in the token's smallest unit (wei/smallest unit). For example, 1 USDC (6 decimals) = '1000000'. */
49
52
  swapAmount?: string | bigint
50
53
  /** The recipient address for the destination token. */
54
+ toAddress?: string | null
55
+ /**
56
+ * @deprecated Use `toAddress` instead. This will be removed in a future version.
57
+ */
51
58
  toRecipient?: string | null
52
59
  /** The trade type: `TradeType.EXACT_INPUT` (you specify input amount) or `TradeType.EXACT_OUTPUT` (you specify output amount). Defaults to `EXACT_OUTPUT`. */
53
60
  tradeType?: TradeType | null
@@ -100,6 +107,8 @@ export type UseQuoteProps = {
100
107
  nodeGatewayEnv?: "prod" | "dev" | "local" | "cors-anywhere"
101
108
  /** Optional flag to indicate if the wallet is a smart wallet. */
102
109
  isSmartWallet?: boolean | null
110
+ /** Optional fund method (e.g., 'wallet', 'qr-code', 'exchange'). Used to determine default balance for external deposit flows. */
111
+ fundMethod?: string | null
103
112
  }
104
113
 
105
114
  /**
@@ -138,10 +147,31 @@ export type RouteProviderInfo = {
138
147
  * and other relevant information for displaying to the user.
139
148
  */
140
149
  export type Quote = {
150
+ // Kept for backward compatibility (use origin*/destination* versions)
141
151
  fromAmount: string
142
152
  fromAmountMin: string
143
153
  toAmount: string
144
154
  toAmountMin: string
155
+ fromAmountUsdDisplay: string
156
+ toAmountUsdDisplay: string
157
+ // PrepareSendQuote compatible names
158
+ originAmount: string
159
+ originAmountMin: string
160
+ destinationAmount: string
161
+ destinationAmountMin: string
162
+ originAmountUsdDisplay: string
163
+ destinationAmountUsdDisplay: string
164
+ originAmountDisplay: string
165
+ destinationAmountDisplay: string
166
+ originAmountMinDisplay: string
167
+ originAmountMinUsdFormatted: string
168
+ originAmountMinUsdDisplay: string
169
+ destinationAmountMinDisplay: string
170
+ destinationAmountMinUsdFormatted: string
171
+ destinationAmountMinUsdDisplay: string
172
+ originAmountUsdFormatted: string
173
+ destinationAmountUsdFormatted: string
174
+ // Common fields
145
175
  originToken: Token
146
176
  destinationToken: Token
147
177
  originChain: Chain
@@ -149,38 +179,65 @@ export type Quote = {
149
179
  fees: PrepareSendFees
150
180
  slippageTolerance: string
151
181
  priceImpact: string
152
- priceImpactUsd?: string
153
- priceImpactUsdDisplay?: string
182
+ priceImpactUsd: string | null
183
+ priceImpactUsdDisplay: string
154
184
  completionEstimateSeconds: number
155
- transactionStates?: TransactionState[]
156
- originTokenRate?: string
157
- routeProviders?: RouteProviderInfo[]
158
- destinationTokenRate?: string
159
- fromAmountUsdDisplay?: string
160
- toAmountUsdDisplay?: string
161
- gasCostUsd?: number
162
- gasCostUsdDisplay?: string
163
- gasCost?: string
164
- gasCostFormatted?: string
165
- totalFeesUsd?: number
166
- totalFeesUsdDisplay?: string
167
- totalGasUsd?: number
168
- totalGasUsdDisplay?: string
169
- allProviderFeesUsd?: number
170
- allProviderFeesUsdDisplay?: string
185
+ completionEstimateDisplay: string
186
+ transactionStates: TransactionState[]
187
+ originTokenRate: string
188
+ routeProviders: RouteProviderInfo[]
189
+ destinationTokenRate: string
190
+ gasCostUsd: number | null
191
+ gasCostUsdDisplay: string
192
+ gasCost: string
193
+ gasCostFormatted: string
194
+ totalFeesUsd: number | null
195
+ totalFeesUsdDisplay: string
196
+ totalGasUsd: number | null
197
+ totalGasUsdDisplay: string
198
+ intentId: string | null
199
+ originAmountFormatted: string
200
+ destinationAmountFormatted: string
201
+ originDepositAddress: string
202
+ destinationDepositAddress: string
203
+ destinationAddress: string
204
+ destinationCalldata: string
205
+ trailsFeeBreakdown: TrailsFeeBreakdown | null
206
+ originGasUsd: number | null
207
+ originGasUsdDisplay: string
208
+ destinationGasUsd: number | null
209
+ destinationGasUsdDisplay: string
210
+ providerFeeUsd: number | null
211
+ providerFeeUsdDisplay: string
212
+ trailsFeeUsd: number | null
213
+ trailsFeeUsdDisplay: string
214
+ totalProviderFeesUsd: number | null
215
+ totalProviderFeesUsdDisplay: string
216
+ noSufficientBalance: boolean
171
217
  }
172
218
 
173
219
  /**
174
220
  * Return type for the {@link useQuote} hook.
175
221
  *
176
- * Provides the quote data, swap function, loading state, error information,
222
+ * Provides the quote data, send function, loading state, error information,
177
223
  * and utility functions for managing the quote lifecycle.
178
224
  */
225
+ export type SendOptions = {
226
+ /** Override the fee option for this transaction (e.g., pay gas in USDC instead of ETH). */
227
+ selectedFeeOption?: FeeOption | null
228
+ /** Callback called when the wallet signature is confirmed and transaction is submitted. */
229
+ onOriginSend?: () => void
230
+ }
231
+
179
232
  export type UseQuoteReturn = {
180
- /** The quote object with swap details, or `null` if not available. */
233
+ /** The quote object with transaction details, or `null` if not available. */
181
234
  quote: Quote | null
182
- /** A function to execute the swap, or `null` if not available. Returns a Promise that resolves to swap result with transaction receipts. */
183
- swap: (() => Promise<SwapReturn | null>) | null
235
+ /** A function to execute the transaction, or `null` if not available. Returns a Promise that resolves to transaction result with receipts. Accepts optional SendOptions to override fee option. */
236
+ send: ((options?: SendOptions) => Promise<SwapReturn | null>) | null
237
+ /**
238
+ * @deprecated Use `send` instead. This will be removed in a future version.
239
+ */
240
+ swap: ((options?: SendOptions) => Promise<SwapReturn | null>) | null
184
241
  /** Boolean indicating if a quote is currently being fetched. */
185
242
  isLoadingQuote: boolean
186
243
  /** Any error that occurred while fetching the quote, or `null`. */
@@ -191,6 +248,14 @@ export type UseQuoteReturn = {
191
248
  refetchQuote: () => void
192
249
  /** Function to abort any in-flight quote requests. */
193
250
  abort: () => void
251
+ /** Fee options available for this quote (e.g., pay gas in different tokens). */
252
+ feeOptions: FeeOption[]
253
+ /** Whether the recipient address is a contract (null if not yet checked). */
254
+ isRecipientContract: boolean | null
255
+ /** Whether the sender address is a contract on the origin chain (null if not yet checked). */
256
+ isSenderContractOnOrigin: boolean | null
257
+ /** Whether the sender address is a contract on the destination chain (null if not yet checked). */
258
+ isSenderContractOnDestination: boolean | null
194
259
  }
195
260
 
196
261
  // TODO: consolidate useSendForm, prepareSend, and useQuote hooks into a single hook
@@ -213,7 +278,7 @@ export type UseQuoteReturn = {
213
278
  * const { data: walletClient } = useWalletClient()
214
279
  * const { address } = useAccount()
215
280
  *
216
- * const { quote, swap, isLoadingQuote, quoteError, refetchQuote } = useQuote({
281
+ * const { quote, send, isLoadingQuote, quoteError, refetchQuote } = useQuote({
217
282
  * walletClient,
218
283
  * fromTokenAddress: '0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48', // USDC
219
284
  * fromChainId: 1, // Ethereum
@@ -221,7 +286,7 @@ export type UseQuoteReturn = {
221
286
  * toChainId: 8453, // Base
222
287
  * swapAmount: '1000000', // 1 USDC (6 decimals)
223
288
  * tradeType: TradeType.EXACT_INPUT,
224
- * toRecipient: address,
289
+ * toAddress: address,
225
290
  * slippageTolerance: '0.005', // 0.5%
226
291
  * onStatusUpdate: (states) => {
227
292
  * console.log('Transaction states:', states)
@@ -236,14 +301,14 @@ export type UseQuoteReturn = {
236
301
  * return () => clearInterval(id)
237
302
  * }, [refetchQuote])
238
303
  *
239
- * const handleSwap = async () => {
240
- * if (!swap) return
304
+ * const handleSend = async () => {
305
+ * if (!send) return
241
306
  *
242
307
  * try {
243
- * const result = await swap()
244
- * console.log('Swap completed:', result)
308
+ * const result = await send()
309
+ * console.log('Transaction completed:', result)
245
310
  * } catch (error) {
246
- * console.error('Swap failed:', error)
311
+ * console.error('Transaction failed:', error)
247
312
  * }
248
313
  * }
249
314
  *
@@ -255,7 +320,7 @@ export type UseQuoteReturn = {
255
320
  * <div>
256
321
  * <div>From: {quote.fromAmount} {quote.originToken.symbol}</div>
257
322
  * <div>To: {quote.toAmount} {quote.destinationToken.symbol}</div>
258
- * <button onClick={handleSwap}>Execute Swap</button>
323
+ * <button onClick={handleSend}>Send</button>
259
324
  * </div>
260
325
  * )
261
326
  * }
@@ -269,7 +334,7 @@ export type UseQuoteReturn = {
269
334
  * @param props.toChainId - The chain ID of the destination chain
270
335
  * @param props.swapAmount - The amount to swap, as a string or bigint in the token's smallest unit (wei/smallest unit). For example, 1 USDC (6 decimals) = '1000000'
271
336
  * @param props.tradeType - The trade type: `TradeType.EXACT_INPUT` (you specify input amount) or `TradeType.EXACT_OUTPUT` (you specify output amount). Defaults to `EXACT_OUTPUT`
272
- * @param props.toRecipient - The recipient address for the destination token.
337
+ * @param props.toAddress - The recipient address for the destination token.
273
338
  * @param props.slippageTolerance - Slippage tolerance as a decimal string or number (e.g., '0.005' for 0.5%, '0.01' for 1%). Defaults to a reasonable value if not provided
274
339
  * @param props.toCalldata - Optional custom calldata for the destination chain. Used for executing arbitrary contract calls (e.g., swap and mint an NFT on destination chain)
275
340
  * @param props.onStatusUpdate - Optional callback function that receives transaction state updates during swap execution
@@ -284,8 +349,8 @@ export type UseQuoteReturn = {
284
349
  * @param props.isSmartWallet - Optional flag to indicate if the wallet is a smart wallet
285
350
  *
286
351
  * @returns An object containing:
287
- * - `quote` - The quote object with swap details, or `null` if not available
288
- * - `swap` - A function to execute the swap, or `null` if not available. Returns a Promise that resolves to swap result with transaction receipts
352
+ * - `quote` - The quote object with transaction details, or `null` if not available
353
+ * - `send` - A function to execute the transaction, or `null` if not available. Returns a Promise that resolves to transaction result with receipts
289
354
  * - `isLoadingQuote` - Boolean indicating if a quote is currently being fetched
290
355
  * - `quoteError` - Any error that occurred while fetching the quote, or `null`
291
356
  * - `quoteErrorPrettified` - A user-friendly error message string
@@ -312,6 +377,7 @@ export function useQuote({
312
377
  toChainId,
313
378
  swapAmount,
314
379
  tradeType,
380
+ toAddress,
315
381
  toRecipient,
316
382
  toCalldata,
317
383
  slippageTolerance,
@@ -325,7 +391,15 @@ export function useQuote({
325
391
  abortSignal: externalAbortSignal,
326
392
  apiKey,
327
393
  isSmartWallet,
394
+ fundMethod,
328
395
  }: Partial<UseQuoteProps> = {}): UseQuoteReturn {
396
+ // Handle deprecated toRecipient prop - prefer toAddress
397
+ if (toRecipient && !toAddress) {
398
+ logger.console.warn(
399
+ "[trails-sdk] [useQuote] Warning: 'toRecipient' is deprecated, use 'toAddress' instead",
400
+ )
401
+ }
402
+ const effectiveToAddress = toAddress ?? toRecipient
329
403
  // Set node gateway environment override for this quote session
330
404
  if (nodeGatewayEnv) {
331
405
  ;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
@@ -354,6 +428,15 @@ export function useQuote({
354
428
  })()
355
429
  : abortControllerRef.current.signal
356
430
 
431
+ // Debounce swapAmount to prevent excessive API calls while user is typing
432
+ const [debouncedSwapAmount, setDebouncedSwapAmount] = useState(swapAmount)
433
+ useEffect(() => {
434
+ const timer = setTimeout(() => {
435
+ setDebouncedSwapAmount(swapAmount)
436
+ }, 500)
437
+ return () => clearTimeout(timer)
438
+ }, [swapAmount])
439
+
357
440
  const { trailsApiKey, trailsApiUrl, sequenceIndexerUrl } = useTrails()
358
441
  // trailsApiKey is the same as sequenceProjectAccessKey
359
442
  const sequenceProjectAccessKey = trailsApiKey
@@ -374,6 +457,74 @@ export function useQuote({
374
457
  const commitIntentMutation = useCommitIntent()
375
458
  const executeIntentMutation = useExecuteIntent()
376
459
 
460
+ // Auto-detect swap/bridge provider for etherlink chains
461
+ const effectiveSwapProvider = useMemo((): RouteProvider | null => {
462
+ // Force lifi for etherlink chains when no provider specified
463
+ if (!swapProvider) {
464
+ if (fromChainId === etherlink.id || toChainId === etherlink.id) {
465
+ return "lifi" as RouteProvider
466
+ }
467
+ }
468
+ return swapProvider ?? null
469
+ }, [swapProvider, fromChainId, toChainId])
470
+
471
+ const effectiveBridgeProvider = useMemo((): RouteProvider | null => {
472
+ // Force lifi for etherlink chains when no provider specified
473
+ if (!bridgeProvider) {
474
+ if (fromChainId === etherlink.id || toChainId === etherlink.id) {
475
+ return "lifi" as RouteProvider
476
+ }
477
+ }
478
+ return bridgeProvider ?? null
479
+ }, [bridgeProvider, fromChainId, toChainId])
480
+
481
+ // Contract detection queries
482
+ const { data: isRecipientContract } = useQuery({
483
+ queryKey: ["isContract", "recipient", effectiveToAddress, toChainId],
484
+ queryFn: async () => {
485
+ if (!effectiveToAddress || !toChainId) return null
486
+ return getIsContract(effectiveToAddress as `0x${string}`, toChainId)
487
+ },
488
+ enabled: !!effectiveToAddress && !!toChainId,
489
+ staleTime: 60 * 1000, // Cache for 1 minute
490
+ })
491
+
492
+ const { data: isSenderContractOnOrigin } = useQuery({
493
+ queryKey: [
494
+ "isContract",
495
+ "senderOrigin",
496
+ walletClient?.account?.address,
497
+ fromChainId,
498
+ ],
499
+ queryFn: async () => {
500
+ if (!walletClient?.account?.address || !fromChainId) return null
501
+ return getIsContract(
502
+ walletClient.account.address as `0x${string}`,
503
+ fromChainId,
504
+ )
505
+ },
506
+ enabled: !!walletClient?.account?.address && !!fromChainId,
507
+ staleTime: 60 * 1000,
508
+ })
509
+
510
+ const { data: isSenderContractOnDestination } = useQuery({
511
+ queryKey: [
512
+ "isContract",
513
+ "senderDestination",
514
+ walletClient?.account?.address,
515
+ toChainId,
516
+ ],
517
+ queryFn: async () => {
518
+ if (!walletClient?.account?.address || !toChainId) return null
519
+ return getIsContract(
520
+ walletClient.account.address as `0x${string}`,
521
+ toChainId,
522
+ )
523
+ },
524
+ enabled: !!walletClient?.account?.address && !!toChainId,
525
+ staleTime: 60 * 1000,
526
+ })
527
+
377
528
  const { data, isLoading, error, refetch } = useQuery({
378
529
  queryKey: [
379
530
  "prepareSend",
@@ -381,8 +532,8 @@ export function useQuote({
381
532
  fromChainId,
382
533
  toTokenAddress,
383
534
  toChainId,
384
- swapAmount?.toString(),
385
- toRecipient,
535
+ debouncedSwapAmount?.toString(),
536
+ effectiveToAddress,
386
537
  toCalldata,
387
538
  tradeType,
388
539
  slippageTolerance,
@@ -390,6 +541,7 @@ export function useQuote({
390
541
  bridgeProvider,
391
542
  apiKey,
392
543
  isSmartWallet,
544
+ fundMethod,
393
545
  ],
394
546
  queryFn: async () => {
395
547
  try {
@@ -406,8 +558,8 @@ export function useQuote({
406
558
  !trailsClient ||
407
559
  !fromTokenAddress ||
408
560
  !toTokenAddress ||
409
- !swapAmount ||
410
- !toRecipient ||
561
+ !debouncedSwapAmount ||
562
+ !effectiveToAddress ||
411
563
  !fromChainId ||
412
564
  !toChainId ||
413
565
  !indexerGatewayClient
@@ -612,15 +764,22 @@ export function useQuote({
612
764
  const destinationTokenSymbol = destinationToken?.symbol ?? ""
613
765
  const originTokenSymbol = originToken?.symbol ?? ""
614
766
 
767
+ // For qr-code and exchange fund methods, use a default amount of 100 tokens
768
+ // since the user will deposit from an external wallet (their connected wallet balance may be 0)
769
+ const effectiveOriginTokenBalance =
770
+ fundMethod === "qr-code" || fundMethod === "exchange"
771
+ ? parseUnits("100", sourceTokenDecimals).toString()
772
+ : originTokenBalanceAmount
773
+
615
774
  const options: PrepareSendOptions = {
616
775
  account: walletClient.account!,
617
776
  originTokenAddress: fromTokenAddress,
618
777
  originChainId: fromChainId,
619
- originTokenBalance: originTokenBalanceAmount,
778
+ originTokenBalance: effectiveOriginTokenBalance,
620
779
  destinationChainId: toChainId,
621
- recipient: toRecipient,
780
+ recipient: effectiveToAddress,
622
781
  destinationTokenAddress: toTokenAddress,
623
- swapAmount: swapAmount.toString(),
782
+ swapAmount: debouncedSwapAmount.toString(),
624
783
  tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
625
784
  originTokenSymbol: originTokenSymbol,
626
785
  destinationTokenSymbol: destinationTokenSymbol,
@@ -629,12 +788,10 @@ export function useQuote({
629
788
  trailsClient,
630
789
  sourceTokenDecimals,
631
790
  destinationTokenDecimals,
632
- fee: "0",
633
- dryMode: false,
634
791
  onTransactionStateChange: onStatusUpdate ?? (() => {}),
635
792
  slippageTolerance: slippageTolerance?.toString(),
636
- swapProvider: swapProvider,
637
- bridgeProvider: bridgeProvider,
793
+ swapProvider: effectiveSwapProvider,
794
+ bridgeProvider: effectiveBridgeProvider,
638
795
  paymasterUrl: paymasterUrl,
639
796
  selectedFeeOption: selectedFeeOption ?? null,
640
797
  abortSignal: combinedAbortSignal,
@@ -649,57 +806,119 @@ export function useQuote({
649
806
  isSmartWallet: isSmartWallet ?? undefined,
650
807
  trailsApiKey,
651
808
  trailsApiUrl,
809
+ fundMethod: fundMethod ?? undefined,
652
810
  }
653
811
 
654
812
  logger.console.log("[trails-sdk] options", options)
655
813
 
656
- const { quote: prepareSendQuote, send } = await prepareSend(options)
814
+ const {
815
+ quote: prepareSendQuote,
816
+ send: prepareSendSend,
817
+ feeOptions: prepareSendFeeOptions,
818
+ } = await prepareSend(options)
657
819
 
658
- const quote = {
820
+ const quote: Quote = {
821
+ // Backward compatible fields
659
822
  fromAmount: prepareSendQuote.originAmount,
660
823
  toAmount: prepareSendQuote.destinationAmount,
661
824
  fromAmountMin: prepareSendQuote.originAmountMin,
662
825
  toAmountMin: prepareSendQuote.destinationAmountMin,
826
+ fromAmountUsdDisplay: prepareSendQuote.originAmountUsdDisplay ?? "",
827
+ toAmountUsdDisplay:
828
+ prepareSendQuote.destinationAmountUsdDisplay ?? "",
829
+ // PrepareSendQuote compatible fields
830
+ originAmount: prepareSendQuote.originAmount,
831
+ originAmountMin: prepareSendQuote.originAmountMin,
832
+ destinationAmount: prepareSendQuote.destinationAmount,
833
+ destinationAmountMin: prepareSendQuote.destinationAmountMin,
834
+ originAmountUsdDisplay: prepareSendQuote.originAmountUsdDisplay ?? "",
835
+ destinationAmountUsdDisplay:
836
+ prepareSendQuote.destinationAmountUsdDisplay ?? "",
837
+ originAmountDisplay: prepareSendQuote.originAmountDisplay ?? "",
838
+ destinationAmountDisplay:
839
+ prepareSendQuote.destinationAmountDisplay ?? "",
840
+ originAmountMinDisplay: prepareSendQuote.originAmountMinDisplay ?? "",
841
+ originAmountMinUsdFormatted:
842
+ prepareSendQuote.originAmountMinUsdFormatted ?? "",
843
+ originAmountMinUsdDisplay:
844
+ prepareSendQuote.originAmountMinUsdDisplay ?? "",
845
+ destinationAmountMinDisplay:
846
+ prepareSendQuote.destinationAmountMinDisplay ?? "",
847
+ destinationAmountMinUsdFormatted:
848
+ prepareSendQuote.destinationAmountMinUsdFormatted ?? "",
849
+ destinationAmountMinUsdDisplay:
850
+ prepareSendQuote.destinationAmountMinUsdDisplay ?? "",
851
+ originAmountUsdFormatted:
852
+ prepareSendQuote.originAmountUsdFormatted ?? "",
853
+ destinationAmountUsdFormatted:
854
+ prepareSendQuote.destinationAmountUsdFormatted ?? "",
855
+ // Common fields
663
856
  originToken: prepareSendQuote.originToken,
664
857
  destinationToken: prepareSendQuote.destinationToken,
665
858
  originChain: prepareSendQuote.originChain,
666
859
  destinationChain: prepareSendQuote.destinationChain,
667
860
  fees: prepareSendQuote.fees,
668
861
  priceImpact: prepareSendQuote.priceImpact,
669
- priceImpactUsd: prepareSendQuote.priceImpactUsd ?? undefined,
670
- priceImpactUsdDisplay:
671
- prepareSendQuote.priceImpactUsdDisplay ?? undefined,
862
+ priceImpactUsd: prepareSendQuote.priceImpactUsd ?? null,
863
+ priceImpactUsdDisplay: prepareSendQuote.priceImpactUsdDisplay ?? "",
672
864
  completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
865
+ completionEstimateDisplay:
866
+ prepareSendQuote.completionEstimateDisplay ?? "",
673
867
  slippageTolerance: prepareSendQuote.slippageTolerance,
674
- transactionStates: prepareSendQuote.transactionStates,
675
- originTokenRate: prepareSendQuote.originTokenRate,
676
- destinationTokenRate: prepareSendQuote.destinationTokenRate,
677
- routeProviders: prepareSendQuote.routeProviders,
678
- fromAmountUsdDisplay:
679
- prepareSendQuote.originAmountUsdDisplay ?? undefined,
680
- toAmountUsdDisplay:
681
- prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
682
- gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
683
- gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
684
- gasCost: prepareSendQuote.gasCost ?? undefined,
685
- gasCostFormatted: prepareSendQuote.gasCostFormatted ?? undefined,
686
- totalFeesUsd: prepareSendQuote.grandTotalUsd ?? undefined,
687
- totalFeesUsdDisplay:
688
- prepareSendQuote.grandTotalUsdDisplay ?? undefined,
689
- totalGasUsd: prepareSendQuote.totalGasUsd ?? undefined,
690
- totalGasUsdDisplay: prepareSendQuote.totalGasUsdDisplay ?? undefined,
691
- allProviderFeesUsd: prepareSendQuote.allProviderFeesUsd ?? undefined,
692
- allProviderFeesUsdDisplay:
693
- prepareSendQuote.allProviderFeesUsdDisplay ?? undefined,
868
+ transactionStates: prepareSendQuote.transactionStates ?? [],
869
+ originTokenRate: prepareSendQuote.originTokenRate ?? "",
870
+ destinationTokenRate: prepareSendQuote.destinationTokenRate ?? "",
871
+ routeProviders: prepareSendQuote.routeProviders ?? [],
872
+ gasCostUsd: prepareSendQuote.gasCostUsd ?? null,
873
+ gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? "",
874
+ gasCost: prepareSendQuote.gasCost ?? "",
875
+ gasCostFormatted: prepareSendQuote.gasCostFormatted ?? "",
876
+ totalFeesUsd: prepareSendQuote.totalFeesUsd ?? null,
877
+ totalFeesUsdDisplay: prepareSendQuote.totalFeesUsdDisplay ?? "",
878
+ totalGasUsd: prepareSendQuote.totalGasUsd ?? null,
879
+ totalGasUsdDisplay: prepareSendQuote.totalGasUsdDisplay ?? "",
880
+ intentId: prepareSendQuote.intentId ?? null,
881
+ originAmountFormatted: prepareSendQuote.originAmountFormatted ?? "",
882
+ destinationAmountFormatted:
883
+ prepareSendQuote.destinationAmountFormatted ?? "",
884
+ originDepositAddress: prepareSendQuote.originDepositAddress ?? "",
885
+ destinationDepositAddress:
886
+ prepareSendQuote.destinationDepositAddress ?? "",
887
+ destinationAddress: prepareSendQuote.destinationAddress ?? "",
888
+ destinationCalldata: prepareSendQuote.destinationCalldata ?? "",
889
+ // Fee breakdown fields
890
+ trailsFeeBreakdown: prepareSendQuote.trailsFeeBreakdown ?? null,
891
+ originGasUsd: prepareSendQuote.originGasUsd ?? null,
892
+ originGasUsdDisplay: prepareSendQuote.originGasUsdDisplay ?? "",
893
+ destinationGasUsd: prepareSendQuote.destinationGasUsd ?? null,
894
+ destinationGasUsdDisplay:
895
+ prepareSendQuote.destinationGasUsdDisplay ?? "",
896
+ providerFeeUsd: prepareSendQuote.providerFeeUsd ?? null,
897
+ providerFeeUsdDisplay: prepareSendQuote.providerFeeUsdDisplay ?? "",
898
+ trailsFeeUsd: prepareSendQuote.trailsFeeUsd ?? null,
899
+ trailsFeeUsdDisplay: prepareSendQuote.trailsFeeUsdDisplay ?? "",
900
+ totalProviderFeesUsd: prepareSendQuote.totalProviderFeesUsd ?? null,
901
+ totalProviderFeesUsdDisplay:
902
+ prepareSendQuote.totalProviderFeesUsdDisplay ?? "",
903
+ noSufficientBalance: prepareSendQuote.noSufficientBalance ?? false,
694
904
  }
695
905
 
696
- const swap = async (): Promise<SwapReturn> => {
906
+ const sendTransaction = async (
907
+ options?: SendOptions,
908
+ ): Promise<SwapReturn> => {
909
+ // Use fee option from send() call if provided, otherwise fall back to prop value
910
+ const effectiveFeeOption =
911
+ options?.selectedFeeOption !== undefined
912
+ ? options.selectedFeeOption
913
+ : (selectedFeeOption ?? null)
914
+
697
915
  const {
698
916
  depositUserTxnReceipt,
699
917
  destinationIntentTransaction,
700
918
  totalCompletionSeconds,
701
- } = await send({
702
- selectedFeeOption: selectedFeeOption ?? null,
919
+ } = await prepareSendSend({
920
+ selectedFeeOption: effectiveFeeOption,
921
+ onOriginSend: options?.onOriginSend,
703
922
  })
704
923
 
705
924
  return {
@@ -725,7 +944,8 @@ export function useQuote({
725
944
 
726
945
  return {
727
946
  quote,
728
- swap,
947
+ send: sendTransaction,
948
+ feeOptions: prepareSendFeeOptions?.feeOptions ?? [],
729
949
  }
730
950
  } catch (error) {
731
951
  logger.console.error(
@@ -741,8 +961,8 @@ export function useQuote({
741
961
  trailsClient &&
742
962
  fromTokenAddress &&
743
963
  toTokenAddress &&
744
- swapAmount &&
745
- toRecipient &&
964
+ debouncedSwapAmount &&
965
+ effectiveToAddress &&
746
966
  fromChainId &&
747
967
  toChainId &&
748
968
  indexerGatewayClient &&
@@ -758,9 +978,22 @@ export function useQuote({
758
978
  refetchOnReconnect: true, // Refetch when network reconnects
759
979
  })
760
980
 
981
+ const sendFn = data?.send || null
982
+
983
+ // Create deprecated swap wrapper
984
+ const swapFn = sendFn
985
+ ? async (options?: SendOptions) => {
986
+ logger.console.warn(
987
+ "[trails-sdk] [useQuote] Warning: 'swap' is deprecated, use 'send' instead",
988
+ )
989
+ return sendFn(options)
990
+ }
991
+ : null
992
+
761
993
  return {
762
994
  quote: data?.quote || null,
763
- swap: data?.swap || null,
995
+ send: sendFn,
996
+ swap: swapFn,
764
997
  isLoadingQuote: isLoading,
765
998
  quoteError: error,
766
999
  quoteErrorPrettified: getPrettifiedErrorMessage(error),
@@ -783,5 +1016,10 @@ export function useQuote({
783
1016
  abortControllerRegistry.getAll(),
784
1017
  )
785
1018
  },
1019
+ feeOptions: data?.feeOptions || [],
1020
+ // Contract detection results
1021
+ isRecipientContract: isRecipientContract ?? null,
1022
+ isSenderContractOnOrigin: isSenderContractOnOrigin ?? null,
1023
+ isSenderContractOnDestination: isSenderContractOnDestination ?? null,
786
1024
  }
787
1025
  }