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