0xtrails 0.2.5 → 0.2.6

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 (116) hide show
  1. package/dist/abortController.d.ts +8 -0
  2. package/dist/abortController.d.ts.map +1 -0
  3. package/dist/{ccip-CXlshvBY.js → ccip-Xjh9d1gb.js} +7 -7
  4. package/dist/constants.d.ts +2 -0
  5. package/dist/constants.d.ts.map +1 -1
  6. package/dist/fees.d.ts +19 -0
  7. package/dist/fees.d.ts.map +1 -0
  8. package/dist/{index-_QuyGrjU.js → index-BnhdZ8Ho.js} +34769 -34247
  9. package/dist/index.js +726 -520
  10. package/dist/prepareSend.d.ts +11 -77
  11. package/dist/prepareSend.d.ts.map +1 -1
  12. package/dist/transactions.d.ts +4 -2
  13. package/dist/transactions.d.ts.map +1 -1
  14. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts +4 -0
  15. package/dist/widget/components/AccountIntentTransactionHistoryButton.d.ts.map +1 -0
  16. package/dist/widget/components/AccountSettings.d.ts.map +1 -1
  17. package/dist/widget/components/ChainFilterDropdown.d.ts.map +1 -1
  18. package/dist/widget/components/ClassicSwap.d.ts +2 -2
  19. package/dist/widget/components/ClassicSwap.d.ts.map +1 -1
  20. package/dist/widget/components/ConnectWallet.d.ts.map +1 -1
  21. package/dist/widget/components/DynamicInputStyles.d.ts +18 -0
  22. package/dist/widget/components/DynamicInputStyles.d.ts.map +1 -0
  23. package/dist/widget/components/Earn.d.ts +2 -2
  24. package/dist/widget/components/Earn.d.ts.map +1 -1
  25. package/dist/widget/components/ErrorAnimationIcon.d.ts +2 -0
  26. package/dist/widget/components/ErrorAnimationIcon.d.ts.map +1 -0
  27. package/dist/widget/components/FeeBreakdown.d.ts +9 -0
  28. package/dist/widget/components/FeeBreakdown.d.ts.map +1 -0
  29. package/dist/widget/components/Fund.d.ts +2 -2
  30. package/dist/widget/components/Fund.d.ts.map +1 -1
  31. package/dist/widget/components/FundSwap.d.ts +2 -2
  32. package/dist/widget/components/FundSwap.d.ts.map +1 -1
  33. package/dist/widget/components/FundingMethodSelectorButton.d.ts.map +1 -1
  34. package/dist/widget/components/Identicon.d.ts.map +1 -1
  35. package/dist/widget/components/MeshConnectExchanges.d.ts +0 -3
  36. package/dist/widget/components/MeshConnectExchanges.d.ts.map +1 -1
  37. package/dist/widget/components/Modal.d.ts.map +1 -1
  38. package/dist/widget/components/Pay.d.ts +2 -2
  39. package/dist/widget/components/Pay.d.ts.map +1 -1
  40. package/dist/widget/components/PoolDeposit.d.ts +3 -2
  41. package/dist/widget/components/PoolDeposit.d.ts.map +1 -1
  42. package/dist/widget/components/PoolWithdraw.d.ts +3 -2
  43. package/dist/widget/components/PoolWithdraw.d.ts.map +1 -1
  44. package/dist/widget/components/QuoteDetails.d.ts +1 -0
  45. package/dist/widget/components/QuoteDetails.d.ts.map +1 -1
  46. package/dist/widget/components/Receipt.d.ts.map +1 -1
  47. package/dist/widget/components/RecipientSelectorButton.d.ts.map +1 -1
  48. package/dist/widget/components/ScreenHeader.d.ts.map +1 -1
  49. package/dist/widget/components/Swap.d.ts +2 -2
  50. package/dist/widget/components/Swap.d.ts.map +1 -1
  51. package/dist/widget/components/ThemeProvider.d.ts.map +1 -1
  52. package/dist/widget/components/TokenDisplayNonSelectable.d.ts +11 -0
  53. package/dist/widget/components/TokenDisplayNonSelectable.d.ts.map +1 -0
  54. package/dist/widget/components/TokenSelector.d.ts.map +1 -1
  55. package/dist/widget/components/TokenSelectorButton.d.ts.map +1 -1
  56. package/dist/widget/components/Tooltip.d.ts +9 -0
  57. package/dist/widget/components/Tooltip.d.ts.map +1 -0
  58. package/dist/widget/components/WaasFeeOptions.d.ts +1 -0
  59. package/dist/widget/components/WaasFeeOptions.d.ts.map +1 -1
  60. package/dist/widget/components/WalletConfirmation.d.ts.map +1 -1
  61. package/dist/widget/components/WalletConnect.d.ts.map +1 -1
  62. package/dist/widget/css/compiled.css +1 -1
  63. package/dist/widget/hooks/useQuote.d.ts +83 -0
  64. package/dist/widget/hooks/useQuote.d.ts.map +1 -0
  65. package/dist/widget/hooks/useSendForm.d.ts +2 -2
  66. package/dist/widget/hooks/useSendForm.d.ts.map +1 -1
  67. package/dist/widget/index.js +2 -2
  68. package/dist/widget/widget.d.ts +5 -0
  69. package/dist/widget/widget.d.ts.map +1 -1
  70. package/package.json +2 -2
  71. package/src/abortController.ts +35 -0
  72. package/src/constants.ts +3 -0
  73. package/src/fees.ts +199 -0
  74. package/src/prepareSend.ts +225 -398
  75. package/src/trails.ts +3 -3
  76. package/src/transactions.ts +62 -18
  77. package/src/widget/compiled.css +1 -1
  78. package/src/widget/components/AccountIntentTransactionHistoryButton.tsx +22 -0
  79. package/src/widget/components/AccountSettings.tsx +48 -36
  80. package/src/widget/components/ChainFilterDropdown.tsx +24 -3
  81. package/src/widget/components/ClassicSwap.tsx +24 -62
  82. package/src/widget/components/ConnectWallet.tsx +4 -1
  83. package/src/widget/components/ConnectedWallets.tsx +21 -21
  84. package/src/widget/components/DynamicInputStyles.tsx +76 -0
  85. package/src/widget/components/Earn.tsx +34 -29
  86. package/src/widget/components/ErrorAnimationIcon.tsx +130 -0
  87. package/src/widget/components/FeeBreakdown.tsx +155 -0
  88. package/src/widget/components/Fund.tsx +10 -26
  89. package/src/widget/components/FundSwap.tsx +2 -2
  90. package/src/widget/components/FundingMethodSelectorButton.tsx +24 -14
  91. package/src/widget/components/Identicon.tsx +164 -95
  92. package/src/widget/components/MeshConnectExchanges.tsx +2 -15
  93. package/src/widget/components/Modal.tsx +0 -12
  94. package/src/widget/components/Pay.tsx +65 -63
  95. package/src/widget/components/PoolDeposit.tsx +206 -230
  96. package/src/widget/components/PoolWithdraw.tsx +219 -238
  97. package/src/widget/components/PriceImpactWarning.tsx +1 -1
  98. package/src/widget/components/QuoteDetails.tsx +25 -8
  99. package/src/widget/components/Receipt.tsx +16 -2
  100. package/src/widget/components/RecipientSelectorButton.tsx +7 -5
  101. package/src/widget/components/Recipients.tsx +1 -1
  102. package/src/widget/components/ScreenHeader.tsx +60 -36
  103. package/src/widget/components/Swap.tsx +2 -2
  104. package/src/widget/components/ThemeProvider.tsx +2 -1
  105. package/src/widget/components/TokenDisplayNonSelectable.tsx +40 -0
  106. package/src/widget/components/TokenImage.tsx +1 -1
  107. package/src/widget/components/TokenSelector.tsx +62 -53
  108. package/src/widget/components/TokenSelectorButton.tsx +38 -15
  109. package/src/widget/components/Tooltip.tsx +51 -0
  110. package/src/widget/components/TransferPendingVertical.tsx +1 -1
  111. package/src/widget/components/WaasFeeOptions.tsx +124 -5
  112. package/src/widget/components/WalletConfirmation.tsx +23 -13
  113. package/src/widget/components/WalletConnect.tsx +93 -29
  114. package/src/widget/hooks/useQuote.ts +413 -0
  115. package/src/widget/hooks/useSendForm.ts +8 -4
  116. package/src/widget/widget.tsx +175 -190
@@ -0,0 +1,413 @@
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 { useAPIClient } from "../../apiClient.js"
6
+ import { useIndexerGatewayClient } from "../../indexerClient.js"
7
+ import { getTokenBalancesWithPrices } from "../../tokenBalances.js"
8
+ import { logger } from "../../logger.js"
9
+ import { getSequenceEnv } from "../../config.js"
10
+ import { useSupportedTokens } from "../../tokens.js"
11
+ import { useTrailsClient } from "../../trailsClient.js"
12
+ import { getExplorerUrl } from "../../explorer.js"
13
+ import { getFullErrorMessage } from "../../error.js"
14
+ import { prepareSend } from "../../prepareSend.js"
15
+ import { abortControllerRegistry } from "../../abortController.js"
16
+ import { useRelayers } from "../../relayer.js"
17
+ import { TradeType } from "../../prepareSend.js"
18
+ import type { Chain } from "../../chains.js"
19
+ import type { SupportedToken } from "../../tokens.js"
20
+ import type { TransactionState } from "../../transactions.js"
21
+ import type { PrepareSendFees } from "../../prepareSend.js"
22
+ import type { MetaTxnReceipt } from "../../relayer.js"
23
+ import type { RelayerEnv } from "../../relayer.js"
24
+ import type { SequenceEnv } from "../../config.js"
25
+
26
+ export type UseQuoteProps = {
27
+ walletClient?: any // TODO: fix this, has to do with viem/wagmi versions
28
+ fromTokenAddress?: string | null
29
+ fromChainId?: number | null
30
+ toTokenAddress?: string | null
31
+ toChainId?: number | null
32
+ toCalldata?: string | null
33
+ swapAmount?: string | bigint
34
+ toRecipient?: string | null
35
+ tradeType?: TradeType | null
36
+ slippageTolerance?: string | number | null
37
+ onStatusUpdate?: ((transactionStates: TransactionState[]) => void) | null
38
+ quoteProvider?: string | null
39
+ gasless?: boolean
40
+ paymasterUrl?: string
41
+ selectedFeeToken?: {
42
+ tokenAddress: string
43
+ tokenSymbol?: string
44
+ } | null
45
+ abortSignal?: AbortSignal
46
+ }
47
+
48
+ export type SwapReturn = {
49
+ originTransaction: {
50
+ transactionHash?: string | null
51
+ explorerUrl?: string | null
52
+ receipt: TransactionReceipt | MetaTxnReceipt | null
53
+ }
54
+ destinationTransaction: {
55
+ transactionHash?: string | null
56
+ explorerUrl?: string | null
57
+ receipt: MetaTxnReceipt | null
58
+ }
59
+ totalCompletionSeconds?: number
60
+ }
61
+
62
+ export type QuoteProviderInfo = {
63
+ id: string
64
+ name: string
65
+ url: string
66
+ }
67
+
68
+ export type Quote = {
69
+ fromAmount: string
70
+ fromAmountMin: string
71
+ toAmount: string
72
+ toAmountMin: string
73
+ originToken: SupportedToken
74
+ destinationToken: SupportedToken
75
+ originChain: Chain
76
+ destinationChain: Chain
77
+ fees: PrepareSendFees
78
+ slippageTolerance: string
79
+ priceImpact: string
80
+ completionEstimateSeconds: number
81
+ transactionStates?: TransactionState[]
82
+ originTokenRate?: string
83
+ quoteProvider?: QuoteProviderInfo | null
84
+ destinationTokenRate?: string
85
+ fromAmountUsdDisplay?: string
86
+ toAmountUsdDisplay?: string
87
+ gasCostUsd?: number
88
+ gasCostUsdDisplay?: string
89
+ gasCost?: string
90
+ gasCostFormatted?: string
91
+ }
92
+
93
+ export type UseQuoteReturn = {
94
+ quote: Quote | null
95
+ swap: (() => Promise<SwapReturn | null>) | null
96
+ isLoadingQuote: boolean
97
+ quoteError: unknown
98
+ refetchQuote: () => void
99
+ abort: () => void
100
+ }
101
+
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
+ gasless,
116
+ paymasterUrl,
117
+ selectedFeeToken,
118
+ relayerEnv,
119
+ nodeGatewayEnv,
120
+ abortSignal: externalAbortSignal,
121
+ }: Partial<
122
+ UseQuoteProps & { relayerEnv?: RelayerEnv; nodeGatewayEnv?: SequenceEnv }
123
+ > = {}): UseQuoteReturn {
124
+ // Set node gateway environment override for this quote session
125
+ if (nodeGatewayEnv) {
126
+ ;(globalThis as any).__testNodeGatewayEnv = nodeGatewayEnv
127
+ }
128
+
129
+ // Create abort controller for this hook instance
130
+ const abortControllerRef = useRef<AbortController>(new AbortController())
131
+
132
+ // Combine external abort signal with internal abort controller
133
+ const combinedAbortSignal = externalAbortSignal
134
+ ? (() => {
135
+ const controller = new AbortController()
136
+
137
+ // Listen to external abort signal
138
+ externalAbortSignal.addEventListener("abort", () => {
139
+ controller.abort()
140
+ abortControllerRef.current?.abort()
141
+ })
142
+
143
+ // Listen to internal abort controller
144
+ abortControllerRef.current?.signal.addEventListener("abort", () => {
145
+ controller.abort()
146
+ })
147
+
148
+ return controller.signal
149
+ })()
150
+ : abortControllerRef.current.signal
151
+
152
+ const apiClient = useAPIClient()
153
+ const trailsClient = useTrailsClient()
154
+ const { getRelayer } = useRelayers({
155
+ env: relayerEnv || (getSequenceEnv() as RelayerEnv),
156
+ })
157
+ const indexerGatewayClient = useIndexerGatewayClient()
158
+
159
+ const { supportedTokens } = useSupportedTokens()
160
+
161
+ const { data, isLoading, error, refetch } = useQuery({
162
+ queryKey: [
163
+ "quote",
164
+ fromTokenAddress,
165
+ fromChainId,
166
+ toTokenAddress,
167
+ toChainId,
168
+ swapAmount?.toString(),
169
+ toRecipient,
170
+ toCalldata,
171
+ tradeType,
172
+ slippageTolerance,
173
+ quoteProvider,
174
+ ],
175
+ queryFn: async () => {
176
+ try {
177
+ // Reset the abort controller for new queries to ensure fresh state
178
+ if (abortControllerRef.current.signal.aborted) {
179
+ logger.console.log(
180
+ "[trails-sdk] Resetting aborted controller for new query",
181
+ )
182
+ abortControllerRef.current = new AbortController()
183
+ }
184
+
185
+ if (
186
+ !walletClient ||
187
+ !apiClient ||
188
+ !trailsClient ||
189
+ !fromTokenAddress ||
190
+ !toTokenAddress ||
191
+ !swapAmount ||
192
+ !toRecipient ||
193
+ !fromChainId ||
194
+ !toChainId ||
195
+ !indexerGatewayClient
196
+ ) {
197
+ return null
198
+ }
199
+
200
+ // Get token balance using async method
201
+ const { balances } = await getTokenBalancesWithPrices({
202
+ account: walletClient.account!.address,
203
+ indexerGatewayClient,
204
+ apiClient,
205
+ })
206
+
207
+ const originTokenBalance = balances.find(
208
+ (b) =>
209
+ b.chainId === fromChainId &&
210
+ (b.contractAddress?.toLowerCase() ===
211
+ fromTokenAddress.toLowerCase() ||
212
+ (!b.contractAddress && fromTokenAddress === zeroAddress)),
213
+ )
214
+
215
+ const originTokenBalanceAmount = originTokenBalance?.balance ?? "0"
216
+ const destinationRelayer = getRelayer(toChainId)
217
+ const originRelayer = getRelayer(fromChainId)
218
+
219
+ // Note: Disable this check for now to allow fetching a quote even when the origin balance is zero
220
+ // if (originTokenBalanceAmount === "0") {
221
+ // return null
222
+ // }
223
+
224
+ // logger.console.log("supportedTokens", supportedTokens)
225
+
226
+ const originToken = supportedTokens?.find(
227
+ (token) =>
228
+ token.contractAddress?.toLowerCase() ===
229
+ fromTokenAddress?.toLowerCase() && token.chainId === fromChainId,
230
+ )
231
+ const destinationToken = supportedTokens?.find(
232
+ (token) =>
233
+ token.contractAddress?.toLowerCase() ===
234
+ toTokenAddress?.toLowerCase() && token.chainId === toChainId,
235
+ )
236
+
237
+ const sourceTokenDecimals = originToken?.decimals
238
+ if (!sourceTokenDecimals) {
239
+ logger.console.error(
240
+ "[trails-sdk] [useQuote] Missing source token decimals:",
241
+ {
242
+ originToken,
243
+ fromTokenAddress,
244
+ fromChainId,
245
+ },
246
+ )
247
+ throw new Error("Source token decimals not found")
248
+ }
249
+ const destinationTokenDecimals = destinationToken?.decimals
250
+ if (!destinationTokenDecimals) {
251
+ logger.console.error(
252
+ "[trails-sdk] Missing destination token decimals:",
253
+ {
254
+ destinationToken,
255
+ toTokenAddress,
256
+ toChainId,
257
+ },
258
+ )
259
+ throw new Error("Destination token decimals not found")
260
+ }
261
+ const destinationTokenSymbol = destinationToken?.symbol ?? ""
262
+ const originTokenSymbol = originToken?.symbol ?? ""
263
+
264
+ const options = {
265
+ account: walletClient.account!,
266
+ originTokenAddress: fromTokenAddress,
267
+ originChainId: fromChainId,
268
+ originTokenBalance: originTokenBalanceAmount,
269
+ destinationChainId: toChainId,
270
+ recipient: toRecipient,
271
+ destinationTokenAddress: toTokenAddress,
272
+ swapAmount: swapAmount.toString(),
273
+ tradeType: tradeType ?? TradeType.EXACT_OUTPUT,
274
+ originTokenSymbol: originTokenSymbol,
275
+ destinationTokenSymbol: destinationTokenSymbol,
276
+ destinationCalldata: toCalldata as string,
277
+ client: walletClient,
278
+ apiClient,
279
+ trailsClient,
280
+ originRelayer,
281
+ destinationRelayer,
282
+ sourceTokenDecimals,
283
+ destinationTokenDecimals,
284
+ fee: "0",
285
+ dryMode: false,
286
+ onTransactionStateChange: onStatusUpdate ?? (() => {}),
287
+ slippageTolerance: slippageTolerance?.toString(),
288
+ quoteProvider: quoteProvider,
289
+ gasless: gasless ?? false,
290
+ paymasterUrl: paymasterUrl,
291
+ selectedFeeToken: selectedFeeToken ?? undefined,
292
+ abortSignal: combinedAbortSignal,
293
+ }
294
+
295
+ logger.console.log("[trails-sdk] options", options)
296
+
297
+ const { quote: prepareSendQuote, send } = await prepareSend(options)
298
+
299
+ const quote = {
300
+ fromAmount: prepareSendQuote.originAmount,
301
+ toAmount: prepareSendQuote.destinationAmount,
302
+ fromAmountMin: prepareSendQuote.originAmountMin,
303
+ toAmountMin: prepareSendQuote.destinationAmountMin,
304
+ originToken: prepareSendQuote.originToken,
305
+ destinationToken: prepareSendQuote.destinationToken,
306
+ originChain: prepareSendQuote.originChain,
307
+ destinationChain: prepareSendQuote.destinationChain,
308
+ fees: prepareSendQuote.fees,
309
+ priceImpact: prepareSendQuote.priceImpact,
310
+ completionEstimateSeconds: prepareSendQuote.completionEstimateSeconds,
311
+ slippageTolerance: prepareSendQuote.slippageTolerance,
312
+ transactionStates: prepareSendQuote.transactionStates,
313
+ originTokenRate: prepareSendQuote.originTokenRate,
314
+ destinationTokenRate: prepareSendQuote.destinationTokenRate,
315
+ quoteProvider: prepareSendQuote.quoteProvider,
316
+ fromAmountUsdDisplay:
317
+ prepareSendQuote.originAmountUsdDisplay ?? undefined,
318
+ toAmountUsdDisplay:
319
+ prepareSendQuote.destinationAmountUsdDisplay ?? undefined,
320
+ gasCostUsd: prepareSendQuote.gasCostUsd ?? undefined,
321
+ gasCostUsdDisplay: prepareSendQuote.gasCostUsdDisplay ?? undefined,
322
+ gasCost: prepareSendQuote.gasCost ?? undefined,
323
+ gasCostFormatted: prepareSendQuote.gasCostFormatted ?? undefined,
324
+ }
325
+
326
+ const swap = async (): Promise<SwapReturn> => {
327
+ const {
328
+ originUserTxReceipt,
329
+ destinationMetaTxnReceipt,
330
+ totalCompletionSeconds,
331
+ } = await send({
332
+ selectedFeeToken: selectedFeeToken ?? undefined,
333
+ })
334
+
335
+ return {
336
+ originTransaction: {
337
+ transactionHash: originUserTxReceipt?.transactionHash,
338
+ explorerUrl: getExplorerUrl({
339
+ txHash: originUserTxReceipt?.transactionHash as string,
340
+ chainId: fromChainId,
341
+ }),
342
+ receipt: originUserTxReceipt,
343
+ },
344
+ destinationTransaction: {
345
+ transactionHash: destinationMetaTxnReceipt?.txnHash,
346
+ explorerUrl: getExplorerUrl({
347
+ txHash: destinationMetaTxnReceipt?.txnHash as string,
348
+ chainId: toChainId,
349
+ }),
350
+ receipt: destinationMetaTxnReceipt,
351
+ },
352
+ totalCompletionSeconds,
353
+ }
354
+ }
355
+
356
+ return {
357
+ quote,
358
+ swap,
359
+ }
360
+ } catch (error) {
361
+ logger.console.error(
362
+ "[trails-sdk] [useQuote] Error getting quote:",
363
+ error,
364
+ )
365
+ throw getFullErrorMessage(error)
366
+ }
367
+ },
368
+ // Prevent unnecessary refetching
369
+ enabled: Boolean(
370
+ walletClient &&
371
+ apiClient &&
372
+ fromTokenAddress &&
373
+ toTokenAddress &&
374
+ swapAmount &&
375
+ toRecipient &&
376
+ fromChainId &&
377
+ toChainId &&
378
+ indexerGatewayClient,
379
+ ),
380
+ staleTime: 30 * 1000, // Consider data fresh for 30 seconds
381
+ refetchOnWindowFocus: false, // Don't refetch when window regains focus
382
+ refetchOnMount: false, // Don't refetch on component remount if data exists
383
+ refetchInterval: false, // Disable automatic polling
384
+ retry: 2, // Limit retry attempts
385
+ refetchOnReconnect: true, // Refetch when network reconnects
386
+ })
387
+
388
+ return {
389
+ quote: data?.quote || null,
390
+ swap: data?.swap || null,
391
+ isLoadingQuote: isLoading,
392
+ quoteError: error,
393
+ refetchQuote: () => refetch(),
394
+ abort: () => {
395
+ logger.console.log("[trails-sdk] useQuote abort() called")
396
+ logger.console.log(
397
+ "[trails-sdk] Active operations before abort:",
398
+ abortControllerRegistry.getAll(),
399
+ )
400
+
401
+ // Abort the internal abort controller
402
+ abortControllerRef.current.abort()
403
+
404
+ // Also abort all active polling operations in prepareSend
405
+ abortControllerRegistry.abortAll()
406
+
407
+ logger.console.log(
408
+ "[trails-sdk] Abort completed, active operations:",
409
+ abortControllerRegistry.getAll(),
410
+ )
411
+ },
412
+ }
413
+ }
@@ -84,14 +84,14 @@ export type OnCompleteProps = {
84
84
  }
85
85
 
86
86
  export type UseSendProps = {
87
- account: Account
87
+ account?: Account
88
88
  toAmount?: string
89
89
  toRecipient?: string
90
90
  toChainId?: number
91
91
  toToken?: string
92
92
  toCalldata?: string
93
93
  refundAddress?: string
94
- walletClient: WalletClient
94
+ walletClient?: WalletClient
95
95
  onTransactionStateChange: (transactionStates: TransactionState[]) => void
96
96
  onError: (error: Error | string | null) => void
97
97
  onWaitingForWalletConfirm: (quote: PrepareSendQuote) => void
@@ -687,6 +687,7 @@ export function useSendForm({
687
687
  const getQuote = useCallback(async () => {
688
688
  // Only get quote if all required inputs are present
689
689
  if (
690
+ !account ||
690
691
  !amount ||
691
692
  !destinationTokenAddress ||
692
693
  !isValidRecipient ||
@@ -945,6 +946,7 @@ export function useSendForm({
945
946
  selectedToken?.chainId,
946
947
  selectedToken?.balance,
947
948
  selectedToken?.tokenPriceUsd,
949
+ recipient, // Add recipient to trigger quote re-fetch when it changes
948
950
  // selectedFeeToken is passed to send() at execution time, not needed here
949
951
  ])
950
952
 
@@ -958,7 +960,8 @@ export function useSendForm({
958
960
  }, [prepareSendResult, toAmountFormatted])
959
961
 
960
962
  const quotedDestinationAmountDisplay = useMemo(() => {
961
- return formatAmountDisplay(quotedDestinationAmount || "0")
963
+ const formattedAmount = formatAmount(quotedDestinationAmount || 0)
964
+ return Number(formattedAmount) === 0 ? "0" : formattedAmount
962
965
  }, [quotedDestinationAmount])
963
966
 
964
967
  // Set raw fee options in the hook whenever prepareSendResult changes
@@ -1158,6 +1161,7 @@ export function useSendForm({
1158
1161
 
1159
1162
  // Get button text based on recipient and calldata
1160
1163
  const buttonText = useMemo(() => {
1164
+ if (!account?.address) return "Connect your wallet"
1161
1165
  if (!selectedToken) return "Select a token"
1162
1166
  if (!amount) return "Enter an amount"
1163
1167
  if (!selectedDestToken?.symbol) return "Select a token"
@@ -1251,7 +1255,7 @@ export function useSendForm({
1251
1255
  amount,
1252
1256
  isValidRecipient,
1253
1257
  recipient,
1254
- account.address,
1258
+ account?.address,
1255
1259
  selectedDestToken?.symbol,
1256
1260
  isWaitingForWalletConfirm,
1257
1261
  isSubmitting,