@silentswap/react 0.0.88 → 0.0.90
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/dist/contexts/SilentSwapContext.js +1 -1
- package/dist/hooks/silent/useBridgeExecution.js +83 -36
- package/dist/hooks/silent/useQuoteCalculation.js +44 -53
- package/dist/hooks/silent/useSilentQuote.js +54 -15
- package/dist/hooks/useStatus.d.ts +9 -3
- package/dist/hooks/useStatus.js +49 -59
- package/package.json +3 -3
|
@@ -110,7 +110,7 @@ function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, bit
|
|
|
110
110
|
const transactionAddress = useTransactionAddress(tokenIn, evmAddress, solAddress, bitcoinAddress);
|
|
111
111
|
const effectiveQuoteAddress = transactionAddress;
|
|
112
112
|
const { getPrice } = usePrices();
|
|
113
|
-
const { serviceFeeRate, overheadUsd, platformHealth: statusPlatformHealth, minimumDepositUusdc, maximumDepositUusdc, outputLimit, identity, volume, liquidity, storage, isLoading: statusLoading, } = useStatus(
|
|
113
|
+
const { serviceFeeRate, overheadUsd, platformHealth: statusPlatformHealth, minimumDepositUusdc, maximumDepositUusdc, outputLimit, identity, volume, liquidity, storage, isLoading: statusLoading, } = useStatus({ baseUrl: config.baseUrl ?? '', environment: config.environment, proId });
|
|
114
114
|
const { fetchEstimates, isLoading: egressEstimatesLoading, egressQuotes: egressQuotesFromEstimates, ingressQuote: ingressQuoteFromEstimates, depositAmountUsd: depositAmountUsdFromEstimates, bridgeProviderFromQuote, usdcPrice: usdcPriceFromEstimates, } = useEgressEstimates({
|
|
115
115
|
evmAddress,
|
|
116
116
|
solAddress,
|
|
@@ -241,17 +241,22 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
241
241
|
}
|
|
242
242
|
// Get relay origin asset parameters
|
|
243
243
|
const { originChainId, originCurrency } = getRelayOriginAssetFromCaip19(sourceAsset);
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
console.log('[SilentSwap:SolanaBridge] Start', {
|
|
245
|
+
sourceAsset,
|
|
246
|
+
sourceAmount,
|
|
247
|
+
usdcAmount,
|
|
248
|
+
solanaSenderAddress,
|
|
249
|
+
evmSignerAddress,
|
|
250
|
+
originChainId,
|
|
251
|
+
originCurrency,
|
|
252
|
+
depositorAddress,
|
|
253
|
+
hasDepositParams: !!depositParams,
|
|
254
|
+
});
|
|
248
255
|
if (!usdcAmount) {
|
|
249
256
|
throw new Error('USDC amount is required for Solana bridge execution. ' +
|
|
250
257
|
'It should be provided from the initial solveOptimalUsdcAmount call in handleGetQuote. ' +
|
|
251
258
|
'This matches Svelte behavior where solve_uusdc_amount is called once before order creation.');
|
|
252
259
|
}
|
|
253
|
-
// Use the provided usdcAmount directly (matches Svelte behavior)
|
|
254
|
-
// In Svelte: zg_amount_src_usdc is used directly without re-solving
|
|
255
260
|
const bridgeUsdcAmount = usdcAmount;
|
|
256
261
|
setCurrentStep('Fetching bridge quote');
|
|
257
262
|
onStatus?.('Fetching bridge quote');
|
|
@@ -292,35 +297,56 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
292
297
|
console.warn('DepositParams not provided, using phony calldata for bridge quote');
|
|
293
298
|
depositCalldataForExecution = createPhonyDepositCalldata(evmSignerAddress);
|
|
294
299
|
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
recipient: depositorAddress, // EVM depositor address (matches Svelte line 1308: recipient: S0X_ADDR_DEPOSITOR)
|
|
306
|
-
tradeType: 'EXACT_OUTPUT', // CRITICAL: Must use EXACT_OUTPUT for execution (matches Svelte line 1309)
|
|
307
|
-
txsGasLimit: 600_000, // Matches Svelte line 1310
|
|
300
|
+
const relayQuoteRequest = {
|
|
301
|
+
user: solanaSenderAddress,
|
|
302
|
+
originChainId,
|
|
303
|
+
originCurrency,
|
|
304
|
+
destinationChainId: NI_CHAIN_ID_AVALANCHE,
|
|
305
|
+
destinationCurrency: S0X_ADDR_USDC_AVALANCHE,
|
|
306
|
+
amount: bridgeUsdcAmount,
|
|
307
|
+
recipient: depositorAddress,
|
|
308
|
+
tradeType: 'EXACT_OUTPUT',
|
|
309
|
+
txsGasLimit: 600_000,
|
|
308
310
|
txs: [
|
|
309
311
|
{
|
|
310
|
-
to: S0X_ADDR_USDC_AVALANCHE,
|
|
311
|
-
value: '0',
|
|
312
|
-
data: approveUsdcCalldata,
|
|
312
|
+
to: S0X_ADDR_USDC_AVALANCHE,
|
|
313
|
+
value: '0',
|
|
314
|
+
data: approveUsdcCalldata,
|
|
313
315
|
},
|
|
314
316
|
{
|
|
315
|
-
to: depositorAddress,
|
|
316
|
-
value: '0',
|
|
317
|
-
data: depositCalldataForExecution,
|
|
317
|
+
to: depositorAddress,
|
|
318
|
+
value: '0',
|
|
319
|
+
data: depositCalldataForExecution,
|
|
318
320
|
},
|
|
319
321
|
],
|
|
322
|
+
};
|
|
323
|
+
console.log('[SilentSwap:SolanaBridge] Relay quote request (EXACT_OUTPUT)', {
|
|
324
|
+
user: relayQuoteRequest.user,
|
|
325
|
+
originChainId: relayQuoteRequest.originChainId,
|
|
326
|
+
originCurrency: relayQuoteRequest.originCurrency,
|
|
327
|
+
destinationChainId: relayQuoteRequest.destinationChainId,
|
|
328
|
+
amount: relayQuoteRequest.amount,
|
|
329
|
+
recipient: relayQuoteRequest.recipient,
|
|
330
|
+
tradeType: relayQuoteRequest.tradeType,
|
|
331
|
+
txsCount: relayQuoteRequest.txs.length,
|
|
332
|
+
depositCalldataLength: depositCalldataForExecution.length,
|
|
320
333
|
});
|
|
321
|
-
|
|
334
|
+
const relayQuote = await fetchRelayQuote(relayQuoteRequest);
|
|
335
|
+
console.log('[SilentSwap:SolanaBridge] Relay quote response', {
|
|
336
|
+
currencyIn: relayQuote.details?.currencyIn ? {
|
|
337
|
+
amount: relayQuote.details.currencyIn.amount,
|
|
338
|
+
amountUsd: relayQuote.details.currencyIn.amountUsd,
|
|
339
|
+
} : 'N/A',
|
|
340
|
+
currencyOut: relayQuote.details?.currencyOut ? {
|
|
341
|
+
amount: relayQuote.details.currencyOut.amount,
|
|
342
|
+
amountUsd: relayQuote.details.currencyOut.amountUsd,
|
|
343
|
+
} : 'N/A',
|
|
344
|
+
totalImpact: relayQuote.details?.totalImpact,
|
|
345
|
+
stepsCount: relayQuote.steps?.length,
|
|
346
|
+
stepIds: relayQuote.steps?.map((s) => s.id),
|
|
347
|
+
});
|
|
348
|
+
// Check price impact
|
|
322
349
|
const impactPercent = Number(relayQuote.details.totalImpact.percent);
|
|
323
|
-
// TODO: check negative impact
|
|
324
350
|
if (impactPercent > X_MAX_IMPACT_PERCENT) {
|
|
325
351
|
throw new Error(`Price impact across bridge too high: ${impactPercent.toFixed(2)}%`);
|
|
326
352
|
}
|
|
@@ -337,7 +363,14 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
337
363
|
throw new Error('No steps in relay quote response');
|
|
338
364
|
}
|
|
339
365
|
// Execute each step from relay.link
|
|
340
|
-
for (
|
|
366
|
+
for (let stepIdx = 0; stepIdx < relayQuote.steps.length; stepIdx++) {
|
|
367
|
+
const step = relayQuote.steps[stepIdx];
|
|
368
|
+
console.log(`[SilentSwap:SolanaBridge] Step ${stepIdx + 1}/${relayQuote.steps.length}`, {
|
|
369
|
+
id: step.id,
|
|
370
|
+
kind: step.kind,
|
|
371
|
+
itemsCount: step.items?.length,
|
|
372
|
+
requestId: step.requestId,
|
|
373
|
+
});
|
|
341
374
|
if (step.kind !== 'transaction') {
|
|
342
375
|
throw new Error(`Unsupported relay step kind: ${step.kind}`);
|
|
343
376
|
}
|
|
@@ -348,20 +381,34 @@ export function useBridgeExecution(walletClient, connector, solanaConnector, sol
|
|
|
348
381
|
const itemData = item.data;
|
|
349
382
|
// Solana transaction
|
|
350
383
|
if ('instructions' in itemData) {
|
|
351
|
-
|
|
384
|
+
const stepLabel = step.id === 'approve'
|
|
352
385
|
? 'Requesting approval...'
|
|
353
386
|
: step.id === 'deposit'
|
|
354
387
|
? 'Requesting deposit...'
|
|
355
|
-
: 'Requesting bridge...'
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
388
|
+
: 'Requesting bridge...';
|
|
389
|
+
setCurrentStep(stepLabel);
|
|
390
|
+
onStatus?.(stepLabel);
|
|
391
|
+
console.log(`[SilentSwap:SolanaBridge] Executing Solana tx: ${step.id}`, {
|
|
392
|
+
instructionsCount: itemData.instructions?.length,
|
|
393
|
+
feePayer: solanaSenderAddress,
|
|
394
|
+
});
|
|
361
395
|
// Convert relay step to BridgeTransaction
|
|
362
396
|
const solanaTx = convertRelaySolanaStepToTransaction(itemData, solanaSenderAddress, N_RELAY_CHAIN_ID_SOLANA);
|
|
363
397
|
// Execute Solana transaction
|
|
364
|
-
|
|
398
|
+
try {
|
|
399
|
+
await solanaExecutor(solanaTx);
|
|
400
|
+
console.log(`[SilentSwap:SolanaBridge] Step ${step.id} succeeded`);
|
|
401
|
+
}
|
|
402
|
+
catch (txError) {
|
|
403
|
+
console.error(`[SilentSwap:SolanaBridge] Step ${step.id} FAILED`, {
|
|
404
|
+
error: txError instanceof Error ? txError.message : String(txError),
|
|
405
|
+
stack: txError instanceof Error ? txError.stack : undefined,
|
|
406
|
+
});
|
|
407
|
+
throw txError;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
console.warn(`[SilentSwap:SolanaBridge] Step ${step.id} has no instructions, skipping`);
|
|
365
412
|
}
|
|
366
413
|
}
|
|
367
414
|
// Find request ID for status monitoring
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { useCallback, useState } from 'react';
|
|
2
|
-
import { isSolanaAsset, isBitcoinAsset, parseEvmCaip19, S_CAIP19_USDC_AVALANCHE, getAssetByCaip19, solveOptimalUsdcAmount,
|
|
2
|
+
import { isSolanaAsset, isBitcoinAsset, parseEvmCaip19, S_CAIP19_USDC_AVALANCHE, getAssetByCaip19, solveOptimalUsdcAmount, N_RELAY_CHAIN_ID_SOLANA, N_RELAY_CHAIN_ID_BITCOIN, SB58_ADDR_SOL_PROGRAM_SYSTEM, isSolanaNativeToken, parseSolanaCaip19, EVM_PHONY_ADDRESS, isValidSolanaAddress, isValidBitcoinAddress, isValidEvmAddress, getAddressFromCaip10, S0X_ADDR_USDC_AVALANCHE, caip19FungibleEvmToken, FacilitatorKeyType, createHdFacilitatorGroupFromEntropy, PublicKeyArgGroups, SB58_CHAIN_ID_SOLANA_MAINNET, caip19SplToken, DeliveryMethod, X_MAX_IMPACT_PERCENT, SBTC_ADDR_BITCOIN_NATIVE, isEvmAsset, } from '@silentswap/sdk';
|
|
3
3
|
import { getAddress } from 'viem';
|
|
4
4
|
import { BigNumber } from 'bignumber.js';
|
|
5
5
|
/**
|
|
@@ -34,6 +34,15 @@ export function useQuoteCalculation({ address, evmAddress, wallet, depositorAddr
|
|
|
34
34
|
let sourceAmountInUnitsForQuote;
|
|
35
35
|
let bridgeProviderResult = 'none';
|
|
36
36
|
let allowanceTargetResult = undefined;
|
|
37
|
+
console.log('[SilentSwap:QuoteCalc] Starting quote calculation', {
|
|
38
|
+
sourceAsset: debouncedSourceAsset,
|
|
39
|
+
sourceAmount: debouncedSourceAmount,
|
|
40
|
+
isSourceUsdcAvalanche,
|
|
41
|
+
isSourceSolana,
|
|
42
|
+
isSourceBitcoin,
|
|
43
|
+
destinationsCount: destinations.length,
|
|
44
|
+
splits,
|
|
45
|
+
});
|
|
37
46
|
if (isSourceUsdcAvalanche) {
|
|
38
47
|
const assetInfo = getAssetByCaip19(S_CAIP19_USDC_AVALANCHE);
|
|
39
48
|
if (!assetInfo)
|
|
@@ -41,31 +50,26 @@ export function useQuoteCalculation({ address, evmAddress, wallet, depositorAddr
|
|
|
41
50
|
const sourceAmountBN = BigNumber(debouncedSourceAmount);
|
|
42
51
|
sourceAmountInUnitsForQuote = sourceAmountBN.shiftedBy(assetInfo.decimals).toFixed(0);
|
|
43
52
|
usdcAmountOut = sourceAmountInUnitsForQuote;
|
|
53
|
+
console.log('[SilentSwap:QuoteCalc] Direct USDC deposit', {
|
|
54
|
+
sourceAmountInUnits: sourceAmountInUnitsForQuote,
|
|
55
|
+
usdcAmountOut,
|
|
56
|
+
});
|
|
44
57
|
}
|
|
45
58
|
else if (isSourceSolana) {
|
|
46
|
-
// Solana assets - use relay.link only (not deBridge)
|
|
47
59
|
const assetInfo = getAssetByCaip19(debouncedSourceAsset);
|
|
48
60
|
if (!assetInfo)
|
|
49
61
|
throw new Error(`Solana asset not found`);
|
|
50
62
|
const sourceAmountBN = BigNumber(debouncedSourceAmount);
|
|
51
63
|
sourceAmountInUnitsForQuote = sourceAmountBN.shiftedBy(assetInfo.decimals).toFixed(0);
|
|
52
|
-
const sourceAmountInUnits = sourceAmountInUnitsForQuote;
|
|
53
|
-
// Parse Solana CAIP-19 to get chain ID and token address
|
|
54
64
|
const solanaParsed = parseSolanaCaip19(debouncedSourceAsset);
|
|
55
65
|
if (!solanaParsed)
|
|
56
66
|
throw new Error(`Invalid Solana CAIP-19 format: ${debouncedSourceAsset}`);
|
|
57
|
-
// Determine origin currency: system program for native SOL, token address for SPL tokens
|
|
58
|
-
// This matches relay_origin_asset() from silentswap-v2-ui/src/services/relay-link.ts
|
|
59
67
|
const originCurrency = isSolanaNativeToken(debouncedSourceAsset)
|
|
60
68
|
? SB58_ADDR_SOL_PROGRAM_SYSTEM
|
|
61
69
|
: solanaParsed.tokenAddress ||
|
|
62
70
|
(() => {
|
|
63
71
|
throw new Error(`Missing token address for Solana asset: ${debouncedSourceAsset}`);
|
|
64
72
|
})();
|
|
65
|
-
// For Solana swaps, we need:
|
|
66
|
-
// - Solana address for the 'user' parameter in relay quote (matches Svelte line 121: user: s_addr_sender)
|
|
67
|
-
// - EVM address for the 'recipient' parameter (matches Svelte line 122: recipient: s0x_signer)
|
|
68
|
-
// - EVM address for deposit calldata (matches Svelte line 156: signer: s0x_signer)
|
|
69
73
|
if (!evmAddress) {
|
|
70
74
|
throw new Error('EVM address required for Solana swaps (needed for deposit calldata and recipient)');
|
|
71
75
|
}
|
|
@@ -73,71 +77,58 @@ export function useQuoteCalculation({ address, evmAddress, wallet, depositorAddr
|
|
|
73
77
|
if (!solanaAddress) {
|
|
74
78
|
throw new Error('Solana address required for Solana swaps');
|
|
75
79
|
}
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
}, abortController.signal);
|
|
80
|
+
console.log('[SilentSwap:QuoteCalc] Solana → solveOptimalUsdcAmount', {
|
|
81
|
+
chainId: N_RELAY_CHAIN_ID_SOLANA,
|
|
82
|
+
originCurrency,
|
|
83
|
+
sourceAmountInUnits: sourceAmountInUnitsForQuote,
|
|
84
|
+
solanaAddress,
|
|
85
|
+
evmAddress,
|
|
86
|
+
depositorAddress,
|
|
87
|
+
decimals: assetInfo.decimals,
|
|
88
|
+
precision: assetInfo.precision,
|
|
89
|
+
});
|
|
90
|
+
const solveResult = await solveOptimalUsdcAmount(N_RELAY_CHAIN_ID_SOLANA, originCurrency, sourceAmountInUnitsForQuote, solanaAddress, undefined, X_MAX_IMPACT_PERCENT, depositorAddress, getAddress(evmAddress));
|
|
88
91
|
if (abortController.signal.aborted) {
|
|
89
92
|
setLoadingAmounts(false);
|
|
90
93
|
return null;
|
|
91
94
|
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
95
|
+
usdcAmountOut = solveResult.usdcAmountOut.toString();
|
|
96
|
+
bridgeProviderResult = solveResult.provider;
|
|
97
|
+
allowanceTargetResult = solveResult.allowanceTarget;
|
|
98
|
+
console.log('[SilentSwap:QuoteCalc] Solana solve result', {
|
|
99
|
+
usdcAmountOut,
|
|
100
|
+
provider: bridgeProviderResult,
|
|
101
|
+
allowanceTarget: allowanceTargetResult,
|
|
102
|
+
});
|
|
97
103
|
}
|
|
98
104
|
else if (isSourceBitcoin) {
|
|
99
|
-
// Bitcoin assets -
|
|
105
|
+
// Bitcoin assets - use solveOptimalUsdcAmount for iterative EXACT_OUTPUT fitting
|
|
106
|
+
// (same issue as Solana: naive EXACT_INPUT amount causes simulation revert
|
|
107
|
+
// when used with EXACT_OUTPUT during execution due to slippage buffer)
|
|
100
108
|
const assetInfo = getAssetByCaip19(debouncedSourceAsset);
|
|
101
109
|
if (!assetInfo)
|
|
102
110
|
throw new Error(`Bitcoin asset not found`);
|
|
103
111
|
const sourceAmountBN = BigNumber(debouncedSourceAmount);
|
|
104
112
|
sourceAmountInUnitsForQuote = sourceAmountBN.shiftedBy(assetInfo.decimals).toFixed(0);
|
|
105
|
-
const sourceAmountInUnits = sourceAmountInUnitsForQuote;
|
|
106
|
-
// For Bitcoin swaps, we need:
|
|
107
|
-
// - Bitcoin address for the 'user' parameter in relay quote
|
|
108
|
-
// - EVM address for the 'recipient' parameter
|
|
109
|
-
// - EVM address for deposit calldata
|
|
110
113
|
if (!evmAddress) {
|
|
111
114
|
throw new Error('EVM address required for Bitcoin swaps (needed for deposit calldata and recipient)');
|
|
112
115
|
}
|
|
113
|
-
// Bitcoin address should be a string that doesn't start with 0x
|
|
114
|
-
// For now, we'll use the address parameter if it's not an EVM address
|
|
115
116
|
const bitcoinAddress = typeof address === 'string' && !address.startsWith('0x') ? address : null;
|
|
116
117
|
if (!bitcoinAddress) {
|
|
117
118
|
throw new Error('Bitcoin address required for Bitcoin swaps');
|
|
118
119
|
}
|
|
119
|
-
//
|
|
120
|
-
//
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
destinationChainId: NI_CHAIN_ID_AVALANCHE,
|
|
125
|
-
originCurrency: SBTC_ADDR_BITCOIN_NATIVE, // Bitcoin native token (BTC) - relay.link requires this specific address
|
|
126
|
-
destinationCurrency: S0X_ADDR_USDC_AVALANCHE,
|
|
127
|
-
amount: sourceAmountInUnits,
|
|
128
|
-
tradeType: 'EXACT_INPUT',
|
|
129
|
-
referrer: 'silentswap',
|
|
130
|
-
recipient: getAddress(evmAddress), // EVM address for recipient
|
|
131
|
-
}, abortController.signal);
|
|
120
|
+
// Use solveOptimalUsdcAmount which iteratively fits EXACT_OUTPUT amount
|
|
121
|
+
// to stay within the source balance (matches Svelte's relay_solve_uusdc_amount)
|
|
122
|
+
const solveResult = await solveOptimalUsdcAmount(N_RELAY_CHAIN_ID_BITCOIN, SBTC_ADDR_BITCOIN_NATIVE, sourceAmountInUnitsForQuote, bitcoinAddress, // Bitcoin address for relay 'user' parameter
|
|
123
|
+
undefined, // depositCalldata - will use phony
|
|
124
|
+
X_MAX_IMPACT_PERCENT, depositorAddress, getAddress(evmAddress));
|
|
132
125
|
if (abortController.signal.aborted) {
|
|
133
126
|
setLoadingAmounts(false);
|
|
134
127
|
return null;
|
|
135
128
|
}
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
allowanceTargetResult = undefined; // Bitcoin doesn't need allowance
|
|
140
|
-
// sourceAmountInUnitsForQuote already set above for Bitcoin
|
|
129
|
+
usdcAmountOut = solveResult.usdcAmountOut.toString();
|
|
130
|
+
bridgeProviderResult = solveResult.provider;
|
|
131
|
+
allowanceTargetResult = solveResult.allowanceTarget;
|
|
141
132
|
}
|
|
142
133
|
else {
|
|
143
134
|
// EVM assets
|
|
@@ -196,21 +196,32 @@ export function useSilentQuote({ client, address, evmAddress, solAddress, wallet
|
|
|
196
196
|
setIsSwapping(true);
|
|
197
197
|
setCurrentStep('Fetching quote...');
|
|
198
198
|
onStatus?.('Fetching quote...');
|
|
199
|
+
console.log('[SilentSwap:ExecuteSwap] Starting executeSwap', {
|
|
200
|
+
sourceAsset,
|
|
201
|
+
sourceAmount,
|
|
202
|
+
destinationsCount: destinations.length,
|
|
203
|
+
splits,
|
|
204
|
+
senderContactId,
|
|
205
|
+
integratorId,
|
|
206
|
+
});
|
|
199
207
|
const quoteResult = await calculateQuote(sourceAsset, sourceAmount, destinations, splits);
|
|
200
208
|
if (!quoteResult) {
|
|
201
|
-
// Ensure loadingAmounts is reset if handleGetQuote failed
|
|
202
209
|
throw new Error('Failed to get quote');
|
|
203
210
|
}
|
|
204
211
|
const { quote: quoteResponse, usdcAmount: effectiveUsdcAmount, sourceAmountInUnits, facilitatorGroup: passedFacilitatorGroup, bridgeProvider: effectiveProvider, allowanceTarget: effectiveAllowanceTarget, } = quoteResult;
|
|
212
|
+
console.log('[SilentSwap:ExecuteSwap] Quote result', {
|
|
213
|
+
usdcAmount: effectiveUsdcAmount,
|
|
214
|
+
sourceAmountInUnits,
|
|
215
|
+
provider: effectiveProvider,
|
|
216
|
+
allowanceTarget: effectiveAllowanceTarget,
|
|
217
|
+
authorizationsCount: quoteResponse.authorizations?.length,
|
|
218
|
+
});
|
|
205
219
|
setCurrentStep('Executing swap...');
|
|
206
220
|
onStatus?.('Executing swap...');
|
|
207
|
-
// Use the exact source amount in units from the quote result so order and deposit
|
|
208
|
-
// always match what was requested (avoids rounding/precision mismatch)
|
|
209
221
|
// Determine provider for bridge swap
|
|
210
222
|
const isDirectDeposit = sourceAsset === S_CAIP19_USDC_AVALANCHE;
|
|
211
223
|
let providerForSwap = undefined;
|
|
212
224
|
if (effectiveUsdcAmount && !isDirectDeposit) {
|
|
213
|
-
// When usdcAmount is provided and not direct deposit, we're doing a bridge swap and MUST have a provider
|
|
214
225
|
if (effectiveProvider === 'relay' || effectiveProvider === 'debridge') {
|
|
215
226
|
providerForSwap = effectiveProvider;
|
|
216
227
|
}
|
|
@@ -243,9 +254,15 @@ export function useSilentQuote({ client, address, evmAddress, solAddress, wallet
|
|
|
243
254
|
const evmSignerAddress = getAddress(evmAddress); // Use user's wallet address, not facilitator group's viewer EVM signer
|
|
244
255
|
// Handle Solana swaps
|
|
245
256
|
if (isSourceSolana) {
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
257
|
+
console.log('[SilentSwap:ExecuteSwap] Entering Solana swap path', {
|
|
258
|
+
sourceAsset,
|
|
259
|
+
sourceAmountInUnits,
|
|
260
|
+
usdcAmount: effectiveUsdcAmount,
|
|
261
|
+
evmSignerAddress,
|
|
262
|
+
viewingAuth,
|
|
263
|
+
provider: providerForSwap,
|
|
264
|
+
});
|
|
265
|
+
const result = await executeSolanaSwap(quoteResponse, sourceAsset, sourceAmountInUnits, effectiveUsdcAmount, senderContactId, solanaConnector, evmSignerAddress, viewingAuth, createOrder, executeSolanaBridge, resolvedGroup, integratorId);
|
|
249
266
|
setOrderId(result.orderId);
|
|
250
267
|
setViewingAuth(result.viewingAuth);
|
|
251
268
|
return result;
|
|
@@ -398,11 +415,20 @@ viewingAuth, createOrder, executeSolanaBridge, facilitatorGroup, integratorId) {
|
|
|
398
415
|
if (!solanaSenderAddress) {
|
|
399
416
|
throw new Error('Failed to get Solana sender address');
|
|
400
417
|
}
|
|
418
|
+
console.log('[SilentSwap:SolanaSwap] Creating order', {
|
|
419
|
+
sourceAsset,
|
|
420
|
+
sourceAmount,
|
|
421
|
+
sourceAmountBigInt: `${BigInt(sourceAmount)}`,
|
|
422
|
+
senderContactId,
|
|
423
|
+
solanaSenderAddress,
|
|
424
|
+
evmSignerAddress,
|
|
425
|
+
usdcAmount,
|
|
426
|
+
authorizationsCount: quoteResponse.authorizations?.length,
|
|
427
|
+
});
|
|
401
428
|
// Create order with empty authorizations (Solana doesn't use EIP-3009)
|
|
402
|
-
// Pass resolved facilitator group (matches Svelte behavior)
|
|
403
429
|
const orderResponse = await createOrder(quoteResponse, quoteResponse.authorizations.map((auth) => ({
|
|
404
430
|
...auth,
|
|
405
|
-
signature: '0x',
|
|
431
|
+
signature: '0x',
|
|
406
432
|
})), {
|
|
407
433
|
sourceAsset: {
|
|
408
434
|
caip19: sourceAsset,
|
|
@@ -413,18 +439,31 @@ viewingAuth, createOrder, executeSolanaBridge, facilitatorGroup, integratorId) {
|
|
|
413
439
|
},
|
|
414
440
|
...(integratorId && { integratorId }),
|
|
415
441
|
}, facilitatorGroup);
|
|
442
|
+
console.log('[SilentSwap:SolanaSwap] Order created', {
|
|
443
|
+
orderId: orderResponse.response?.orderId,
|
|
444
|
+
hasTransaction: !!orderResponse.transaction,
|
|
445
|
+
hasMetadata: !!orderResponse.transaction?.metadata,
|
|
446
|
+
hasParams: !!orderResponse.transaction?.metadata?.params,
|
|
447
|
+
});
|
|
416
448
|
// Get deposit parameters from order
|
|
417
449
|
const depositParams = orderResponse.transaction.metadata?.params;
|
|
418
450
|
if (!depositParams) {
|
|
419
451
|
throw new Error('Missing deposit parameters in order response');
|
|
420
452
|
}
|
|
453
|
+
console.log('[SilentSwap:SolanaSwap] Executing bridge', {
|
|
454
|
+
sourceAsset,
|
|
455
|
+
sourceAmount,
|
|
456
|
+
usdcAmount,
|
|
457
|
+
solanaSenderAddress,
|
|
458
|
+
evmSignerAddress,
|
|
459
|
+
depositParamsKeys: Object.keys(depositParams),
|
|
460
|
+
});
|
|
421
461
|
// Execute bridge transaction
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
depositParams);
|
|
462
|
+
const bridgeResult = await executeSolanaBridge(sourceAsset, sourceAmount, usdcAmount, solanaSenderAddress, evmSignerAddress, depositParams);
|
|
463
|
+
console.log('[SilentSwap:SolanaSwap] Bridge result', {
|
|
464
|
+
depositTxHash: bridgeResult.depositTxHash,
|
|
465
|
+
provider: bridgeResult.provider,
|
|
466
|
+
});
|
|
428
467
|
const resultOrderId = orderResponse.response.orderId;
|
|
429
468
|
// Note: setOrderId is not available in this function scope,
|
|
430
469
|
// but the orderId will be set by the calling executeSwap function
|
|
@@ -12,14 +12,20 @@ export interface StatusResponse {
|
|
|
12
12
|
storage?: Record<string, string>;
|
|
13
13
|
}
|
|
14
14
|
export interface UseStatusOptions {
|
|
15
|
+
/** Base URL for the status endpoint (e.g. https://api.silentswap.io) */
|
|
16
|
+
baseUrl: string;
|
|
17
|
+
/** Environment name, used to re-fetch when environment changes */
|
|
18
|
+
environment: string;
|
|
15
19
|
/** Pro user ID for volume tracking (from ?pro= URL or localStorage) */
|
|
16
20
|
proId?: string;
|
|
17
21
|
}
|
|
18
22
|
/**
|
|
19
|
-
* Hook to fetch the current platform status, fee rates, and limits
|
|
20
|
-
*
|
|
23
|
+
* Hook to fetch the current platform status, fee rates, and limits.
|
|
24
|
+
* Requires baseUrl and environment to be passed directly (since this hook
|
|
25
|
+
* is called inside SilentSwapInnerProvider before the context is available).
|
|
26
|
+
* When proId is provided, fetches with ?pro= for pro user volume tracking.
|
|
21
27
|
*/
|
|
22
|
-
export declare function useStatus(options
|
|
28
|
+
export declare function useStatus(options: UseStatusOptions): {
|
|
23
29
|
status: StatusResponse;
|
|
24
30
|
isLoading: boolean;
|
|
25
31
|
error: Error | null;
|
package/dist/hooks/useStatus.js
CHANGED
|
@@ -1,72 +1,62 @@
|
|
|
1
1
|
import { useState, useEffect } from 'react';
|
|
2
|
-
|
|
2
|
+
// Module-level cache: survives component unmount/remount and Strict Mode cycles.
|
|
3
|
+
// Keyed by fetch params so a new fetch only happens when params actually change.
|
|
4
|
+
let statusCache = null;
|
|
3
5
|
/**
|
|
4
|
-
* Hook to fetch the current platform status, fee rates, and limits
|
|
5
|
-
*
|
|
6
|
+
* Hook to fetch the current platform status, fee rates, and limits.
|
|
7
|
+
* Requires baseUrl and environment to be passed directly (since this hook
|
|
8
|
+
* is called inside SilentSwapInnerProvider before the context is available).
|
|
9
|
+
* When proId is provided, fetches with ?pro= for pro user volume tracking.
|
|
6
10
|
*/
|
|
7
11
|
export function useStatus(options) {
|
|
8
|
-
const {
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const [
|
|
12
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
12
|
+
const { baseUrl, environment, proId } = options;
|
|
13
|
+
const fetchKey = `${baseUrl}|${environment}|${proId ?? ''}`;
|
|
14
|
+
const [status, setStatus] = useState(() => statusCache?.key === fetchKey && statusCache.data ? statusCache.data : {});
|
|
15
|
+
const [isLoading, setIsLoading] = useState(() => !(statusCache?.key === fetchKey && statusCache.data && Object.keys(statusCache.data).length > 0));
|
|
13
16
|
const [error, setError] = useState(null);
|
|
14
17
|
useEffect(() => {
|
|
15
|
-
|
|
16
|
-
if (!baseUrl) {
|
|
17
|
-
console.warn('[useStatus] baseUrl is not available yet');
|
|
18
|
+
if (!baseUrl)
|
|
18
19
|
return;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
const isPlaceholderClient = client &&
|
|
25
|
-
typeof client === 'object' &&
|
|
26
|
-
Object.getPrototypeOf(client) === Object.prototype &&
|
|
27
|
-
Object.keys(client).length === 0;
|
|
28
|
-
if (isPlaceholderClient) {
|
|
29
|
-
console.warn('[useStatus] Context not yet initialized (placeholder client detected), skipping fetch until real context is available');
|
|
30
|
-
setIsLoading(false);
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
let isMounted = true;
|
|
34
|
-
const fetchStatus = async () => {
|
|
35
|
-
try {
|
|
36
|
-
setIsLoading(true);
|
|
37
|
-
const search = proId ? `?${new URLSearchParams({ pro: proId }).toString()}` : '';
|
|
38
|
-
const statusUrl = `${baseUrl}/status${search}`;
|
|
39
|
-
console.log('[useStatus] Fetching status from:', statusUrl, 'environment:', config.environment);
|
|
40
|
-
const response = await fetch(statusUrl);
|
|
41
|
-
if (!response.ok) {
|
|
42
|
-
throw new Error(`Failed to fetch status: ${response.statusText}`);
|
|
43
|
-
}
|
|
44
|
-
const data = await response.json();
|
|
45
|
-
if (isMounted) {
|
|
46
|
-
console.log('[useStatus] Status fetched successfully from:', statusUrl);
|
|
20
|
+
// Already fetched or in-flight for this exact param set — skip
|
|
21
|
+
if (statusCache?.key === fetchKey) {
|
|
22
|
+
if (statusCache.promise) {
|
|
23
|
+
// In-flight from another mount — wait for it and apply
|
|
24
|
+
statusCache.promise.then((data) => {
|
|
47
25
|
setStatus(data);
|
|
48
|
-
setError(null);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
catch (err) {
|
|
52
|
-
if (isMounted) {
|
|
53
|
-
console.error('[useStatus] Failed to fetch status from:', baseUrl, err);
|
|
54
|
-
setError(err instanceof Error ? err : new Error('Failed to fetch status'));
|
|
55
|
-
// Use defaults on error
|
|
56
|
-
setStatus({});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
finally {
|
|
60
|
-
if (isMounted) {
|
|
61
26
|
setIsLoading(false);
|
|
62
|
-
}
|
|
27
|
+
});
|
|
63
28
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const search = proId ? `?${new URLSearchParams({ pro: proId }).toString()}` : '';
|
|
32
|
+
const statusUrl = `${baseUrl}/status${search}`;
|
|
33
|
+
console.log('[useStatus] Fetching status from:', statusUrl, 'environment:', environment);
|
|
34
|
+
const promise = fetch(statusUrl)
|
|
35
|
+
.then((response) => {
|
|
36
|
+
if (!response.ok)
|
|
37
|
+
throw new Error(`Failed to fetch status: ${response.statusText}`);
|
|
38
|
+
return response.json();
|
|
39
|
+
})
|
|
40
|
+
.then((data) => {
|
|
41
|
+
console.log('[useStatus] Status fetched successfully from:', statusUrl);
|
|
42
|
+
statusCache = { key: fetchKey, data };
|
|
43
|
+
setStatus(data);
|
|
44
|
+
setError(null);
|
|
45
|
+
return data;
|
|
46
|
+
})
|
|
47
|
+
.catch((err) => {
|
|
48
|
+
console.error('[useStatus] Failed to fetch status from:', baseUrl, err);
|
|
49
|
+
setError(err instanceof Error ? err : new Error('Failed to fetch status'));
|
|
50
|
+
setStatus({});
|
|
51
|
+
statusCache = null; // Allow retry
|
|
52
|
+
return {};
|
|
53
|
+
})
|
|
54
|
+
.finally(() => {
|
|
55
|
+
setIsLoading(false);
|
|
56
|
+
});
|
|
57
|
+
// Claim the slot immediately so no other mount starts a duplicate
|
|
58
|
+
statusCache = { key: fetchKey, data: {}, promise };
|
|
59
|
+
}, [baseUrl, environment, proId, fetchKey]);
|
|
70
60
|
// Convert overheadUsd from string to number
|
|
71
61
|
const overheadUsd = status.overheadUsd ? parseFloat(status.overheadUsd) : 0;
|
|
72
62
|
// serviceFeeRate is a Decimal (0.01 = 1%), convert to number
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@silentswap/react",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.90",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@bigmi/core": "^0.6.5",
|
|
26
26
|
"@ensdomains/ensjs": "^4.2.0",
|
|
27
|
-
"@silentswap/sdk": "0.0.
|
|
28
|
-
"@silentswap/ui-kit": "0.0.
|
|
27
|
+
"@silentswap/sdk": "0.0.90",
|
|
28
|
+
"@silentswap/ui-kit": "0.0.90",
|
|
29
29
|
"@solana/codecs-strings": "^5.1.0",
|
|
30
30
|
"@solana/kit": "^5.1.0",
|
|
31
31
|
"@solana/rpc": "^5.1.0",
|