@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.
- package/README.md +48 -0
- package/dist/contexts/AssetsContext.d.ts +24 -0
- package/dist/contexts/AssetsContext.js +83 -0
- package/dist/contexts/BalancesContext.d.ts +28 -0
- package/dist/contexts/BalancesContext.js +533 -0
- package/dist/contexts/OrdersContext.d.ts +53 -0
- package/dist/contexts/OrdersContext.js +240 -0
- package/dist/contexts/PricesContext.d.ts +12 -0
- package/dist/contexts/PricesContext.js +109 -0
- package/dist/contexts/SilentSwapContext.d.ts +58 -0
- package/dist/contexts/SilentSwapContext.js +205 -0
- package/dist/hooks/silent/orderTrackingWebSocketManager.d.ts +48 -0
- package/dist/hooks/silent/orderTrackingWebSocketManager.js +284 -0
- package/dist/hooks/silent/solana-transaction.d.ts +60 -0
- package/dist/hooks/silent/solana-transaction.js +236 -0
- package/dist/hooks/silent/useAuth.d.ts +90 -0
- package/dist/hooks/silent/useAuth.js +269 -0
- package/dist/hooks/silent/useBridgeExecution.d.ts +39 -0
- package/dist/hooks/silent/useBridgeExecution.js +877 -0
- package/dist/hooks/silent/useOrderSigning.d.ts +34 -0
- package/dist/hooks/silent/useOrderSigning.js +133 -0
- package/dist/hooks/silent/useOrderTracking.d.ts +174 -0
- package/dist/hooks/silent/useOrderTracking.js +524 -0
- package/dist/hooks/silent/useQuoteCalculation.d.ts +50 -0
- package/dist/hooks/silent/useQuoteCalculation.js +331 -0
- package/dist/hooks/silent/useQuoteFetching.d.ts +18 -0
- package/dist/hooks/silent/useQuoteFetching.js +54 -0
- package/dist/hooks/silent/useRefund.d.ts +26 -0
- package/dist/hooks/silent/useRefund.js +134 -0
- package/dist/hooks/silent/useSilentClient.d.ts +16 -0
- package/dist/hooks/silent/useSilentClient.js +32 -0
- package/dist/hooks/silent/useSilentOrders.d.ts +174 -0
- package/dist/hooks/silent/useSilentOrders.js +73 -0
- package/dist/hooks/silent/useSilentQuote.d.ts +88 -0
- package/dist/hooks/silent/useSilentQuote.js +381 -0
- package/dist/hooks/silent/useWallet.d.ts +76 -0
- package/dist/hooks/silent/useWallet.js +203 -0
- package/dist/hooks/useAssetPrice.d.ts +8 -0
- package/dist/hooks/useAssetPrice.js +47 -0
- package/dist/hooks/useContacts.d.ts +52 -0
- package/dist/hooks/useContacts.js +259 -0
- package/dist/hooks/useEgressEstimates.d.ts +32 -0
- package/dist/hooks/useEgressEstimates.js +230 -0
- package/dist/hooks/useHiddenSwapFees.d.ts +22 -0
- package/dist/hooks/useHiddenSwapFees.js +81 -0
- package/dist/hooks/useOrderEstimates.d.ts +37 -0
- package/dist/hooks/useOrderEstimates.js +393 -0
- package/dist/hooks/useOutputAssetInfo.d.ts +12 -0
- package/dist/hooks/useOutputAssetInfo.js +38 -0
- package/dist/hooks/usePrices.d.ts +60 -0
- package/dist/hooks/usePrices.js +188 -0
- package/dist/hooks/useQuote.d.ts +73 -0
- package/dist/hooks/useQuote.js +507 -0
- package/dist/hooks/useResetSwapForm.d.ts +16 -0
- package/dist/hooks/useResetSwapForm.js +68 -0
- package/dist/hooks/useSlippageUsd.d.ts +11 -0
- package/dist/hooks/useSlippageUsd.js +19 -0
- package/dist/hooks/useSolanaAdapter.d.ts +15 -0
- package/dist/hooks/useSolanaAdapter.js +55 -0
- package/dist/hooks/useStatus.d.ts +25 -0
- package/dist/hooks/useStatus.js +60 -0
- package/dist/hooks/useSwap.d.ts +67 -0
- package/dist/hooks/useSwap.js +285 -0
- package/dist/hooks/useTransaction.d.ts +119 -0
- package/dist/hooks/useTransaction.js +353 -0
- package/dist/hooks/useTransactionAddress.d.ts +11 -0
- package/dist/hooks/useTransactionAddress.js +26 -0
- package/dist/hooks/useUsdValue.d.ts +7 -0
- package/dist/hooks/useUsdValue.js +19 -0
- package/dist/index.d.ts +54 -0
- package/dist/index.js +41 -0
- package/dist/stories/SilentSwapOverview.stories.d.ts +10 -0
- package/dist/stories/SilentSwapOverview.stories.js +364 -0
- package/dist/stories/useAuth.stories.d.ts +6 -0
- package/dist/stories/useAuth.stories.js +55 -0
- package/dist/stories/useSilentClient.stories.d.ts +9 -0
- package/dist/stories/useSilentClient.stories.js +39 -0
- package/dist/stories/useSilentOrders.stories.d.ts +1 -0
- package/dist/stories/useSilentOrders.stories.js +1 -0
- package/dist/stories/useSilentQuote.stories.d.ts +6 -0
- package/dist/stories/useSilentQuote.stories.js +267 -0
- package/dist/stories/useTransaction.stories.d.ts +6 -0
- package/dist/stories/useTransaction.stories.js +121 -0
- package/dist/utils/formatters.d.ts +33 -0
- package/dist/utils/formatters.js +82 -0
- 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
|
+
}
|