@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,331 @@
1
+ import { useCallback, useState } from 'react';
2
+ import { isSolanaAsset, parseEvmCaip19, S_CAIP19_USDC_AVALANCHE, getAssetByCaip19, solveOptimalUsdcAmount, N_RELAY_CHAIN_ID_SOLANA, SB58_ADDR_SOL_PROGRAM_SYSTEM, isSolanaNativeToken, parseSolanaCaip19, EVM_PHONY_ADDRESS, isValidSolanaAddress, getAddressFromCaip10, S0X_ADDR_USDC_AVALANCHE, isEvmNativeToken, caip19FungibleEvmToken, FacilitatorKeyType, createHdFacilitatorGroupFromEntropy, PublicKeyArgGroups, SB58_CHAIN_ID_SOLANA_MAINNET, caip19SplToken, DeliveryMethod, X_MAX_IMPACT_PERCENT, } from '@silentswap/sdk';
3
+ import { getAddress } from 'viem';
4
+ import { BigNumber } from 'bignumber.js';
5
+ /**
6
+ * Hook for calculating quotes and USDC amounts
7
+ *
8
+ * Extracted from useSilentQuote to improve modularity and reusability.
9
+ * Handles:
10
+ * - USDC amount calculation for different asset types (Solana, EVM, direct USDC)
11
+ * - Facilitator group resolution
12
+ * - Quote request construction and fetching
13
+ * - Destination amount updates
14
+ */
15
+ export function useQuoteCalculation({ address, evmAddress, wallet, depositorAddress, getQuote, getPrice, setDestinations, }) {
16
+ const [loadingAmounts, setLoadingAmounts] = useState(false);
17
+ const [depositAmountUsdc, setDepositAmountUsdc] = useState(0);
18
+ const calculateQuote = useCallback(async (debouncedSourceAsset, debouncedSourceAmount, destinations, splits) => {
19
+ if (!debouncedSourceAmount || parseFloat(debouncedSourceAmount) <= 0) {
20
+ return null;
21
+ }
22
+ const abortController = new AbortController();
23
+ try {
24
+ setLoadingAmounts(true);
25
+ if (abortController.signal.aborted) {
26
+ setLoadingAmounts(false);
27
+ return null;
28
+ }
29
+ const isSourceUsdcAvalanche = debouncedSourceAsset === S_CAIP19_USDC_AVALANCHE;
30
+ const isSourceSolana = isSolanaAsset(debouncedSourceAsset);
31
+ let usdcAmountOut;
32
+ let bridgeProviderResult = 'none';
33
+ let allowanceTargetResult = undefined;
34
+ if (isSourceUsdcAvalanche) {
35
+ const assetInfo = getAssetByCaip19(S_CAIP19_USDC_AVALANCHE);
36
+ if (!assetInfo)
37
+ throw new Error(`USDC asset not found`);
38
+ const sourceAmountBN = BigNumber(debouncedSourceAmount);
39
+ usdcAmountOut = sourceAmountBN.shiftedBy(assetInfo.decimals).toFixed(0);
40
+ }
41
+ else if (isSourceSolana) {
42
+ // Solana assets - both relay.link and deBridge support Solana
43
+ // solveOptimalUsdcAmount will compare both providers and choose the best rate
44
+ const assetInfo = getAssetByCaip19(debouncedSourceAsset);
45
+ if (!assetInfo)
46
+ throw new Error(`Solana asset not found`);
47
+ const sourceAmountBN = BigNumber(debouncedSourceAmount);
48
+ const sourceAmountInUnits = sourceAmountBN.shiftedBy(assetInfo.decimals).toFixed(0);
49
+ // Parse Solana CAIP-19 to get chain ID and token address
50
+ const solanaParsed = parseSolanaCaip19(debouncedSourceAsset);
51
+ if (!solanaParsed)
52
+ throw new Error(`Invalid Solana CAIP-19 format: ${debouncedSourceAsset}`);
53
+ // Determine origin currency: system program for native SOL, token address for SPL tokens
54
+ // This matches relay_origin_asset() from silentswap-v2-ui/src/services/relay-link.ts
55
+ const originCurrency = isSolanaNativeToken(debouncedSourceAsset)
56
+ ? SB58_ADDR_SOL_PROGRAM_SYSTEM
57
+ : solanaParsed.tokenAddress ||
58
+ (() => {
59
+ throw new Error(`Missing token address for Solana asset: ${debouncedSourceAsset}`);
60
+ })();
61
+ // For Solana swaps, we need:
62
+ // - Solana address for the 'user' parameter in relay quote (matches Svelte line 121: user: s_addr_sender)
63
+ // - EVM address for the 'recipient' parameter (matches Svelte line 122: recipient: s0x_signer)
64
+ // - EVM address for deposit calldata (matches Svelte line 156: signer: s0x_signer)
65
+ if (!evmAddress) {
66
+ throw new Error('EVM address required for Solana swaps (needed for deposit calldata and recipient)');
67
+ }
68
+ const solanaAddress = typeof address === 'string' && !address.startsWith('0x') ? address : null;
69
+ if (!solanaAddress) {
70
+ throw new Error('Solana address required for Solana swaps');
71
+ }
72
+ // Pass Solana address for user parameter, and EVM address for recipient and deposit calldata
73
+ // This matches Svelte behavior where s_addr_sender (Solana) is used for user, and s0x_signer (EVM) for recipient
74
+ const solveResult = await solveOptimalUsdcAmount(N_RELAY_CHAIN_ID_SOLANA, originCurrency, // Base58 Solana address (system program or SPL token)
75
+ sourceAmountInUnits, solanaAddress, // Pass Solana address for user parameter in relay quote
76
+ undefined, // depositCalldata will be created with EVM address
77
+ X_MAX_IMPACT_PERCENT, depositorAddress, // Required depositor address
78
+ getAddress(evmAddress));
79
+ if (abortController.signal.aborted) {
80
+ setLoadingAmounts(false);
81
+ return null;
82
+ }
83
+ // solveOptimalUsdcAmount compares both relay and deBridge and returns the best provider
84
+ usdcAmountOut = solveResult.usdcAmountOut.toString();
85
+ bridgeProviderResult = solveResult.provider; // Use the provider chosen by solveOptimalUsdcAmount
86
+ allowanceTargetResult = solveResult.allowanceTarget; // deBridge may return allowance target
87
+ }
88
+ else {
89
+ // EVM assets
90
+ const sourceAssetParsed = parseEvmCaip19(debouncedSourceAsset);
91
+ if (!sourceAssetParsed)
92
+ throw new Error(`Invalid source asset format`);
93
+ const srcChainId = sourceAssetParsed.chainId;
94
+ const srcToken = sourceAssetParsed.tokenAddress || '0x0000000000000000000000000000000000000000';
95
+ const assetInfo = getAssetByCaip19(debouncedSourceAsset);
96
+ if (!assetInfo)
97
+ throw new Error(`Asset not found`);
98
+ const sourceAmountBN = BigNumber(debouncedSourceAmount);
99
+ const sourceAmountInUnits = sourceAmountBN.shiftedBy(assetInfo.decimals).toFixed(0);
100
+ const solveResult = await solveOptimalUsdcAmount(srcChainId, srcToken, sourceAmountInUnits, address, undefined, // depositCalldata
101
+ X_MAX_IMPACT_PERCENT, depositorAddress);
102
+ if (abortController.signal.aborted) {
103
+ setLoadingAmounts(false);
104
+ return null;
105
+ }
106
+ usdcAmountOut = solveResult.usdcAmountOut.toString();
107
+ bridgeProviderResult = solveResult.provider;
108
+ allowanceTargetResult = solveResult.allowanceTarget;
109
+ }
110
+ // Use wallet's facilitator group if available, otherwise create a temporary one
111
+ let facilitatorGroupForQuote;
112
+ let viewerPk;
113
+ let groupPks;
114
+ if (wallet?.accounts[0]) {
115
+ facilitatorGroupForQuote = await wallet.accounts[0].group();
116
+ const viewer = await facilitatorGroupForQuote.viewer();
117
+ const { publicKeyBytes } = viewer.exportPublicKey('*', FacilitatorKeyType.SECP256K1);
118
+ viewerPk = publicKeyBytes;
119
+ groupPks = await facilitatorGroupForQuote.exportPublicKeys(destinations.length, PublicKeyArgGroups.GENERIC);
120
+ }
121
+ else {
122
+ const tempEntropy = new Uint8Array(32);
123
+ facilitatorGroupForQuote = await createHdFacilitatorGroupFromEntropy(tempEntropy, 0);
124
+ const viewer = await facilitatorGroupForQuote.viewer();
125
+ const { publicKeyBytes } = viewer.exportPublicKey('*', FacilitatorKeyType.SECP256K1);
126
+ viewerPk = publicKeyBytes;
127
+ groupPks = await facilitatorGroupForQuote.exportPublicKeys(destinations.length, PublicKeyArgGroups.GENERIC);
128
+ }
129
+ const usdcAmountBN = BigNumber(usdcAmountOut);
130
+ // Calculate split USDC amounts for each destination (matches Svelte Form.svelte lines 2550-2561)
131
+ // The value in the quote request should be USDC micro units, NOT the currencyOut.amount from egress quotes
132
+ // Egress quotes are used for retention rates and destination amounts, but NOT for quote request values
133
+ const splitUsdcAmounts = destinations.map((dest, idx) => {
134
+ const splitAmount = idx === 0 ? splits[0] : splits[idx] - splits[idx - 1];
135
+ return usdcAmountBN.times(splitAmount).toFixed(0);
136
+ });
137
+ const outputs = destinations.map((dest, idx) => {
138
+ const isDestSolana = isSolanaAsset(dest.asset);
139
+ let asset;
140
+ let recipientAddress; // Changed from `0x${string}` to string to support Solana addresses
141
+ if (isDestSolana) {
142
+ // Solana destination
143
+ const destParsed = parseSolanaCaip19(dest.asset);
144
+ if (!destParsed)
145
+ throw new Error(`Invalid Solana destination asset format`);
146
+ const isNative = isSolanaNativeToken(dest.asset);
147
+ // Extract base58 chain ID from CAIP-19 string for CAIP-19 construction
148
+ const match = dest.asset.match(/^solana:([a-zA-Z0-9]+)\//);
149
+ const chainIdBase58 = match ? match[1] : SB58_CHAIN_ID_SOLANA_MAINNET;
150
+ asset = isNative ? dest.asset : caip19SplToken(chainIdBase58, destParsed.tokenAddress);
151
+ // Extract Solana address from contact field
152
+ // CRITICAL: Must be a valid Solana base58 address, not an EVM address
153
+ if (dest.contact) {
154
+ // Check if contact is in CAIP10 format
155
+ if (dest.contact.startsWith('caip10:solana:')) {
156
+ recipientAddress = getAddressFromCaip10(dest.contact);
157
+ // Validate it's actually a Solana address
158
+ if (!isValidSolanaAddress(recipientAddress)) {
159
+ throw new Error(`Invalid Solana address extracted from CAIP10: ${dest.contact} -> ${recipientAddress}`);
160
+ }
161
+ }
162
+ else if (isValidSolanaAddress(dest.contact)) {
163
+ // Contact is already a plain Solana address (base58)
164
+ recipientAddress = dest.contact;
165
+ }
166
+ else {
167
+ // Contact is not a valid Solana address - this is an error
168
+ throw new Error(`Invalid Solana recipient address for destination ${idx}: ${dest.contact}. Expected base58 Solana address or caip10:solana:*: format.`);
169
+ }
170
+ }
171
+ else {
172
+ throw new Error(`Solana destination ${idx} requires a recipient address in the contact field`);
173
+ }
174
+ }
175
+ else {
176
+ // EVM destination
177
+ const destParsed = parseEvmCaip19(dest.asset);
178
+ if (!destParsed)
179
+ throw new Error(`Invalid destination asset format`);
180
+ const isNative = isEvmNativeToken(dest.asset);
181
+ const destChainId = destParsed.chainId;
182
+ asset = isNative
183
+ ? dest.asset
184
+ : caip19FungibleEvmToken(destChainId, destParsed.tokenAddress);
185
+ if (dest.contact) {
186
+ try {
187
+ // Extract EVM address from contact (handles both CAIP10 and plain addresses)
188
+ if (dest.contact.startsWith('caip10:eip155:')) {
189
+ recipientAddress = getAddressFromCaip10(dest.contact);
190
+ }
191
+ else {
192
+ recipientAddress = getAddress(dest.contact);
193
+ }
194
+ }
195
+ catch (error) {
196
+ console.error('Failed to parse EVM recipient address:', {
197
+ destination: idx,
198
+ contact: dest.contact,
199
+ error: error instanceof Error ? error.message : String(error),
200
+ });
201
+ throw new Error(`Invalid EVM recipient address for destination ${idx}: ${dest.contact}. ${error instanceof Error ? error.message : String(error)}`);
202
+ }
203
+ }
204
+ else {
205
+ recipientAddress = EVM_PHONY_ADDRESS;
206
+ }
207
+ }
208
+ // Use split USDC amount as the output value (matches Svelte Form.svelte line 2588)
209
+ // The value field in the quote request should be USDC micro units, not the destination asset amount
210
+ // This matches how Svelte calculates a_amounts_out and uses it as value in the quote request
211
+ const outputValue = splitUsdcAmounts[idx];
212
+ // Final validation: Ensure Solana destinations have Solana addresses, EVM destinations have EVM addresses
213
+ if (isDestSolana && !isValidSolanaAddress(recipientAddress)) {
214
+ throw new Error(`Invalid recipient address type for Solana destination ${idx}: ${recipientAddress}. Expected base58 Solana address.`);
215
+ }
216
+ else if (!isDestSolana && !recipientAddress.startsWith('0x')) {
217
+ throw new Error(`Invalid recipient address type for EVM destination ${idx}: ${recipientAddress}. Expected 0x-prefixed EVM address.`);
218
+ }
219
+ return {
220
+ method: DeliveryMethod.SNIP,
221
+ asset: asset,
222
+ value: outputValue,
223
+ recipient: recipientAddress,
224
+ facilitatorPublicKeys: groupPks[idx],
225
+ };
226
+ });
227
+ if (abortController.signal.aborted) {
228
+ setLoadingAmounts(false);
229
+ return null;
230
+ }
231
+ // CRITICAL: Use user's wallet address for quote request (matches Svelte behavior)
232
+ // In Svelte: s0x_signer = await contact_from_id(UserState.senderAtNamespace('eip155')).address()
233
+ // This is the user's EVM wallet address, NOT the facilitator group's viewer EVM signer
234
+ // The signer address in quote must match the signer address in deposit calldata
235
+ // Both use the user's wallet address, not the facilitator group's derived address
236
+ const quoteSignerAddress = typeof evmAddress === 'string' && evmAddress.startsWith('0x')
237
+ ? getAddress(evmAddress)
238
+ : (() => {
239
+ throw new Error('EVM address required for quote request');
240
+ })();
241
+ const quoteResult = await getQuote({
242
+ signer: quoteSignerAddress,
243
+ viewer: viewerPk,
244
+ outputs,
245
+ });
246
+ if (abortController.signal.aborted) {
247
+ setLoadingAmounts(false);
248
+ return null;
249
+ }
250
+ if (quoteResult) {
251
+ const depositAmount = BigNumber(usdcAmountOut).shiftedBy(-6).toNumber();
252
+ setDepositAmountUsdc(depositAmount);
253
+ // Store usdcAmount in micro units for swap execution
254
+ const updatedDestinations = await Promise.all(destinations.map(async (dest, idx) => {
255
+ const outputValueMicroUsdc = outputs[idx]?.value;
256
+ if (!outputValueMicroUsdc)
257
+ return dest;
258
+ const destAssetInfo = getAssetByCaip19(dest.asset);
259
+ if (!destAssetInfo)
260
+ return dest;
261
+ const usdValue = BigNumber(outputValueMicroUsdc).shiftedBy(-6).toNumber();
262
+ const isUsdc = dest.asset.includes(S0X_ADDR_USDC_AVALANCHE);
263
+ let amount = '';
264
+ if (isUsdc) {
265
+ amount = BigNumber(outputValueMicroUsdc)
266
+ .shiftedBy(-6)
267
+ .toFixed(destAssetInfo.precision ?? 6);
268
+ }
269
+ else {
270
+ try {
271
+ const assetPrice = await getPrice(destAssetInfo);
272
+ if (assetPrice > 0) {
273
+ amount = BigNumber(usdValue / assetPrice).toFixed(destAssetInfo.precision ?? 6);
274
+ }
275
+ else {
276
+ amount = `$${usdValue.toFixed(2)}`;
277
+ }
278
+ }
279
+ catch (error) {
280
+ console.error('Failed to get price for destination asset:', {
281
+ asset: dest.asset,
282
+ error: error instanceof Error ? error.message : String(error),
283
+ });
284
+ amount = `$${usdValue.toFixed(2)}`;
285
+ }
286
+ }
287
+ return { ...dest, amount };
288
+ }));
289
+ setDestinations(updatedDestinations);
290
+ // Reset loading state only on successful quote
291
+ setLoadingAmounts(false);
292
+ }
293
+ // Return both quote, usdcAmount, and facilitatorGroup (matches Svelte's solve_uusdc_amount return [usdcAmount, provider, allowanceTarget])
294
+ // The facilitatorGroup is resolved during quote fetching and can be reused in executeSwap
295
+ return quoteResult
296
+ ? {
297
+ quote: quoteResult,
298
+ usdcAmount: usdcAmountOut,
299
+ facilitatorGroup: facilitatorGroupForQuote,
300
+ bridgeProvider: bridgeProviderResult,
301
+ allowanceTarget: allowanceTargetResult,
302
+ }
303
+ : null;
304
+ }
305
+ catch (error) {
306
+ if (abortController.signal.aborted) {
307
+ setLoadingAmounts(false);
308
+ return null;
309
+ }
310
+ console.error('Quote failed:', {
311
+ sourceAsset: debouncedSourceAsset,
312
+ sourceAmount: debouncedSourceAmount,
313
+ destinationsCount: destinations.length,
314
+ error: error instanceof Error ? error.message : String(error),
315
+ stack: error instanceof Error ? error.stack : undefined,
316
+ });
317
+ // Always reset loading state on error to prevent UI freezing
318
+ setLoadingAmounts(false);
319
+ return null;
320
+ }
321
+ finally {
322
+ // Always ensure loading state is reset (safety net)
323
+ setLoadingAmounts(false);
324
+ }
325
+ }, [wallet, address, evmAddress, getQuote, getPrice, setDestinations, depositorAddress]);
326
+ return {
327
+ calculateQuote,
328
+ loadingAmounts,
329
+ depositAmountUsdc,
330
+ };
331
+ }
@@ -0,0 +1,18 @@
1
+ import type { SilentSwapClient, QuoteRequest, QuoteResponse } from '@silentswap/sdk';
2
+ /**
3
+ * Hook for fetching SilentSwap quotes
4
+ *
5
+ * This hook provides a simple interface for fetching quotes from the SilentSwap API.
6
+ * It handles loading states and error management for quote requests.
7
+ *
8
+ * @param client - SilentSwap client instance
9
+ * @param address - User's address (EVM hex address or Solana base58 address)
10
+ * @param setIsLoading - Callback to set loading state
11
+ * @param setCurrentStep - Callback to set current step message
12
+ * @param setError - Callback to set error state
13
+ * @param onStatus - Optional status update callback
14
+ * @returns Function to fetch a quote
15
+ */
16
+ export declare function useQuoteFetching(client: SilentSwapClient, address: `0x${string}` | string | null, setIsLoading: (loading: boolean) => void, setCurrentStep: (step: string) => void, setError: (error: Error | null) => void, onStatus?: (status: string) => void): {
17
+ getQuote: (request: QuoteRequest) => Promise<QuoteResponse | null>;
18
+ };
@@ -0,0 +1,54 @@
1
+ import { useCallback } from 'react';
2
+ /**
3
+ * Hook for fetching SilentSwap quotes
4
+ *
5
+ * This hook provides a simple interface for fetching quotes from the SilentSwap API.
6
+ * It handles loading states and error management for quote requests.
7
+ *
8
+ * @param client - SilentSwap client instance
9
+ * @param address - User's address (EVM hex address or Solana base58 address)
10
+ * @param setIsLoading - Callback to set loading state
11
+ * @param setCurrentStep - Callback to set current step message
12
+ * @param setError - Callback to set error state
13
+ * @param onStatus - Optional status update callback
14
+ * @returns Function to fetch a quote
15
+ */
16
+ export function useQuoteFetching(client, address, setIsLoading, setCurrentStep, setError, onStatus) {
17
+ /**
18
+ * Get a quote for a swap
19
+ *
20
+ * Fetches a quote from the SilentSwap API based on the provided request.
21
+ * The quote includes all necessary information for executing a swap, including
22
+ * facilitator public keys, authorizations, and transaction details.
23
+ *
24
+ * @param request - Quote request with signer, viewer, and outputs
25
+ * @returns Promise resolving to quote response or null if failed
26
+ */
27
+ const getQuote = useCallback(async (request) => {
28
+ if (!address) {
29
+ throw new Error('Address required for quote request');
30
+ }
31
+ setIsLoading(true);
32
+ setCurrentStep('Fetching quote');
33
+ onStatus?.('Fetching quote');
34
+ setError(null);
35
+ try {
36
+ const [error, response] = await client.quote(request);
37
+ if (error) {
38
+ throw new Error(`Failed to get quote: ${error.error}`);
39
+ }
40
+ const quoteResponse = response;
41
+ return quoteResponse;
42
+ }
43
+ catch (err) {
44
+ const error = err instanceof Error ? err : new Error('Failed to fetch quote');
45
+ setError(error);
46
+ return null;
47
+ }
48
+ finally {
49
+ setIsLoading(false);
50
+ setCurrentStep('');
51
+ }
52
+ }, [client, address, setIsLoading, setCurrentStep, setError, onStatus]);
53
+ return { getQuote };
54
+ }
@@ -0,0 +1,26 @@
1
+ import type { PublicClient, WalletClient, Hex } from 'viem';
2
+ import { type GatewayOrderStatus, type RefundEligibility } from '@silentswap/sdk';
3
+ export interface UseRefundOptions {
4
+ orderId?: string;
5
+ publicClient?: PublicClient;
6
+ walletClient?: WalletClient;
7
+ s0xGatewayAddress: Hex;
8
+ onSuccess?: (txHash: Hex) => void;
9
+ onError?: (error: Error) => void;
10
+ }
11
+ export interface UseRefundReturn {
12
+ isLoading: boolean;
13
+ error: Error | null;
14
+ orderStatus: GatewayOrderStatus | null;
15
+ refundEligibility: RefundEligibility | null;
16
+ isRecoveryEligible: boolean;
17
+ checkOrderStatus: (orderId: string, s0xGatewayAddress: Hex) => Promise<GatewayOrderStatus | null>;
18
+ checkRefund: (orderId: string, s0xGatewayAddress: Hex) => Promise<RefundEligibility | null>;
19
+ checkRecovery: (orderId: string, depositTimestamp: number) => Promise<boolean>;
20
+ refund: (orderId: string, s0xGatewayAddress: Hex) => Promise<Hex | null>;
21
+ reset: () => void;
22
+ }
23
+ /**
24
+ * React hook for managing refunds and recovery
25
+ */
26
+ export declare function useRefund({ orderId, publicClient, walletClient, s0xGatewayAddress, onSuccess, onError }: UseRefundOptions): UseRefundReturn;
@@ -0,0 +1,134 @@
1
+ import { useCallback, useState, useEffect } from 'react';
2
+ import { queryOrderStatus, checkRefundEligibility, executeRefund, checkRecoveryEligibility, } from '@silentswap/sdk';
3
+ /**
4
+ * React hook for managing refunds and recovery
5
+ */
6
+ export function useRefund({ orderId, publicClient, walletClient, s0xGatewayAddress, onSuccess, onError }) {
7
+ const [isLoading, setIsLoading] = useState(false);
8
+ const [error, setError] = useState(null);
9
+ const [orderStatus, setOrderStatus] = useState(null);
10
+ const [refundEligibility, setRefundEligibility] = useState(null);
11
+ const [isRecoveryEligible, setIsRecoveryEligible] = useState(false);
12
+ // Check order status from gateway contract
13
+ const checkOrderStatus = useCallback(async (orderId, s0xGatewayAddress) => {
14
+ if (!publicClient) {
15
+ setError(new Error('Public client not provided'));
16
+ return null;
17
+ }
18
+ setIsLoading(true);
19
+ setError(null);
20
+ try {
21
+ const status = await queryOrderStatus(publicClient, orderId, s0xGatewayAddress);
22
+ setOrderStatus(status);
23
+ return status;
24
+ }
25
+ catch (err) {
26
+ const error = err instanceof Error ? err : new Error('Failed to check order status');
27
+ setError(error);
28
+ onError?.(error);
29
+ return null;
30
+ }
31
+ finally {
32
+ setIsLoading(false);
33
+ }
34
+ }, [publicClient, s0xGatewayAddress, onError]);
35
+ // Check if refund is available
36
+ const checkRefund = useCallback(async (orderId) => {
37
+ if (!publicClient) {
38
+ setError(new Error('Public client not provided'));
39
+ return null;
40
+ }
41
+ setIsLoading(true);
42
+ setError(null);
43
+ try {
44
+ const eligibility = await checkRefundEligibility(publicClient, orderId, s0xGatewayAddress);
45
+ setRefundEligibility(eligibility);
46
+ return eligibility;
47
+ }
48
+ catch (err) {
49
+ const error = err instanceof Error ? err : new Error('Failed to check refund eligibility');
50
+ setError(error);
51
+ onError?.(error);
52
+ return null;
53
+ }
54
+ finally {
55
+ setIsLoading(false);
56
+ }
57
+ }, [publicClient, s0xGatewayAddress, onError]);
58
+ useEffect(() => {
59
+ if (!orderId)
60
+ return;
61
+ const tId = setInterval(() => {
62
+ checkRefund(orderId);
63
+ }, 10000);
64
+ return () => clearInterval(tId);
65
+ }, [orderId]);
66
+ // Check if recovery is available (for completed orders where delivery failed)
67
+ const checkRecovery = useCallback(async (orderId, depositTimestamp) => {
68
+ if (!publicClient) {
69
+ setError(new Error('Public client not provided'));
70
+ return false;
71
+ }
72
+ setIsLoading(true);
73
+ setError(null);
74
+ try {
75
+ const eligible = await checkRecoveryEligibility(publicClient, orderId, s0xGatewayAddress, depositTimestamp);
76
+ setIsRecoveryEligible(eligible);
77
+ return eligible;
78
+ }
79
+ catch (err) {
80
+ const error = err instanceof Error ? err : new Error('Failed to check recovery eligibility');
81
+ setError(error);
82
+ onError?.(error);
83
+ return false;
84
+ }
85
+ finally {
86
+ setIsLoading(false);
87
+ }
88
+ }, [publicClient, s0xGatewayAddress, onError]);
89
+ // Execute refund
90
+ const refund = useCallback(async (orderId) => {
91
+ if (!walletClient) {
92
+ setError(new Error('Wallet client not provided'));
93
+ return null;
94
+ }
95
+ setIsLoading(true);
96
+ setError(null);
97
+ try {
98
+ const txHash = await executeRefund(walletClient, orderId, s0xGatewayAddress);
99
+ onSuccess?.(txHash);
100
+ return txHash;
101
+ }
102
+ catch (err) {
103
+ const error = err instanceof Error ? err : new Error('Failed to execute refund');
104
+ setError(error);
105
+ onError?.(error);
106
+ return null;
107
+ }
108
+ finally {
109
+ setIsLoading(false);
110
+ }
111
+ }, [walletClient, s0xGatewayAddress, onSuccess, onError]);
112
+ // Reset state
113
+ const reset = useCallback(() => {
114
+ setIsLoading(false);
115
+ setError(null);
116
+ setOrderStatus(null);
117
+ setRefundEligibility(null);
118
+ setIsRecoveryEligible(false);
119
+ }, []);
120
+ return {
121
+ // State
122
+ isLoading,
123
+ error,
124
+ orderStatus,
125
+ refundEligibility,
126
+ isRecoveryEligible,
127
+ // Methods
128
+ checkOrderStatus,
129
+ checkRefund,
130
+ checkRecovery,
131
+ refund,
132
+ reset,
133
+ };
134
+ }
@@ -0,0 +1,16 @@
1
+ import { type SilentSwapClient, type SilentSwapClientConfig } from '@silentswap/sdk';
2
+ export interface useSilentClientOptions {
3
+ config?: SilentSwapClientConfig;
4
+ }
5
+ export interface useSilentClientReturn {
6
+ client: SilentSwapClient;
7
+ }
8
+ /**
9
+ * React hook for creating and managing a SilentSwap client instance
10
+ *
11
+ * If config is not provided, it will use the environment from EnvironmentProvider
12
+ *
13
+ * @param options - Configuration options for the client (optional if using EnvironmentProvider)
14
+ * @returns Object containing the SilentSwap client instance
15
+ */
16
+ export declare function useSilentClient({ config }?: useSilentClientOptions): useSilentClientReturn;
@@ -0,0 +1,32 @@
1
+ import { useMemo } from 'react';
2
+ import { createSilentSwapClient } from '@silentswap/sdk';
3
+ import { useSilentSwap } from '../../contexts/SilentSwapContext.js';
4
+ /**
5
+ * React hook for creating and managing a SilentSwap client instance
6
+ *
7
+ * If config is not provided, it will use the environment from EnvironmentProvider
8
+ *
9
+ * @param options - Configuration options for the client (optional if using EnvironmentProvider)
10
+ * @returns Object containing the SilentSwap client instance
11
+ */
12
+ export function useSilentClient({ config } = {}) {
13
+ // Try to get environment from context, but don't require it (for backward compatibility)
14
+ let environmentConfig;
15
+ try {
16
+ const { config: envConfig } = useSilentSwap();
17
+ environmentConfig = config || envConfig;
18
+ }
19
+ catch {
20
+ // Not in SilentSwapProvider - use provided config or throw
21
+ if (!config) {
22
+ throw new Error('useSilentClient requires either a config parameter or to be used within SilentSwapProvider');
23
+ }
24
+ environmentConfig = config;
25
+ }
26
+ const client = useMemo(() => {
27
+ return createSilentSwapClient(environmentConfig);
28
+ }, [environmentConfig]);
29
+ return {
30
+ client,
31
+ };
32
+ }