@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,507 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import BigNumber from 'bignumber.js';
3
+ import { NI_CHAIN_ID_AVALANCHE, S0X_ADDR_USDC_AVALANCHE, S_CAIP19_USDC_AVALANCHE, X_MAX_IMPACT_PERCENT, getBridgeQuote as sdkGetBridgeQuote, interpolateSamples as sdkInterpolateSamples, fetchRelayQuote, fetchDebridgeOrder, normalizeAddress, getAssetByCaip19, N_DEBRIDGE_CHAIN_ID_SOLANA, N_RELAY_CHAIN_ID_SOLANA, SB58_ADDR_SOL_PROGRAM_SYSTEM, S0X_ADDR_EVM_RELAY_LINK_DEAD, SB58_ADDR_SOL_RELAY_LINK_RECIPIENT, isSolanaAsset, } from '@silentswap/sdk';
4
+ // Constants for estimateLive
5
+ const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000';
6
+ const EVM_PHONY_ADDRESS_FALLBACK = '0x1111111111111111111111111111111111111111';
7
+ /**
8
+ * Hook for getting optimized bridge quotes with retention rate tracking
9
+ * Implements the same functionality as estimate.ts from the original app
10
+ */
11
+ export function useQuote({ address, maxImpactPercent = X_MAX_IMPACT_PERCENT } = {}) {
12
+ const [isLoading, setIsLoading] = useState(false);
13
+ const [error, setError] = useState(null);
14
+ // Estimates cache (replaces H_ESTIMATES_INGRESS and H_ESTIMATES_EGRESS from original)
15
+ const ingressEstimatesRef = useRef({});
16
+ const egressEstimatesRef = useRef({});
17
+ // Normalize address - handle both EVM (hex) and Solana (base58) addresses
18
+ const normalizedAddress = address ? normalizeAddress(address) : null;
19
+ // Abort controller for cancelling requests
20
+ const abortControllerRef = useRef(null);
21
+ // Cleanup on unmount
22
+ useEffect(() => {
23
+ return () => {
24
+ // Cancel any pending requests when component unmounts
25
+ if (abortControllerRef.current) {
26
+ abortControllerRef.current.abort();
27
+ abortControllerRef.current = null;
28
+ }
29
+ };
30
+ }, []);
31
+ /**
32
+ * Cancel any ongoing requests and create a new abort controller
33
+ */
34
+ const createNewAbortController = useCallback(() => {
35
+ // Cancel previous request if any
36
+ if (abortControllerRef.current) {
37
+ abortControllerRef.current.abort();
38
+ }
39
+ // Create new abort controller
40
+ const controller = new AbortController();
41
+ abortControllerRef.current = controller;
42
+ return controller;
43
+ }, []);
44
+ /**
45
+ * Get quote for cross-chain swap
46
+ */
47
+ const getQuote = useCallback(async (srcChainId, srcToken, srcAmount, dstChainId, dstToken, recipientAddress, sourceAddress) => {
48
+ console.log('[Quote] Step 1: Starting getQuote', {
49
+ srcChainId,
50
+ srcToken,
51
+ srcAmount,
52
+ dstChainId,
53
+ dstToken,
54
+ hasRecipientAddress: !!recipientAddress,
55
+ hasSourceAddress: !!sourceAddress,
56
+ });
57
+ if (!normalizedAddress) {
58
+ throw new Error('Address required for bridge quote operations');
59
+ }
60
+ setIsLoading(true);
61
+ setError(null);
62
+ // Create abort controller for this request
63
+ const controller = createNewAbortController();
64
+ const signal = controller.signal;
65
+ try {
66
+ // Normalize addresses
67
+ console.log('[Quote] Step 2: Normalizing token addresses');
68
+ const srcTokenNorm = srcToken === '0x0' || srcToken === '0x0000000000000000000000000000000000000000' ? ZERO_ADDRESS : srcToken;
69
+ const dstTokenNorm = dstToken === '0x0' || dstToken === '0x0000000000000000000000000000000000000000' ? ZERO_ADDRESS : dstToken;
70
+ // Use shared quote fetching function
71
+ // normalizedAddress can be EVM (0x...) or Solana (base58) address
72
+ // sourceAddress is the address format for the source chain (for relay.link)
73
+ console.log('[Quote] Step 3: Calling sdkGetBridgeQuote');
74
+ const result = await sdkGetBridgeQuote(srcChainId, srcTokenNorm, srcAmount, dstChainId, dstTokenNorm, normalizedAddress, signal, recipientAddress, // Pass recipient address for Solana destinations
75
+ sourceAddress);
76
+ console.log('[Quote] Step 4: getQuote completed successfully', {
77
+ provider: result.provider,
78
+ });
79
+ return result;
80
+ }
81
+ catch (err) {
82
+ // Re-throw if it's an abort error
83
+ if (err instanceof Error && err.name === 'AbortError') {
84
+ console.log('[Quote] getQuote aborted');
85
+ throw err;
86
+ }
87
+ const error = err instanceof Error ? err : new Error('Failed to get quote');
88
+ console.error('[Quote] Error: Failed to get quote', error);
89
+ setError(error);
90
+ throw error;
91
+ }
92
+ finally {
93
+ setIsLoading(false);
94
+ }
95
+ }, [normalizedAddress, createNewAbortController]);
96
+ /**
97
+ * Get live estimate for a given asset and direction
98
+ */
99
+ const estimateLive = useCallback(async (direction, assetCaip19, chainId, tokenAddress, amount, usdPrice, externalSignal, recipientAddress, // Optional recipient address (required for Solana destinations in egress)
100
+ isReverseCalculation, // For reverse calculation (output-to-input)
101
+ targetAmount) => {
102
+ console.log('[Quote] Step 1: Starting estimateLive', {
103
+ direction,
104
+ assetCaip19,
105
+ chainId,
106
+ tokenAddress,
107
+ amount,
108
+ usdPrice,
109
+ isReverseCalculation,
110
+ targetAmount,
111
+ });
112
+ // Determine if this is a Solana chain by checking the asset CAIP-19
113
+ // This is more reliable than checking chainId format
114
+ const isSolanaChain = isSolanaAsset(assetCaip19);
115
+ console.log('[Quote] Step 2: Chain type determined', { isSolanaChain, chainId });
116
+ let numericChainId;
117
+ if (isSolanaChain) {
118
+ // For Solana, we'll use different chain IDs for different providers
119
+ // This will be set per-provider below
120
+ numericChainId = 0; // Placeholder, will be set per provider
121
+ }
122
+ else {
123
+ // For EVM chains, convert chainId to number if needed
124
+ if (typeof chainId === 'string') {
125
+ const parsed = parseInt(chainId, 10);
126
+ if (isNaN(parsed)) {
127
+ throw new Error(`Invalid chain ID: ${chainId}`);
128
+ }
129
+ numericChainId = parsed;
130
+ }
131
+ else {
132
+ numericChainId = chainId;
133
+ }
134
+ }
135
+ if (!normalizedAddress) {
136
+ throw new Error('Address required');
137
+ }
138
+ // Use external signal if provided, otherwise create new abort controller
139
+ let signal;
140
+ if (externalSignal) {
141
+ // Use external signal (from estimateOrder)
142
+ signal = externalSignal;
143
+ }
144
+ else {
145
+ // Create new abort controller and cancel any pending requests
146
+ const controller = createNewAbortController();
147
+ signal = controller.signal;
148
+ }
149
+ try {
150
+ // Special case for USDC on Avalanche
151
+ // Check numericChainId (or converted chainId for non-Solana chains)
152
+ const checkChainId = isSolanaChain ? 0 : numericChainId; // Solana will never match Avalanche
153
+ if (checkChainId === NI_CHAIN_ID_AVALANCHE &&
154
+ tokenAddress.toLowerCase() === S0X_ADDR_USDC_AVALANCHE.toLowerCase()) {
155
+ console.log('[Quote] Step 3: Special case - USDC on Avalanche, returning identity estimate');
156
+ const estimate = {
157
+ gasFee: 0,
158
+ samples: [
159
+ {
160
+ baseline: Infinity,
161
+ retention: 1,
162
+ source: 'none',
163
+ },
164
+ ],
165
+ };
166
+ if (direction === 'ingress') {
167
+ ingressEstimatesRef.current[assetCaip19] = estimate;
168
+ }
169
+ else {
170
+ egressEstimatesRef.current[assetCaip19] = estimate;
171
+ }
172
+ return {
173
+ estimate,
174
+ gasAmount: BigInt(BigNumber(300_000).times(2).shiftedBy(9).toFixed(0)),
175
+ response: {},
176
+ provider: 'relay',
177
+ };
178
+ }
179
+ console.log('[Quote] Step 3: Getting asset info and preparing quote request');
180
+ // Get asset info to determine correct decimals
181
+ const assetInfo = getAssetByCaip19(assetCaip19);
182
+ if (!assetInfo) {
183
+ throw new Error(`Asset not found: ${assetCaip19}`);
184
+ }
185
+ // Calculate amount in token units
186
+ // For ingress: amount is in source asset human-readable format, convert using source asset decimals
187
+ // For egress:
188
+ // - Normal: amount is in USDC human-readable format (USD value when price = 1), convert using USDC decimals (6)
189
+ // - Reverse: amount is in destination token human-readable format, convert using destination asset decimals
190
+ const decimalsToUse = (direction === 'egress' && !isReverseCalculation)
191
+ ? 6 // USDC has 6 decimals (for normal egress)
192
+ : assetInfo.decimals; // Use asset decimals (for ingress or reverse egress)
193
+ const tokenAmount = BigNumber(amount).shiftedBy(decimalsToUse).toFixed(0);
194
+ // Get quotes from both providers using SDK functions
195
+ // Determine user address based on direction and chain
196
+ // This matches the Svelte app behavior in estimate.ts
197
+ console.log('[Quote] Step 4: Determining user address and chain IDs');
198
+ let userAddress;
199
+ if (direction === 'ingress') {
200
+ // For ingress, user address should match the source chain
201
+ if (isSolanaChain) {
202
+ // For Solana ingress, use Solana address (or phony if not available)
203
+ userAddress = normalizedAddress && normalizedAddress.startsWith('0x') === false
204
+ ? normalizedAddress
205
+ : SB58_ADDR_SOL_PROGRAM_SYSTEM;
206
+ }
207
+ else {
208
+ // For EVM ingress, use EVM address (or phony if not available)
209
+ userAddress = normalizedAddress && normalizedAddress.startsWith('0x')
210
+ ? normalizedAddress
211
+ : EVM_PHONY_ADDRESS_FALLBACK;
212
+ }
213
+ }
214
+ else {
215
+ // For egress, origin is always Avalanche (EVM), so user must be EVM address
216
+ userAddress = normalizedAddress && normalizedAddress.startsWith('0x')
217
+ ? normalizedAddress
218
+ : EVM_PHONY_ADDRESS_FALLBACK;
219
+ }
220
+ // For Solana, use different chain IDs for different providers
221
+ const relayChainId = isSolanaChain ? N_RELAY_CHAIN_ID_SOLANA : numericChainId;
222
+ const debridgeChainId = isSolanaChain ? N_DEBRIDGE_CHAIN_ID_SOLANA : numericChainId;
223
+ console.log('[Quote] Step 4: Address and chain IDs determined', {
224
+ userAddress,
225
+ relayChainId,
226
+ debridgeChainId,
227
+ tradeType: isReverseCalculation ? 'EXACT_OUTPUT' : 'EXACT_INPUT',
228
+ });
229
+ console.log('[Quote] Step 5: Fetching quotes from Relay and DeBridge providers');
230
+ const [relayResult, debridgeResult] = await Promise.allSettled([
231
+ (async () => {
232
+ try {
233
+ // Determine trade type: EXACT_OUTPUT for reverse calculation, EXACT_INPUT for normal
234
+ const tradeType = isReverseCalculation ? 'EXACT_OUTPUT' : 'EXACT_INPUT';
235
+ // For reverse calculation ingress: use targetDepositUsd as destination amount
236
+ // For reverse calculation egress: use targetOutputAmount as destination amount
237
+ let quoteAmount = tokenAmount;
238
+ if (isReverseCalculation && direction === 'ingress' && targetAmount) {
239
+ // For ingress reverse: we want a specific deposit USD, calculate required input
240
+ // Convert targetDepositUsd (USD) to USDC micro units
241
+ const usdcAsset = getAssetByCaip19(S_CAIP19_USDC_AVALANCHE);
242
+ if (usdcAsset) {
243
+ quoteAmount = BigNumber(targetAmount).shiftedBy(usdcAsset.decimals).toFixed(0);
244
+ }
245
+ else {
246
+ throw new Error('USDC asset not found');
247
+ }
248
+ }
249
+ else if (isReverseCalculation && direction === 'egress' && targetAmount) {
250
+ // For egress reverse: we want a specific output amount, calculate required USDC
251
+ // targetAmount is the output amount in destination token units (human-readable)
252
+ // For EXACT_OUTPUT, we need to pass the destination token amount in micro units
253
+ // The quote will calculate how much USDC is needed to get this output
254
+ quoteAmount = tokenAmount; // tokenAmount is already in micro units (calculated from targetAmount)
255
+ }
256
+ const quote = await fetchRelayQuote({
257
+ user: userAddress,
258
+ referrer: 'silentswap',
259
+ tradeType,
260
+ ...(direction === 'ingress'
261
+ ? {
262
+ originChainId: relayChainId,
263
+ destinationChainId: NI_CHAIN_ID_AVALANCHE,
264
+ originCurrency: tokenAddress === ZERO_ADDRESS ? ZERO_ADDRESS : tokenAddress,
265
+ destinationCurrency: S0X_ADDR_USDC_AVALANCHE,
266
+ ...(isReverseCalculation && tradeType === 'EXACT_OUTPUT'
267
+ ? {
268
+ // For reverse ingress: we specify the destination (deposit) amount
269
+ amount: quoteAmount, // USDC micro units we want to receive
270
+ }
271
+ : {
272
+ // Normal ingress: we specify the source amount
273
+ amount: tokenAmount,
274
+ }),
275
+ // For Solana ingress, set recipient to EVM phony address (for Avalanche destination)
276
+ ...(isSolanaChain
277
+ ? { recipient: EVM_PHONY_ADDRESS_FALLBACK }
278
+ : {}),
279
+ }
280
+ : {
281
+ originChainId: NI_CHAIN_ID_AVALANCHE,
282
+ originCurrency: S0X_ADDR_USDC_AVALANCHE,
283
+ destinationChainId: relayChainId,
284
+ destinationCurrency: tokenAddress === ZERO_ADDRESS ? ZERO_ADDRESS : tokenAddress,
285
+ ...(isReverseCalculation && tradeType === 'EXACT_OUTPUT'
286
+ ? {
287
+ // For reverse egress: we specify the destination (output) amount
288
+ amount: quoteAmount, // Destination token amount we want to receive (in micro units)
289
+ }
290
+ : {
291
+ // Normal egress: we specify the source (USDC) amount
292
+ amount: tokenAmount,
293
+ }),
294
+ // For egress to Solana, use EVM dead address for user and Solana recipient
295
+ ...(isSolanaChain
296
+ ? {
297
+ user: S0X_ADDR_EVM_RELAY_LINK_DEAD,
298
+ recipient: recipientAddress || SB58_ADDR_SOL_RELAY_LINK_RECIPIENT,
299
+ }
300
+ : {}),
301
+ }),
302
+ }, signal);
303
+ return quote;
304
+ }
305
+ catch (err) {
306
+ // Don't log abort errors
307
+ if (err instanceof Error && err.name !== 'AbortError') {
308
+ console.warn('Relay quote failed:', err);
309
+ }
310
+ return null;
311
+ }
312
+ })(),
313
+ (async () => {
314
+ try {
315
+ // DeBridge egress is not supported (matches Svelte app behavior)
316
+ // Only Relay.link is used for egress quotes
317
+ if (direction === 'egress') {
318
+ return null; // Skip DeBridge for egress, will use Relay.link only
319
+ }
320
+ // For reverse calculation ingress: use srcChainTokenInAmount: 'auto' and specify dstChainTokenOutAmount
321
+ const debridgeParams = {
322
+ srcChainId: debridgeChainId,
323
+ srcChainTokenIn: tokenAddress === ZERO_ADDRESS ? ZERO_ADDRESS : tokenAddress,
324
+ dstChainId: NI_CHAIN_ID_AVALANCHE,
325
+ dstChainTokenOut: S0X_ADDR_USDC_AVALANCHE,
326
+ };
327
+ if (isReverseCalculation && direction === 'ingress' && targetAmount) {
328
+ // For reverse ingress: specify destination amount, let DeBridge calculate source
329
+ const usdcAsset = getAssetByCaip19(S_CAIP19_USDC_AVALANCHE);
330
+ if (usdcAsset) {
331
+ debridgeParams.srcChainTokenInAmount = 'auto';
332
+ debridgeParams.dstChainTokenOutAmount = BigNumber(targetAmount)
333
+ .shiftedBy(usdcAsset.decimals)
334
+ .toFixed(0);
335
+ }
336
+ else {
337
+ throw new Error('USDC asset not found');
338
+ }
339
+ }
340
+ else {
341
+ // Normal calculation: specify source amount
342
+ debridgeParams.srcChainTokenInAmount = tokenAmount;
343
+ debridgeParams.dstChainTokenOutAmount = 'auto';
344
+ }
345
+ const quote = await fetchDebridgeOrder(debridgeParams, signal);
346
+ return quote;
347
+ }
348
+ catch (err) {
349
+ // Don't log abort errors
350
+ if (err instanceof Error && err.name !== 'AbortError') {
351
+ console.warn('DeBridge quote failed:', err);
352
+ }
353
+ return null;
354
+ }
355
+ })(),
356
+ ]);
357
+ const relayQuote = relayResult.status === 'fulfilled' ? relayResult.value : null;
358
+ const debridgeQuote = debridgeResult.status === 'fulfilled' ? debridgeResult.value : null;
359
+ console.log('[Quote] Step 6: Quote results received', {
360
+ hasRelayQuote: !!relayQuote,
361
+ hasDebridgeQuote: !!debridgeQuote,
362
+ relayStatus: relayResult.status,
363
+ debridgeStatus: debridgeResult.status,
364
+ });
365
+ // Both failed
366
+ if (!relayQuote && !debridgeQuote) {
367
+ console.error('[Quote] Error: All quote providers failed');
368
+ throw new AggregateError([
369
+ relayResult.status === 'rejected' ? relayResult.reason : null,
370
+ debridgeResult.status === 'rejected' ? debridgeResult.reason : null,
371
+ ].filter(Boolean), 'All quote providers failed 2');
372
+ }
373
+ // Calculate retention rates
374
+ console.log('[Quote] Step 7: Calculating retention rates and gas amounts');
375
+ let retentionRelay = 0;
376
+ let retentionDebridge = 0;
377
+ let gasAmountRelay = 0n;
378
+ let gasAmountDebridge = 0n;
379
+ let gasFeeUsd = 0;
380
+ let calculatedInputAmount = undefined;
381
+ if (relayQuote) {
382
+ if (isReverseCalculation && direction === 'ingress') {
383
+ console.log('[Quote] Step 7: Processing Relay quote (reverse calculation ingress)');
384
+ // For reverse calculation, extract the calculated input amount from the quote
385
+ // currencyIn is the source token amount we need to send
386
+ calculatedInputAmount = Number(relayQuote.details.currencyIn.amount);
387
+ if (!calculatedInputAmount) {
388
+ const calculatedInputAmountUsd = Number(relayQuote.details.currencyIn.amountUsd || 0);
389
+ if (calculatedInputAmountUsd > 0 && usdPrice > 0) {
390
+ calculatedInputAmount = calculatedInputAmountUsd / usdPrice;
391
+ }
392
+ }
393
+ // Retention is still output/input, but now input is calculated
394
+ retentionRelay = BigNumber(relayQuote.details.currencyOut.amountUsd)
395
+ .div(relayQuote.details.currencyIn.amountUsd)
396
+ .toNumber();
397
+ console.log('[Quote] Step 7: Relay quote processed (reverse)', { calculatedInputAmount, retentionRelay });
398
+ }
399
+ else {
400
+ retentionRelay = BigNumber(relayQuote.details.currencyOut.amountUsd)
401
+ .div(relayQuote.details.currencyIn.amountUsd)
402
+ .toNumber();
403
+ console.log('[Quote] Step 7: Relay quote processed (normal)', { retentionRelay });
404
+ }
405
+ gasFeeUsd = Number(relayQuote.fees.relayerGas.amountUsd || '0');
406
+ gasAmountRelay = BigInt((BigInt(relayQuote.fees.gas.amount || 0) * 15n) / 10n);
407
+ }
408
+ if (debridgeQuote) {
409
+ if (isReverseCalculation && direction === 'ingress') {
410
+ console.log('[Quote] Step 7: Processing DeBridge quote (reverse calculation ingress)');
411
+ // For reverse calculation, extract the calculated input amount from DeBridge quote
412
+ calculatedInputAmount = Number(debridgeQuote.estimation.srcChainTokenIn.amount);
413
+ if (!calculatedInputAmount) {
414
+ const calculatedInputAmountUsd = Number(debridgeQuote.estimation.srcChainTokenIn.approximateUsdValue || 0);
415
+ if (calculatedInputAmountUsd > 0 && usdPrice > 0) {
416
+ calculatedInputAmount = calculatedInputAmountUsd / usdPrice;
417
+ }
418
+ }
419
+ // Retention is still output/input, but now input is calculated
420
+ retentionDebridge = BigNumber(debridgeQuote.estimation.dstChainTokenOut.approximateUsdValue)
421
+ .div(debridgeQuote.estimation.srcChainTokenIn.approximateUsdValue)
422
+ .toNumber();
423
+ console.log('[Quote] Step 7: DeBridge quote processed (reverse)', { calculatedInputAmount, retentionDebridge });
424
+ }
425
+ else {
426
+ retentionDebridge = BigNumber(debridgeQuote.estimation.dstChainTokenOut.approximateUsdValue)
427
+ .div(debridgeQuote.estimation.srcChainTokenIn.approximateUsdValue)
428
+ .toNumber();
429
+ console.log('[Quote] Step 7: DeBridge quote processed (normal)', { retentionDebridge });
430
+ }
431
+ gasFeeUsd =
432
+ debridgeQuote.estimation.costDetails?.reduce((sum, detail) => sum + Number(detail.payload?.feeApproximateUsdValue || 0), 0) || 0;
433
+ gasAmountDebridge = BigInt((BigInt(debridgeQuote.estimatedTransactionFee?.total || 0) * 15n) / 10n);
434
+ }
435
+ // Determine best source
436
+ console.log('[Quote] Step 8: Determining best provider', { retentionRelay, retentionDebridge });
437
+ const retention = Math.max(retentionRelay, retentionDebridge);
438
+ const source = retentionRelay >= retentionDebridge ? 'relay' : 'debridge';
439
+ const usdValue = BigNumber(usdPrice).times(amount).toNumber();
440
+ console.log('[Quote] Step 8: Best provider determined', { source, retention, usdValue });
441
+ // Create sample
442
+ const sample = {
443
+ baseline: usdValue,
444
+ retention,
445
+ source,
446
+ };
447
+ // Update estimates cache
448
+ console.log('[Quote] Step 9: Updating estimates cache', { direction, assetCaip19 });
449
+ const estimatesCache = direction === 'ingress' ? ingressEstimatesRef.current : egressEstimatesRef.current;
450
+ const existingEstimate = estimatesCache[assetCaip19];
451
+ // Filter out samples within $5 of this price
452
+ const filteredSamples = (existingEstimate?.samples || []).filter((s) => !(s.baseline >= sample.baseline - 5 && s.baseline <= sample.baseline + 5));
453
+ // Add new sample and sort
454
+ const samples = [...filteredSamples, sample].sort((a, b) => a.baseline - b.baseline);
455
+ const estimate = {
456
+ gasFee: direction === 'ingress' ? 0 : gasFeeUsd,
457
+ samples,
458
+ };
459
+ estimatesCache[assetCaip19] = estimate;
460
+ console.log('[Quote] Step 9: Estimates cache updated', {
461
+ samplesCount: samples.length,
462
+ gasFee: estimate.gasFee,
463
+ });
464
+ const result = {
465
+ estimate,
466
+ gasAmount: source === 'relay' ? gasAmountRelay : gasAmountDebridge,
467
+ response: (source === 'relay' ? relayQuote : debridgeQuote),
468
+ provider: source,
469
+ calculatedInputAmount, // Include calculated input amount for reverse calculation
470
+ };
471
+ console.log('[Quote] Step 10: estimateLive completed successfully', {
472
+ provider: source,
473
+ retention,
474
+ calculatedInputAmount,
475
+ });
476
+ return result;
477
+ }
478
+ catch (err) {
479
+ // Re-throw if it's an abort error
480
+ if (err instanceof Error && err.name === 'AbortError') {
481
+ console.log('[Quote] estimateLive aborted');
482
+ throw err;
483
+ }
484
+ // Wrap and re-throw other errors
485
+ const error = err instanceof Error ? err : new Error('Failed to get live estimate');
486
+ console.error('[Quote] Error: Failed to get live estimate', error);
487
+ throw error;
488
+ }
489
+ }, [normalizedAddress, createNewAbortController]);
490
+ /**
491
+ * Interpolate retention rate from samples
492
+ */
493
+ const interpolateSamples = useCallback((samples, usdValue, marginHi = Infinity) => {
494
+ return sdkInterpolateSamples(samples, usdValue, marginHi);
495
+ }, []);
496
+ return {
497
+ // State
498
+ isLoading,
499
+ error,
500
+ ingressEstimates: ingressEstimatesRef.current,
501
+ egressEstimates: egressEstimatesRef.current,
502
+ // Methods
503
+ getQuote,
504
+ estimateLive,
505
+ interpolateSamples,
506
+ };
507
+ }
@@ -0,0 +1,16 @@
1
+ import type { SilentSwapWallet } from './silent/useWallet.js';
2
+ import type { FacilitatorGroup } from '../contexts/OrdersContext.js';
3
+ export interface UseResetSwapFormOptions {
4
+ clearQuote: () => void;
5
+ isConnected: boolean;
6
+ wallet: SilentSwapWallet | null;
7
+ setFacilitatorGroups: (groups: FacilitatorGroup[]) => void;
8
+ refreshWallet?: () => Promise<void>;
9
+ }
10
+ /**
11
+ * Hook to handle resetting the swap form after completion
12
+ * Matches Svelte Form.svelte "New swap" button behavior
13
+ */
14
+ export declare function useResetSwapForm({ clearQuote, isConnected, wallet, setFacilitatorGroups, refreshWallet, }: UseResetSwapFormOptions): {
15
+ handleNewSwap: () => void;
16
+ };
@@ -0,0 +1,68 @@
1
+ import { useCallback } from 'react';
2
+ import { useSwap } from './useSwap.js';
3
+ /**
4
+ * Hook to handle resetting the swap form after completion
5
+ * Matches Svelte Form.svelte "New swap" button behavior
6
+ */
7
+ export function useResetSwapForm({ clearQuote, isConnected, wallet, setFacilitatorGroups, refreshWallet, }) {
8
+ const setSplits = useSwap((state) => state.setSplits);
9
+ const setInputAmount = useSwap((state) => state.setInputAmount);
10
+ const setDestinations = useSwap((state) => state.setDestinations);
11
+ const handleNewSwap = useCallback(() => {
12
+ // Clear order tracking state (clears orderId and viewingAuth)
13
+ clearQuote();
14
+ // Reset splits to single output
15
+ setSplits([1]);
16
+ // Clear input amount
17
+ setInputAmount('');
18
+ // Clear destinations - keep first, remove others (matches Svelte: AppState.destinations.splice(1, ...))
19
+ setDestinations((prev) => {
20
+ if (prev.length > 0) {
21
+ // Keep first destination but reset amount and price
22
+ return [
23
+ {
24
+ asset: prev[0].asset,
25
+ contact: prev[0].contact,
26
+ amount: '',
27
+ },
28
+ ];
29
+ }
30
+ return prev;
31
+ });
32
+ // Reset URL params (matches Svelte Form.svelte line 2740-2742)
33
+ // Preserve only src and dst params (if they exist), remove all others including order and auth
34
+ if (typeof window !== 'undefined') {
35
+ const currentParams = new URLSearchParams(window.location.search);
36
+ const params = new URLSearchParams();
37
+ // Preserve src and dst if they exist (matching Svelte behavior of preserving initial params)
38
+ const src = currentParams.get('src');
39
+ const dst = currentParams.get('dst');
40
+ if (src)
41
+ params.set('src', src);
42
+ if (dst)
43
+ params.set('dst', dst);
44
+ // Clear all other params (order, auth, etc.)
45
+ window.history.pushState({}, '', params.toString() ? `?${params.toString()}` : window.location.pathname);
46
+ }
47
+ // If connected, force regenerate all facilitator groups (matches Svelte: sender_resolve_group(-1, true))
48
+ if (isConnected && refreshWallet) {
49
+ // Regenerate wallet with all deposits to get fresh groups
50
+ void refreshWallet();
51
+ }
52
+ else if (isConnected && wallet?.accounts) {
53
+ // Fallback: if wallet is already loaded but refreshWallet not available,
54
+ // ensure all groups are set (matches Svelte behavior)
55
+ setFacilitatorGroups(wallet.accounts.map((account) => account.group));
56
+ }
57
+ }, [
58
+ clearQuote,
59
+ setSplits,
60
+ setInputAmount,
61
+ setDestinations,
62
+ isConnected,
63
+ wallet,
64
+ setFacilitatorGroups,
65
+ refreshWallet,
66
+ ]);
67
+ return { handleNewSwap };
68
+ }
@@ -0,0 +1,11 @@
1
+ import type { AssetInfo } from '@silentswap/sdk';
2
+ export interface UseSlippageUsdOptions {
3
+ inputUsdValue: number;
4
+ slippage: number;
5
+ inputAmount?: string;
6
+ token?: AssetInfo | null;
7
+ }
8
+ /**
9
+ * Hook to calculate slippage impact in USD
10
+ */
11
+ export declare function useSlippageUsd({ inputUsdValue, slippage, inputAmount, token }: UseSlippageUsdOptions): number;
@@ -0,0 +1,19 @@
1
+ import { useMemo } from 'react';
2
+ /**
3
+ * Hook to calculate slippage impact in USD
4
+ */
5
+ export function useSlippageUsd({ inputUsdValue, slippage, inputAmount, token }) {
6
+ return useMemo(() => {
7
+ if (inputAmount !== undefined && (!inputAmount || parseFloat(inputAmount) <= 0)) {
8
+ return 0;
9
+ }
10
+ if (token !== undefined && !token) {
11
+ return 0;
12
+ }
13
+ if (inputUsdValue <= 0) {
14
+ return 0;
15
+ }
16
+ const slippagePercent = slippage / 100;
17
+ return Math.abs(inputUsdValue * slippagePercent);
18
+ }, [inputUsdValue, slippage, inputAmount, token]);
19
+ }
@@ -0,0 +1,15 @@
1
+ import type { SolanaConnection } from './silent/solana-transaction.js';
2
+ /**
3
+ * Reusable hook for Solana wallet adapter setup
4
+ * Returns the Solana connector and connection adapter for use with SilentSwap hooks
5
+ */
6
+ export declare function useSolanaAdapter(): {
7
+ solanaConnector: {
8
+ signTransaction: (transaction: any) => Promise<any>;
9
+ sendTransaction: (transaction: any, connection: any) => Promise<string>;
10
+ publicKey: {
11
+ toString: () => string;
12
+ };
13
+ } | undefined;
14
+ solanaConnectionAdapter: SolanaConnection | undefined;
15
+ };