@silentswap/react 0.0.41

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 (86) hide show
  1. package/README.md +48 -0
  2. package/dist/contexts/AssetsContext.d.ts +24 -0
  3. package/dist/contexts/AssetsContext.js +83 -0
  4. package/dist/contexts/BalancesContext.d.ts +28 -0
  5. package/dist/contexts/BalancesContext.js +533 -0
  6. package/dist/contexts/OrdersContext.d.ts +53 -0
  7. package/dist/contexts/OrdersContext.js +240 -0
  8. package/dist/contexts/PricesContext.d.ts +12 -0
  9. package/dist/contexts/PricesContext.js +109 -0
  10. package/dist/contexts/SilentSwapContext.d.ts +58 -0
  11. package/dist/contexts/SilentSwapContext.js +205 -0
  12. package/dist/hooks/silent/orderTrackingWebSocketManager.d.ts +48 -0
  13. package/dist/hooks/silent/orderTrackingWebSocketManager.js +284 -0
  14. package/dist/hooks/silent/solana-transaction.d.ts +60 -0
  15. package/dist/hooks/silent/solana-transaction.js +236 -0
  16. package/dist/hooks/silent/useAuth.d.ts +90 -0
  17. package/dist/hooks/silent/useAuth.js +269 -0
  18. package/dist/hooks/silent/useBridgeExecution.d.ts +39 -0
  19. package/dist/hooks/silent/useBridgeExecution.js +877 -0
  20. package/dist/hooks/silent/useOrderSigning.d.ts +34 -0
  21. package/dist/hooks/silent/useOrderSigning.js +133 -0
  22. package/dist/hooks/silent/useOrderTracking.d.ts +174 -0
  23. package/dist/hooks/silent/useOrderTracking.js +524 -0
  24. package/dist/hooks/silent/useQuoteCalculation.d.ts +50 -0
  25. package/dist/hooks/silent/useQuoteCalculation.js +331 -0
  26. package/dist/hooks/silent/useQuoteFetching.d.ts +18 -0
  27. package/dist/hooks/silent/useQuoteFetching.js +54 -0
  28. package/dist/hooks/silent/useRefund.d.ts +26 -0
  29. package/dist/hooks/silent/useRefund.js +134 -0
  30. package/dist/hooks/silent/useSilentClient.d.ts +16 -0
  31. package/dist/hooks/silent/useSilentClient.js +32 -0
  32. package/dist/hooks/silent/useSilentOrders.d.ts +174 -0
  33. package/dist/hooks/silent/useSilentOrders.js +73 -0
  34. package/dist/hooks/silent/useSilentQuote.d.ts +88 -0
  35. package/dist/hooks/silent/useSilentQuote.js +381 -0
  36. package/dist/hooks/silent/useWallet.d.ts +76 -0
  37. package/dist/hooks/silent/useWallet.js +203 -0
  38. package/dist/hooks/useAssetPrice.d.ts +8 -0
  39. package/dist/hooks/useAssetPrice.js +47 -0
  40. package/dist/hooks/useContacts.d.ts +52 -0
  41. package/dist/hooks/useContacts.js +259 -0
  42. package/dist/hooks/useEgressEstimates.d.ts +32 -0
  43. package/dist/hooks/useEgressEstimates.js +230 -0
  44. package/dist/hooks/useHiddenSwapFees.d.ts +22 -0
  45. package/dist/hooks/useHiddenSwapFees.js +81 -0
  46. package/dist/hooks/useOrderEstimates.d.ts +37 -0
  47. package/dist/hooks/useOrderEstimates.js +393 -0
  48. package/dist/hooks/useOutputAssetInfo.d.ts +12 -0
  49. package/dist/hooks/useOutputAssetInfo.js +38 -0
  50. package/dist/hooks/usePrices.d.ts +60 -0
  51. package/dist/hooks/usePrices.js +188 -0
  52. package/dist/hooks/useQuote.d.ts +73 -0
  53. package/dist/hooks/useQuote.js +507 -0
  54. package/dist/hooks/useResetSwapForm.d.ts +16 -0
  55. package/dist/hooks/useResetSwapForm.js +68 -0
  56. package/dist/hooks/useSlippageUsd.d.ts +11 -0
  57. package/dist/hooks/useSlippageUsd.js +19 -0
  58. package/dist/hooks/useSolanaAdapter.d.ts +15 -0
  59. package/dist/hooks/useSolanaAdapter.js +55 -0
  60. package/dist/hooks/useStatus.d.ts +25 -0
  61. package/dist/hooks/useStatus.js +60 -0
  62. package/dist/hooks/useSwap.d.ts +67 -0
  63. package/dist/hooks/useSwap.js +285 -0
  64. package/dist/hooks/useTransaction.d.ts +119 -0
  65. package/dist/hooks/useTransaction.js +353 -0
  66. package/dist/hooks/useTransactionAddress.d.ts +11 -0
  67. package/dist/hooks/useTransactionAddress.js +26 -0
  68. package/dist/hooks/useUsdValue.d.ts +7 -0
  69. package/dist/hooks/useUsdValue.js +19 -0
  70. package/dist/index.d.ts +54 -0
  71. package/dist/index.js +41 -0
  72. package/dist/stories/SilentSwapOverview.stories.d.ts +10 -0
  73. package/dist/stories/SilentSwapOverview.stories.js +364 -0
  74. package/dist/stories/useAuth.stories.d.ts +6 -0
  75. package/dist/stories/useAuth.stories.js +55 -0
  76. package/dist/stories/useSilentClient.stories.d.ts +9 -0
  77. package/dist/stories/useSilentClient.stories.js +39 -0
  78. package/dist/stories/useSilentOrders.stories.d.ts +1 -0
  79. package/dist/stories/useSilentOrders.stories.js +1 -0
  80. package/dist/stories/useSilentQuote.stories.d.ts +6 -0
  81. package/dist/stories/useSilentQuote.stories.js +267 -0
  82. package/dist/stories/useTransaction.stories.d.ts +6 -0
  83. package/dist/stories/useTransaction.stories.js +121 -0
  84. package/dist/utils/formatters.d.ts +33 -0
  85. package/dist/utils/formatters.js +82 -0
  86. package/package.json +67 -0
@@ -0,0 +1,524 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ /**
3
+ * Output stage enum matching Svelte's OutputStage (string values)
4
+ */
5
+ export var OutputStage;
6
+ (function (OutputStage) {
7
+ OutputStage["NONE"] = "NONE";
8
+ OutputStage["INIT"] = "INIT";
9
+ OutputStage["FUNDED"] = "FUNDED";
10
+ OutputStage["REDEEMED"] = "REDEEMED";
11
+ OutputStage["IBC_SENT"] = "IBC_SENT";
12
+ OutputStage["IBC_RCVD"] = "IBC_RCVD";
13
+ OutputStage["BRIDGE_SENT"] = "BRIDGE_SENT";
14
+ OutputStage["BRIDGE_CFRM"] = "BRIDGE_CFRM";
15
+ OutputStage["BRIDGE_RCVD"] = "BRIDGE_RCVD";
16
+ OutputStage["SWAP_USDC_GAS"] = "SWAP_USDC_GAS";
17
+ OutputStage["SWAP_USDC_TRG"] = "SWAP_USDC_TRG";
18
+ OutputStage["LTRL_TRG_SENT"] = "LTRL_TRG_SENT";
19
+ OutputStage["LTRL_TRG_RCVD"] = "LTRL_TRG_RCVD";
20
+ OutputStage["SWAP_TRG_DST"] = "SWAP_TRG_DST";
21
+ OutputStage["XFER_TRG_DST"] = "XFER_TRG_DST";
22
+ OutputStage["REFUND_NATIVE"] = "REFUND_NATIVE";
23
+ OutputStage["FINALIZED"] = "FINALIZED";
24
+ })(OutputStage || (OutputStage = {}));
25
+ /**
26
+ * Get human-readable status text from output stage
27
+ */
28
+ export function getStatusTextFromStage(stage) {
29
+ switch (stage) {
30
+ case OutputStage.NONE:
31
+ return 'Uncertain state';
32
+ case OutputStage.INIT:
33
+ return 'Verifying deposit';
34
+ case OutputStage.FUNDED:
35
+ return 'Initializing';
36
+ case OutputStage.REDEEMED:
37
+ return 'Anonymizing';
38
+ case OutputStage.IBC_SENT:
39
+ return 'Moving';
40
+ case OutputStage.IBC_RCVD:
41
+ return 'Staging';
42
+ case OutputStage.BRIDGE_SENT:
43
+ case OutputStage.BRIDGE_CFRM:
44
+ case OutputStage.BRIDGE_RCVD:
45
+ return 'Bridging';
46
+ case OutputStage.SWAP_USDC_GAS:
47
+ return 'Fueling';
48
+ case OutputStage.SWAP_USDC_TRG:
49
+ return 'Swapping';
50
+ case OutputStage.LTRL_TRG_SENT:
51
+ case OutputStage.LTRL_TRG_RCVD:
52
+ case OutputStage.SWAP_TRG_DST:
53
+ return 'Finalizing';
54
+ case OutputStage.XFER_TRG_DST:
55
+ case OutputStage.REFUND_NATIVE:
56
+ case OutputStage.FINALIZED:
57
+ return 'Complete';
58
+ default:
59
+ return 'Processing';
60
+ }
61
+ }
62
+ /**
63
+ * Get progress value (0-1) from output stage
64
+ */
65
+ export function getProgressFromStage(stage) {
66
+ switch (stage) {
67
+ case OutputStage.NONE:
68
+ return 0;
69
+ case OutputStage.INIT:
70
+ return 0;
71
+ case OutputStage.FUNDED:
72
+ return 0.1;
73
+ case OutputStage.REDEEMED:
74
+ return 0.15;
75
+ case OutputStage.IBC_SENT:
76
+ return 0.25;
77
+ case OutputStage.IBC_RCVD:
78
+ return 0.35;
79
+ case OutputStage.BRIDGE_SENT:
80
+ return 0.5;
81
+ case OutputStage.BRIDGE_CFRM:
82
+ return 0.6;
83
+ case OutputStage.BRIDGE_RCVD:
84
+ return 0.7;
85
+ case OutputStage.SWAP_USDC_GAS:
86
+ return 0.8;
87
+ case OutputStage.SWAP_USDC_TRG:
88
+ return 0.9;
89
+ case OutputStage.LTRL_TRG_SENT:
90
+ case OutputStage.LTRL_TRG_RCVD:
91
+ case OutputStage.SWAP_TRG_DST:
92
+ return 0.95;
93
+ case OutputStage.XFER_TRG_DST:
94
+ case OutputStage.REFUND_NATIVE:
95
+ case OutputStage.FINALIZED:
96
+ return 1;
97
+ default:
98
+ return 0;
99
+ }
100
+ }
101
+ /**
102
+ * React hook for tracking SilentSwap orders via WebSocket
103
+ */
104
+ export function useOrderTracking({ client, orderId: initialOrderId, viewingAuth: initialAuth, onStatusUpdate, onError, onComplete, } = {}) {
105
+ const [isConnected, setIsConnected] = useState(false);
106
+ const [isLoading, setIsLoading] = useState(false);
107
+ const [error, setError] = useState(null);
108
+ const [orderStatus, setOrderStatus] = useState(null);
109
+ const [deposit, setDeposit] = useState(null);
110
+ const [outputs, setOutputs] = useState([]);
111
+ const [progresses, setProgresses] = useState([undefined]);
112
+ const [statusTexts, setStatusTexts] = useState(['Connecting']);
113
+ const [completedTimestamp, setCompletedTimestamp] = useState(null);
114
+ const [isComplete, setIsComplete] = useState(false);
115
+ const wsRef = useRef(null);
116
+ const orderIdRef = useRef(initialOrderId);
117
+ const authRef = useRef(initialAuth);
118
+ const requestCounterRef = useRef(0);
119
+ const responseHandlersRef = useRef(new Map());
120
+ const reconnectTimeoutRef = useRef(null);
121
+ const lastReceivedRef = useRef(0);
122
+ const isReconnectingRef = useRef(false);
123
+ const reconnectAttemptsRef = useRef(0);
124
+ const maxReconnectAttempts = 5; // Maximum number of reconnection attempts
125
+ const reconnectDelayRef = useRef(1000); // Initial delay in ms, will increase exponentially
126
+ // Track the last connected orderId/auth to prevent unnecessary reconnections
127
+ // These are used in both the auto-connect useEffect and the onclose handler
128
+ const lastConnectedOrderIdRef = useRef(undefined);
129
+ const lastConnectedAuthRef = useRef(undefined);
130
+ // Update output state when status changes
131
+ const updateOutput = useCallback((index, stage, timestamp = Date.now(), asset) => {
132
+ const progress = getProgressFromStage(stage);
133
+ const status = getStatusTextFromStage(stage);
134
+ setProgresses((prev) => {
135
+ const newProgresses = [...prev];
136
+ newProgresses[index] = progress;
137
+ return newProgresses;
138
+ });
139
+ setStatusTexts((prev) => {
140
+ const newTexts = [...prev];
141
+ newTexts[index] = status;
142
+ return newTexts;
143
+ });
144
+ setOutputs((prev) => {
145
+ const newOutputs = [...prev];
146
+ newOutputs[index] = {
147
+ ...newOutputs[index],
148
+ index,
149
+ stage,
150
+ timestamp,
151
+ ...(asset ? { asset } : {}),
152
+ // Preserve recipient if it exists
153
+ ...(prev[index]?.recipient ? { recipient: prev[index].recipient } : {}),
154
+ };
155
+ return newOutputs;
156
+ });
157
+ // Check if all outputs are complete
158
+ setProgresses((currentProgresses) => {
159
+ if (currentProgresses.every((p) => (p ?? 0) >= 1)) {
160
+ setIsComplete(true);
161
+ setCompletedTimestamp(timestamp);
162
+ onComplete?.();
163
+ }
164
+ return currentProgresses;
165
+ });
166
+ }, [onComplete]);
167
+ // Submit JSON-RPC request
168
+ const submitRequest = useCallback((method, params, handler) => {
169
+ const ws = wsRef.current;
170
+ if (!ws || ws.readyState !== WebSocket.OPEN) {
171
+ console.warn('WebSocket not connected, cannot send request');
172
+ return -1;
173
+ }
174
+ const requestId = requestCounterRef.current++;
175
+ const request = {
176
+ jsonrpc: '2.0',
177
+ id: requestId,
178
+ method,
179
+ params,
180
+ };
181
+ if (handler) {
182
+ responseHandlersRef.current.set(requestId, handler);
183
+ }
184
+ ws.send(JSON.stringify(request));
185
+ return requestId;
186
+ }, []);
187
+ // Connect to WebSocket using JSON-RPC protocol (matching Svelte implementation)
188
+ const connect = useCallback((orderId, auth) => {
189
+ orderIdRef.current = orderId;
190
+ authRef.current = auth;
191
+ setIsLoading(true);
192
+ setError(null);
193
+ isReconnectingRef.current = false;
194
+ // Clear any existing reconnection timeout
195
+ if (reconnectTimeoutRef.current) {
196
+ clearTimeout(reconnectTimeoutRef.current);
197
+ reconnectTimeoutRef.current = null;
198
+ }
199
+ // Close existing connection
200
+ if (wsRef.current) {
201
+ wsRef.current.close();
202
+ wsRef.current = null;
203
+ }
204
+ // Clear response handlers
205
+ responseHandlersRef.current.clear();
206
+ requestCounterRef.current = 0;
207
+ // Reset reconnection attempts and delay for new connection
208
+ reconnectAttemptsRef.current = 0;
209
+ reconnectDelayRef.current = 1000;
210
+ // Get WebSocket URL (matching Svelte implementation)
211
+ // Use env var if available, otherwise use client baseUrl, otherwise construct from location
212
+ // IMPORTANT: URL should be just /websocket with NO path or query params
213
+ // Auth is sent via JSON-RPC connect method, not in URL
214
+ let wsUrl;
215
+ if (typeof window !== 'undefined' && window.__WEBSOCKET_URL__) {
216
+ // WebSocket URL from window (for runtime override)
217
+ wsUrl = window.__WEBSOCKET_URL__;
218
+ }
219
+ else if (client) {
220
+ // Use client baseUrl (convert http/https to ws/wss)
221
+ const baseUrl = client.baseUrl;
222
+ try {
223
+ const url = new URL(baseUrl);
224
+ const protocol = url.protocol === 'https:' ? 'wss:' : 'ws:';
225
+ wsUrl = `${protocol}//${url.host}/websocket`;
226
+ }
227
+ catch {
228
+ // If URL parsing fails, try simple string replacement
229
+ wsUrl = baseUrl.replace(/^https?:\/\//, 'wss://').replace(/^http:\/\//, 'ws://') + '/websocket';
230
+ }
231
+ }
232
+ else if (typeof window !== 'undefined') {
233
+ // Construct from current location
234
+ wsUrl = `${window.location.protocol === 'https:' ? 'wss' : 'ws'}://${window.location.host}/websocket`;
235
+ }
236
+ else {
237
+ wsUrl = 'ws://localhost/websocket';
238
+ }
239
+ // Ensure URL doesn't have any path or query params (just /websocket)
240
+ // This is critical: the WebSocket URL must be exactly: wss://domain/websocket (no params)
241
+ try {
242
+ const url = new URL(wsUrl);
243
+ // Force protocol to ws/wss (remove any existing path/query)
244
+ const protocol = url.protocol === 'https:' || url.protocol === 'wss:' ? 'wss:' : 'ws:';
245
+ // Reconstruct URL with only host and /websocket path
246
+ wsUrl = `${protocol}//${url.host}/websocket`;
247
+ }
248
+ catch {
249
+ // If URL parsing fails, try to clean it manually
250
+ // Remove any query params, hash, or extra paths
251
+ wsUrl = wsUrl.split('?')[0].split('#')[0].replace(/\/+$/, '') + '/websocket';
252
+ }
253
+ try {
254
+ const ws = new WebSocket(wsUrl);
255
+ wsRef.current = ws;
256
+ ws.onopen = () => {
257
+ lastReceivedRef.current = Date.now();
258
+ setIsConnected(true);
259
+ setIsLoading(false);
260
+ isReconnectingRef.current = false;
261
+ // Reset reconnection attempts on successful connection
262
+ reconnectAttemptsRef.current = 0;
263
+ reconnectDelayRef.current = 1000;
264
+ // Send connect JSON-RPC request (matching Svelte)
265
+ const connectRequestId = submitRequest('connect', {
266
+ auth: {
267
+ orderId,
268
+ viewingAuth: auth,
269
+ },
270
+ }, (error, result) => {
271
+ if (error) {
272
+ const err = new Error(error.message || 'Connection error');
273
+ setError(err);
274
+ setIsLoading(false);
275
+ onError?.(err);
276
+ return;
277
+ }
278
+ // Handle connected response
279
+ const status = result;
280
+ setOrderStatus(status);
281
+ if (status.deposit) {
282
+ setDeposit({
283
+ amount: status.deposit.amount,
284
+ timestamp: status.deposit.timestamp,
285
+ duration: status.deposit.duration,
286
+ orderId: status.deposit.orderId,
287
+ tx: status.deposit.tx,
288
+ });
289
+ }
290
+ // Initialize outputs
291
+ const numOutputs = status.outputs?.length ?? 1;
292
+ setProgresses(new Array(numOutputs).fill(undefined));
293
+ setStatusTexts(new Array(numOutputs).fill('Connecting'));
294
+ // Process initial output statuses
295
+ status.outputs?.forEach((output, index) => {
296
+ // Set output with recipient information (matching Svelte)
297
+ setOutputs((prev) => {
298
+ const newOutputs = [...prev];
299
+ newOutputs[index] = {
300
+ index,
301
+ stage: output.stage,
302
+ timestamp: output.timestamp,
303
+ recipient: output.recipient,
304
+ asset: output.output,
305
+ txs: output.txs,
306
+ };
307
+ return newOutputs;
308
+ });
309
+ // Update progress and status
310
+ updateOutput(index, output.stage, output.timestamp, output.output);
311
+ });
312
+ // Register status handler for subsequent updates (matching Svelte pattern)
313
+ // Status updates come as responses to the same request ID
314
+ if (connectRequestId >= 0) {
315
+ responseHandlersRef.current.set(connectRequestId, (statusError, statusResult) => {
316
+ if (statusError) {
317
+ const err = new Error(statusError.message || 'Status error');
318
+ setError(err);
319
+ onError?.(err);
320
+ return;
321
+ }
322
+ // Handle status update
323
+ const update = statusResult;
324
+ handleStatusUpdate(update);
325
+ });
326
+ }
327
+ onStatusUpdate?.(status);
328
+ });
329
+ };
330
+ ws.onmessage = (event) => {
331
+ lastReceivedRef.current = Date.now();
332
+ try {
333
+ const response = JSON.parse(event.data);
334
+ // Validate JSON-RPC response
335
+ if (response.jsonrpc !== '2.0' || typeof response.id !== 'number') {
336
+ return;
337
+ }
338
+ // Get handler(s) for this request ID (matching Svelte pattern)
339
+ // Status updates come as multiple responses to the same request ID
340
+ // So we DON'T delete the handler - it stays registered for subsequent updates
341
+ const handler = responseHandlersRef.current.get(response.id);
342
+ if (handler) {
343
+ // Call handler but DON'T delete it - status updates reuse the same request ID
344
+ handler(response.error, response.result);
345
+ }
346
+ }
347
+ catch (parseError) {
348
+ console.warn('Failed to parse WebSocket message:', parseError);
349
+ }
350
+ };
351
+ ws.onerror = (event) => {
352
+ const err = new Error('WebSocket connection error');
353
+ setError(err);
354
+ setIsLoading(false);
355
+ // Don't call onError here to prevent infinite error logging
356
+ // The error will be handled by onclose reconnection logic
357
+ };
358
+ ws.onclose = async () => {
359
+ setIsConnected(false);
360
+ responseHandlersRef.current.clear();
361
+ // Automatic reconnection with retry limit and exponential backoff
362
+ // Only reconnect if we still have valid orderId/auth and we're not already reconnecting
363
+ // Also check that the orderId/auth matches what we're supposed to be tracking
364
+ if (orderIdRef.current &&
365
+ authRef.current &&
366
+ !isReconnectingRef.current &&
367
+ orderIdRef.current === lastConnectedOrderIdRef.current &&
368
+ authRef.current === lastConnectedAuthRef.current) {
369
+ // Check if we've exceeded max reconnection attempts
370
+ if (reconnectAttemptsRef.current >= maxReconnectAttempts) {
371
+ const err = new Error(`WebSocket connection failed after ${maxReconnectAttempts} attempts`);
372
+ setError(err);
373
+ setIsLoading(false);
374
+ onError?.(err);
375
+ // Reset attempts after a longer delay (30 seconds) to allow retry later
376
+ reconnectTimeoutRef.current = setTimeout(() => {
377
+ reconnectAttemptsRef.current = 0;
378
+ reconnectDelayRef.current = 1000;
379
+ }, 30000);
380
+ return;
381
+ }
382
+ isReconnectingRef.current = true;
383
+ reconnectAttemptsRef.current += 1;
384
+ // Exponential backoff: delay increases with each attempt (1s, 2s, 4s, 8s, 16s)
385
+ const currentDelay = reconnectDelayRef.current;
386
+ reconnectDelayRef.current = Math.min(currentDelay * 2, 30000); // Cap at 30 seconds
387
+ // If last received was a while ago, add extra delay
388
+ const timeSinceLastReceived = Date.now() - lastReceivedRef.current;
389
+ const extraDelay = timeSinceLastReceived > 90000 ? 5000 : 0;
390
+ const totalDelay = currentDelay + extraDelay;
391
+ reconnectTimeoutRef.current = setTimeout(() => {
392
+ // Double-check that orderId/auth still match before reconnecting
393
+ if (orderIdRef.current &&
394
+ authRef.current &&
395
+ orderIdRef.current === lastConnectedOrderIdRef.current &&
396
+ authRef.current === lastConnectedAuthRef.current) {
397
+ isReconnectingRef.current = false; // Reset before connecting
398
+ connect(orderIdRef.current, authRef.current);
399
+ }
400
+ else {
401
+ // Order changed, don't reconnect
402
+ isReconnectingRef.current = false;
403
+ reconnectAttemptsRef.current = 0;
404
+ reconnectDelayRef.current = 1000;
405
+ }
406
+ }, totalDelay);
407
+ }
408
+ };
409
+ }
410
+ catch (err) {
411
+ const error = err instanceof Error ? err : new Error('Failed to connect to order tracking');
412
+ setError(error);
413
+ setIsLoading(false);
414
+ onError?.(error);
415
+ }
416
+ }, [submitRequest, updateOutput, onStatusUpdate, onError]);
417
+ // Handle status updates
418
+ const handleStatusUpdate = useCallback((update) => {
419
+ switch (update.type) {
420
+ case 'deposit': {
421
+ setDeposit({
422
+ amount: update.data.amount,
423
+ timestamp: update.data.timestamp,
424
+ duration: update.data.duration,
425
+ orderId: update.data.orderId,
426
+ tx: update.data.tx,
427
+ });
428
+ break;
429
+ }
430
+ case 'stage': {
431
+ updateOutput(update.data.index, update.data.stage, update.data.timestamp, update.data.asset);
432
+ break;
433
+ }
434
+ case 'transaction': {
435
+ setOutputs((prev) => {
436
+ const newOutputs = [...prev];
437
+ if (newOutputs[update.data.index]) {
438
+ const existingTxs = newOutputs[update.data.index].txs || {};
439
+ newOutputs[update.data.index] = {
440
+ ...newOutputs[update.data.index],
441
+ txs: {
442
+ ...existingTxs,
443
+ [update.data.kind]: {
444
+ txId: update.data.txId,
445
+ chain: update.data.chain,
446
+ },
447
+ },
448
+ };
449
+ }
450
+ return newOutputs;
451
+ });
452
+ break;
453
+ }
454
+ case 'error': {
455
+ const err = new Error(update.data.message || 'Order tracking error');
456
+ setError(err);
457
+ onError?.(err);
458
+ break;
459
+ }
460
+ }
461
+ }, [updateOutput, onError]);
462
+ // Disconnect from WebSocket
463
+ const disconnect = useCallback(() => {
464
+ // Clear reconnection timeout
465
+ if (reconnectTimeoutRef.current) {
466
+ clearTimeout(reconnectTimeoutRef.current);
467
+ reconnectTimeoutRef.current = null;
468
+ }
469
+ isReconnectingRef.current = true; // Prevent reconnection
470
+ orderIdRef.current = undefined;
471
+ authRef.current = undefined;
472
+ if (wsRef.current) {
473
+ wsRef.current.close();
474
+ wsRef.current = null;
475
+ }
476
+ responseHandlersRef.current.clear();
477
+ setIsConnected(false);
478
+ }, []);
479
+ // Auto-connect if orderId and auth are provided
480
+ useEffect(() => {
481
+ // Only connect if orderId/auth changed or if not yet connected
482
+ // Also check if we're already connected to the same orderId/auth to prevent reconnection
483
+ const shouldConnect = initialOrderId &&
484
+ initialAuth &&
485
+ client &&
486
+ (lastConnectedOrderIdRef.current !== initialOrderId || lastConnectedAuthRef.current !== initialAuth) &&
487
+ !isReconnectingRef.current &&
488
+ (!wsRef.current || wsRef.current.readyState === WebSocket.CLOSED);
489
+ if (shouldConnect) {
490
+ lastConnectedOrderIdRef.current = initialOrderId;
491
+ lastConnectedAuthRef.current = initialAuth;
492
+ connect(initialOrderId, initialAuth);
493
+ }
494
+ return () => {
495
+ // Only disconnect if we're actually disconnecting (orderId/auth cleared)
496
+ if (!initialOrderId || !initialAuth) {
497
+ lastConnectedOrderIdRef.current = undefined;
498
+ lastConnectedAuthRef.current = undefined;
499
+ disconnect();
500
+ }
501
+ };
502
+ // Only depend on initialOrderId, initialAuth, and client
503
+ // Don't include connect/disconnect to prevent infinite loops
504
+ // eslint-disable-next-line react-hooks/exhaustive-deps
505
+ }, [initialOrderId, initialAuth, client]);
506
+ return {
507
+ // State
508
+ isConnected,
509
+ isLoading,
510
+ error,
511
+ orderStatus,
512
+ deposit,
513
+ outputs,
514
+ progresses,
515
+ statusTexts,
516
+ completedTimestamp,
517
+ isComplete,
518
+ // Methods
519
+ connect,
520
+ disconnect,
521
+ getStatusText: getStatusTextFromStage,
522
+ getProgress: getProgressFromStage,
523
+ };
524
+ }
@@ -0,0 +1,50 @@
1
+ import type { QuoteResponse, HdFacilitatorGroup, BridgeProvider, AssetInfo, QuoteRequest } from '@silentswap/sdk';
2
+ import type { SilentSwapWallet } from './useWallet.js';
3
+ export interface Destination {
4
+ asset: string;
5
+ contact: string;
6
+ amount: string;
7
+ priceUsd?: number;
8
+ }
9
+ export interface QuoteCalculationResult {
10
+ quote: QuoteResponse;
11
+ usdcAmount: string;
12
+ facilitatorGroup: HdFacilitatorGroup | undefined;
13
+ bridgeProvider: BridgeProvider;
14
+ allowanceTarget: `0x${string}` | undefined;
15
+ }
16
+ export interface UseQuoteCalculationOptions {
17
+ /** User's address (EVM hex address or Solana base58 address) */
18
+ address: `0x${string}` | string;
19
+ /** EVM address */
20
+ evmAddress: `0x${string}` | string;
21
+ /** SilentSwap wallet instance */
22
+ wallet: SilentSwapWallet | null;
23
+ /** Depositor address from client */
24
+ depositorAddress: `0x${string}`;
25
+ /** Get quote function */
26
+ getQuote: (request: QuoteRequest) => Promise<QuoteResponse | null>;
27
+ /** Get price function */
28
+ getPrice: (asset: AssetInfo) => Promise<number>;
29
+ /** Set destinations callback */
30
+ setDestinations: (updater: Destination[] | ((prev: Destination[]) => Destination[])) => void;
31
+ }
32
+ export interface UseQuoteCalculationReturn {
33
+ /** Calculate quote and return result */
34
+ calculateQuote: (sourceAsset: string, sourceAmount: string, destinations: Destination[], splits: number[]) => Promise<QuoteCalculationResult | null>;
35
+ /** Loading state for quote calculation */
36
+ loadingAmounts: boolean;
37
+ /** Deposit amount in USDC */
38
+ depositAmountUsdc: number;
39
+ }
40
+ /**
41
+ * Hook for calculating quotes and USDC amounts
42
+ *
43
+ * Extracted from useSilentQuote to improve modularity and reusability.
44
+ * Handles:
45
+ * - USDC amount calculation for different asset types (Solana, EVM, direct USDC)
46
+ * - Facilitator group resolution
47
+ * - Quote request construction and fetching
48
+ * - Destination amount updates
49
+ */
50
+ export declare function useQuoteCalculation({ address, evmAddress, wallet, depositorAddress, getQuote, getPrice, setDestinations, }: UseQuoteCalculationOptions): UseQuoteCalculationReturn;