@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,81 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { BigNumber } from 'bignumber.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hook to calculate fees for hidden swap
|
|
5
|
+
* Extracts actual fees from bridge quotes when available, otherwise uses estimated rates
|
|
6
|
+
*/
|
|
7
|
+
export function useHiddenSwapFees({ depositAmountUsdc, bridgeProvider, serviceFeeRate = 0.01, overheadUsd = 0, usdcPrice = 1, bridgeFeeRate = 0.001, ingressQuote, egressQuotes, }) {
|
|
8
|
+
const fees = useMemo(() => {
|
|
9
|
+
if (depositAmountUsdc <= 0) {
|
|
10
|
+
return {
|
|
11
|
+
serviceFeeUsd: 0,
|
|
12
|
+
bridgeFeeIngressUsd: 0,
|
|
13
|
+
bridgeFeeEgressUsd: 0,
|
|
14
|
+
totalFeesUsd: 0,
|
|
15
|
+
};
|
|
16
|
+
}
|
|
17
|
+
// Extract ingress bridge fee from quote
|
|
18
|
+
let bridgeFeeIngress = 0;
|
|
19
|
+
if (ingressQuote) {
|
|
20
|
+
if ('fees' in ingressQuote) {
|
|
21
|
+
// Relay.link quote
|
|
22
|
+
bridgeFeeIngress = BigNumber(ingressQuote.fees?.relayer?.amountUsd || 0).toNumber();
|
|
23
|
+
}
|
|
24
|
+
else if ('estimation' in ingressQuote) {
|
|
25
|
+
// DeBridge quote
|
|
26
|
+
const debridgeQuote = ingressQuote;
|
|
27
|
+
bridgeFeeIngress = BigNumber(debridgeQuote.estimation.costDetails
|
|
28
|
+
?.map((detail) => Number(detail.payload?.feeApproximateUsdValue || 0))
|
|
29
|
+
?.reduce((sum, fee) => sum + fee, 0) || 0).toNumber();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else if (bridgeProvider && bridgeProvider !== 'none') {
|
|
33
|
+
bridgeFeeIngress = depositAmountUsdc * bridgeFeeRate;
|
|
34
|
+
}
|
|
35
|
+
// Extract egress bridge fees from quotes
|
|
36
|
+
let bridgeFeeEgress = 0;
|
|
37
|
+
if (egressQuotes && egressQuotes.length > 0) {
|
|
38
|
+
const egressFees = egressQuotes.map((quote) => {
|
|
39
|
+
if (!quote)
|
|
40
|
+
return BigNumber(0);
|
|
41
|
+
if ('fees' in quote) {
|
|
42
|
+
const relayerFee = BigNumber(quote.fees?.relayer?.amountUsd || 0);
|
|
43
|
+
const gasFee = BigNumber(quote.fees?.gas?.amountUsd || 0);
|
|
44
|
+
return relayerFee.plus(gasFee);
|
|
45
|
+
}
|
|
46
|
+
else if ('estimation' in quote) {
|
|
47
|
+
const debridgeQuote = quote;
|
|
48
|
+
const feeFromCostDetails = BigNumber(debridgeQuote.estimation.costDetails
|
|
49
|
+
?.map((detail) => Number(detail.payload?.feeApproximateUsdValue || 0))
|
|
50
|
+
?.reduce((sum, fee) => sum + fee, 0) || 0);
|
|
51
|
+
// Match Svelte behavior: add the same value twice
|
|
52
|
+
return feeFromCostDetails.plus(feeFromCostDetails);
|
|
53
|
+
}
|
|
54
|
+
return BigNumber(0);
|
|
55
|
+
});
|
|
56
|
+
bridgeFeeEgress = egressFees.reduce((sum, fee) => sum.plus(fee), BigNumber(0)).toNumber();
|
|
57
|
+
}
|
|
58
|
+
else if (bridgeProvider && bridgeProvider !== 'none') {
|
|
59
|
+
bridgeFeeEgress = depositAmountUsdc * bridgeFeeRate;
|
|
60
|
+
}
|
|
61
|
+
// Calculate service fees
|
|
62
|
+
const serviceFee = BigNumber(depositAmountUsdc).times(serviceFeeRate).plus(overheadUsd).times(usdcPrice).toNumber();
|
|
63
|
+
const totalFees = serviceFee + bridgeFeeIngress + bridgeFeeEgress;
|
|
64
|
+
return {
|
|
65
|
+
serviceFeeUsd: serviceFee,
|
|
66
|
+
bridgeFeeIngressUsd: bridgeFeeIngress,
|
|
67
|
+
bridgeFeeEgressUsd: bridgeFeeEgress,
|
|
68
|
+
totalFeesUsd: totalFees,
|
|
69
|
+
};
|
|
70
|
+
}, [
|
|
71
|
+
depositAmountUsdc,
|
|
72
|
+
bridgeProvider,
|
|
73
|
+
serviceFeeRate,
|
|
74
|
+
overheadUsd,
|
|
75
|
+
usdcPrice,
|
|
76
|
+
bridgeFeeRate,
|
|
77
|
+
ingressQuote,
|
|
78
|
+
egressQuotes,
|
|
79
|
+
]);
|
|
80
|
+
return fees;
|
|
81
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Estimate, RelayQuoteResponse, DeBridgeOrderResponse } from '@silentswap/sdk';
|
|
2
|
+
import { type AssetInfo } from '@silentswap/sdk';
|
|
3
|
+
export interface OrderEstimateResult {
|
|
4
|
+
ingressGasAmount: bigint;
|
|
5
|
+
ingressRate: number;
|
|
6
|
+
egressRates: number[];
|
|
7
|
+
ingressEstimate: Estimate;
|
|
8
|
+
egressEstimates: Estimate[];
|
|
9
|
+
ingressQuote?: RelayQuoteResponse | DeBridgeOrderResponse;
|
|
10
|
+
egressQuotes?: (RelayQuoteResponse | DeBridgeOrderResponse)[];
|
|
11
|
+
calculatedInputAmount?: number;
|
|
12
|
+
}
|
|
13
|
+
export interface UseOrderEstimatesOptions {
|
|
14
|
+
/** User's EVM address (hex address starting with 0x) */
|
|
15
|
+
evmAddress?: `0x${string}`;
|
|
16
|
+
/** User's Solana address (base58 address) */
|
|
17
|
+
solAddress?: string;
|
|
18
|
+
/** Source asset for determining which address to use (if Solana, use solAddress; if EVM, use evmAddress) */
|
|
19
|
+
sourceAsset?: AssetInfo | null;
|
|
20
|
+
/** Maximum price impact percentage allowed */
|
|
21
|
+
maxImpactPercent?: number;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Hook for fetching both ingress and egress estimates in parallel
|
|
25
|
+
* Matches the behavior of estimate_order() from silentswap-v2-ui/src/web3/estimate.ts
|
|
26
|
+
*
|
|
27
|
+
* This hook fetches:
|
|
28
|
+
* - Ingress quote: from source chain to Avalanche (as USDC)
|
|
29
|
+
* - Egress quotes: from Avalanche (USDC) to each destination chain
|
|
30
|
+
*
|
|
31
|
+
* Returns retention rates that account for bridge fees and slippage
|
|
32
|
+
*/
|
|
33
|
+
export declare function useOrderEstimates({ evmAddress, solAddress, sourceAsset, maxImpactPercent, }?: UseOrderEstimatesOptions): {
|
|
34
|
+
estimateOrder: (usdcPrice: number, sourceAsset: AssetInfo, sourcePrice: number, sourceAmount: number | string, destinationAssets: AssetInfo[], destinationPrices: number[], splits: number[], calculationDirection?: "input-to-output" | "output-to-input", outputAmounts?: string[]) => Promise<OrderEstimateResult>;
|
|
35
|
+
isLoading: boolean;
|
|
36
|
+
error: Error | null;
|
|
37
|
+
};
|
|
@@ -0,0 +1,393 @@
|
|
|
1
|
+
import { useCallback, useRef, useState, useMemo } from 'react';
|
|
2
|
+
import BigNumber from 'bignumber.js';
|
|
3
|
+
import { parseEvmCaip19, parseSolanaCaip19, isEvmNativeToken, isSolanaNativeToken, isSolanaAsset, } from '@silentswap/sdk';
|
|
4
|
+
import { useQuote } from './useQuote.js';
|
|
5
|
+
// Fallback EVM address for egress quotes when evmAddress is not available
|
|
6
|
+
// Egress quotes always start from Avalanche (EVM), so they require an EVM address
|
|
7
|
+
const EVM_PHONY_ADDRESS_FALLBACK = '0x1111111111111111111111111111111111111111';
|
|
8
|
+
/**
|
|
9
|
+
* Hook for fetching both ingress and egress estimates in parallel
|
|
10
|
+
* Matches the behavior of estimate_order() from silentswap-v2-ui/src/web3/estimate.ts
|
|
11
|
+
*
|
|
12
|
+
* This hook fetches:
|
|
13
|
+
* - Ingress quote: from source chain to Avalanche (as USDC)
|
|
14
|
+
* - Egress quotes: from Avalanche (USDC) to each destination chain
|
|
15
|
+
*
|
|
16
|
+
* Returns retention rates that account for bridge fees and slippage
|
|
17
|
+
*/
|
|
18
|
+
export function useOrderEstimates({ evmAddress, solAddress, sourceAsset, maxImpactPercent, } = {}) {
|
|
19
|
+
const [error, setError] = useState(null);
|
|
20
|
+
// Determine address for ingress quotes based on source chain
|
|
21
|
+
// For ingress, we need the address that matches the source chain (not sourceAsset)
|
|
22
|
+
// For egress, we always start from Avalanche (EVM), so we can use evmAddress as default
|
|
23
|
+
// But we'll create separate useQuote instances to handle this correctly
|
|
24
|
+
const ingressAddress = useMemo(() => {
|
|
25
|
+
if (sourceAsset) {
|
|
26
|
+
// For ingress, address should match the source chain
|
|
27
|
+
return sourceAsset.caip19.includes('solana') ? solAddress : evmAddress;
|
|
28
|
+
}
|
|
29
|
+
return undefined;
|
|
30
|
+
}, [sourceAsset, solAddress, evmAddress]);
|
|
31
|
+
// For egress, we always start from Avalanche (EVM), so use EVM address
|
|
32
|
+
// If evmAddress is undefined, use the phony EVM address as fallback
|
|
33
|
+
// This ensures egress quotes always use an EVM address format (required for Avalanche origin)
|
|
34
|
+
const egressAddress = evmAddress || EVM_PHONY_ADDRESS_FALLBACK;
|
|
35
|
+
// Initialize useQuote hooks - one for ingress (uses address based on source chain)
|
|
36
|
+
// and one for egress (uses EVM address since egress always starts from Avalanche)
|
|
37
|
+
const ingressQuote = useQuote({
|
|
38
|
+
address: ingressAddress,
|
|
39
|
+
maxImpactPercent,
|
|
40
|
+
});
|
|
41
|
+
const egressQuote = useQuote({
|
|
42
|
+
address: egressAddress, // Always an EVM address (either evmAddress or EVM_PHONY_ADDRESS_FALLBACK)
|
|
43
|
+
maxImpactPercent,
|
|
44
|
+
});
|
|
45
|
+
const { estimateLive: estimateLiveIngress, interpolateSamples: interpolate } = ingressQuote;
|
|
46
|
+
const { estimateLive: estimateLiveEgress } = egressQuote;
|
|
47
|
+
const abortControllerRef = useRef(null);
|
|
48
|
+
const lastEstimateParamsRef = useRef('');
|
|
49
|
+
const inFlightRequestRef = useRef(null);
|
|
50
|
+
/**
|
|
51
|
+
* Fetch both ingress and egress estimates in parallel
|
|
52
|
+
* Matches estimate_order() from Svelte app
|
|
53
|
+
*/
|
|
54
|
+
const estimateOrder = useCallback(async (usdcPrice, sourceAsset, sourcePrice, sourceAmount, destinationAssets, destinationPrices, splits, calculationDirection = 'input-to-output', outputAmounts) => {
|
|
55
|
+
// Validate inputs
|
|
56
|
+
if (!sourceAsset) {
|
|
57
|
+
throw new Error('sourceAsset is required');
|
|
58
|
+
}
|
|
59
|
+
if (!sourceAsset.caip19) {
|
|
60
|
+
throw new Error('sourceAsset.caip19 is required');
|
|
61
|
+
}
|
|
62
|
+
if (!destinationAssets || destinationAssets.length === 0) {
|
|
63
|
+
throw new Error('destinationAssets is required and must not be empty');
|
|
64
|
+
}
|
|
65
|
+
if (destinationAssets.some((asset) => !asset || !asset.caip19)) {
|
|
66
|
+
throw new Error('All destinationAssets must have a valid caip19 property');
|
|
67
|
+
}
|
|
68
|
+
// For reverse calculation, validate output amounts
|
|
69
|
+
if (calculationDirection === 'output-to-input') {
|
|
70
|
+
if (!outputAmounts || outputAmounts.length !== destinationAssets.length) {
|
|
71
|
+
throw new Error('outputAmounts are required for reverse calculation');
|
|
72
|
+
}
|
|
73
|
+
if (outputAmounts.some((amt) => !amt || parseFloat(amt) <= 0)) {
|
|
74
|
+
throw new Error('All outputAmounts must be valid positive numbers for reverse calculation');
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
// Create a key for these parameters to detect duplicate calls
|
|
78
|
+
const sourceAmountStr = typeof sourceAmount === 'string' ? sourceAmount : sourceAmount.toString();
|
|
79
|
+
const estimateKey = `${sourceAsset.caip19}-${sourceAmountStr}-${destinationAssets.map((a) => a.caip19).join(',')}-${splits.join(',')}`;
|
|
80
|
+
// If same parameters and request is in flight, return the existing promise (prevents duplicate requests)
|
|
81
|
+
if (lastEstimateParamsRef.current === estimateKey && inFlightRequestRef.current) {
|
|
82
|
+
return inFlightRequestRef.current;
|
|
83
|
+
}
|
|
84
|
+
// Cancel any previous request (parameters changed)
|
|
85
|
+
if (abortControllerRef.current) {
|
|
86
|
+
abortControllerRef.current.abort();
|
|
87
|
+
}
|
|
88
|
+
lastEstimateParamsRef.current = estimateKey;
|
|
89
|
+
const abortController = new AbortController();
|
|
90
|
+
abortControllerRef.current = abortController;
|
|
91
|
+
setError(null);
|
|
92
|
+
// Create the request promise (declare first to avoid closure issues)
|
|
93
|
+
let requestPromise;
|
|
94
|
+
requestPromise = (async () => {
|
|
95
|
+
try {
|
|
96
|
+
console.log('[OrderEstimates] Step 1: Starting estimateOrder', {
|
|
97
|
+
calculationDirection,
|
|
98
|
+
sourceAsset: sourceAsset.caip19,
|
|
99
|
+
sourceAmount,
|
|
100
|
+
destinationsCount: destinationAssets.length,
|
|
101
|
+
splits,
|
|
102
|
+
hasOutputAmounts: !!outputAmounts,
|
|
103
|
+
});
|
|
104
|
+
// Calculate USD value of source amount
|
|
105
|
+
const sourceAmountNum = typeof sourceAmount === 'string' ? parseFloat(sourceAmount) : sourceAmount;
|
|
106
|
+
const usdValueSrc = BigNumber(sourcePrice).times(sourceAmountNum).toNumber();
|
|
107
|
+
console.log('[OrderEstimates] Step 2: Calculated source USD value', { sourceAmountNum, sourcePrice, usdValueSrc });
|
|
108
|
+
// Parse source asset to get chain ID and token address from CAIP-19
|
|
109
|
+
console.log('[OrderEstimates] Step 3: Parsing source asset CAIP-19');
|
|
110
|
+
const isSourceSolana = isSolanaAsset(sourceAsset.caip19);
|
|
111
|
+
const sourceEvmParsed = !isSourceSolana ? parseEvmCaip19(sourceAsset.caip19) : null;
|
|
112
|
+
const sourceSolanaParsed = isSourceSolana ? parseSolanaCaip19(sourceAsset.caip19) : null;
|
|
113
|
+
const sourceChainId = sourceEvmParsed?.chainId ?? sourceSolanaParsed?.chainId ?? 0;
|
|
114
|
+
const sourceTokenAddress = isEvmNativeToken(sourceAsset.caip19)
|
|
115
|
+
? '0x0000000000000000000000000000000000000000'
|
|
116
|
+
: isSolanaNativeToken(sourceAsset.caip19)
|
|
117
|
+
? '11111111111111111111111111111111'
|
|
118
|
+
: (sourceEvmParsed?.tokenAddress ?? sourceSolanaParsed?.tokenAddress ?? '');
|
|
119
|
+
console.log('[OrderEstimates] Step 3: Source asset parsed', { sourceChainId, sourceTokenAddress, isSourceSolana });
|
|
120
|
+
// For reverse calculation, calculate total output USD value first
|
|
121
|
+
let totalOutputUsd = 0;
|
|
122
|
+
let calculatedInputAmount = undefined;
|
|
123
|
+
if (calculationDirection === 'output-to-input' && outputAmounts) {
|
|
124
|
+
console.log('[OrderEstimates] Step 4: Reverse calculation - calculating total output USD');
|
|
125
|
+
// Calculate total USD value of outputs
|
|
126
|
+
totalOutputUsd = destinationAssets.reduce((sum, destAsset, idx) => {
|
|
127
|
+
const outputAmount = parseFloat(outputAmounts[idx] || '0');
|
|
128
|
+
const destPrice = destinationPrices[idx] || 0;
|
|
129
|
+
return sum + (outputAmount * destPrice);
|
|
130
|
+
}, 0);
|
|
131
|
+
console.log('[OrderEstimates] Step 4: Total output USD calculated', { totalOutputUsd, outputAmounts });
|
|
132
|
+
// For reverse calculation, we'll use EXACT_OUTPUT for egress quotes
|
|
133
|
+
// and let ingress calculate the required input with srcChainTokenInAmount: 'auto'
|
|
134
|
+
}
|
|
135
|
+
else {
|
|
136
|
+
console.log('[OrderEstimates] Step 4: Normal calculation (input-to-output)');
|
|
137
|
+
}
|
|
138
|
+
// Fetch ingress and egress estimates in parallel (matching Svelte behavior)
|
|
139
|
+
// Pass abort signal to estimateLive so both can be cancelled together
|
|
140
|
+
console.log('[OrderEstimates] Step 5: Fetching ingress and egress estimates in parallel', {
|
|
141
|
+
calculationDirection,
|
|
142
|
+
sourceChainId,
|
|
143
|
+
sourceTokenAddress,
|
|
144
|
+
destinationsCount: destinationAssets.length,
|
|
145
|
+
});
|
|
146
|
+
const [ingressResult, egressResults] = await Promise.all([
|
|
147
|
+
// Ingress: source chain -> Avalanche (USDC)
|
|
148
|
+
// For reverse calculation, use EXACT_OUTPUT with calculated deposit USD
|
|
149
|
+
// For normal calculation, use EXACT_INPUT with source amount
|
|
150
|
+
calculationDirection === 'output-to-input' && totalOutputUsd > 0
|
|
151
|
+
? estimateLiveIngress('ingress', sourceAsset.caip19, sourceChainId, sourceTokenAddress, totalOutputUsd / sourcePrice, // Approximate input for quote (will be refined)
|
|
152
|
+
sourcePrice, abortController.signal, undefined, // recipientAddress (not needed for ingress)
|
|
153
|
+
true, // isReverseCalculation
|
|
154
|
+
totalOutputUsd).catch((err) => {
|
|
155
|
+
if (err instanceof Error && err.name === 'AbortError')
|
|
156
|
+
throw err;
|
|
157
|
+
console.warn('Ingress estimate failed (reverse):', err);
|
|
158
|
+
return null;
|
|
159
|
+
})
|
|
160
|
+
: estimateLiveIngress('ingress', sourceAsset.caip19, sourceChainId, sourceTokenAddress, sourceAmountNum, sourcePrice, abortController.signal).catch((err) => {
|
|
161
|
+
if (err instanceof Error && err.name === 'AbortError')
|
|
162
|
+
throw err;
|
|
163
|
+
console.warn('Ingress estimate failed:', err);
|
|
164
|
+
return null;
|
|
165
|
+
}),
|
|
166
|
+
// Egress: Avalanche (USDC) -> each destination chain
|
|
167
|
+
Promise.all(destinationAssets.map((destAsset, idx) => {
|
|
168
|
+
// Parse destination asset to get chain ID and token address from CAIP-19
|
|
169
|
+
const isDestSolana = isSolanaAsset(destAsset.caip19);
|
|
170
|
+
const destEvmParsed = !isDestSolana ? parseEvmCaip19(destAsset.caip19) : null;
|
|
171
|
+
const destSolanaParsed = isDestSolana ? parseSolanaCaip19(destAsset.caip19) : null;
|
|
172
|
+
const destChainId = destEvmParsed?.chainId ?? destSolanaParsed?.chainId ?? 0;
|
|
173
|
+
const destTokenAddress = isEvmNativeToken(destAsset.caip19)
|
|
174
|
+
? '0x0000000000000000000000000000000000000000'
|
|
175
|
+
: isSolanaNativeToken(destAsset.caip19)
|
|
176
|
+
? '11111111111111111111111111111111'
|
|
177
|
+
: (destEvmParsed?.tokenAddress ?? destSolanaParsed?.tokenAddress ?? '');
|
|
178
|
+
// Determine recipient address based on destination chain
|
|
179
|
+
// For Solana destinations, use Solana address; for EVM, use EVM address
|
|
180
|
+
const recipientAddress = isDestSolana ? solAddress : evmAddress;
|
|
181
|
+
// For reverse calculation, use output amount directly with EXACT_OUTPUT
|
|
182
|
+
if (calculationDirection === 'output-to-input' && outputAmounts && outputAmounts[idx]) {
|
|
183
|
+
const outputAmount = parseFloat(outputAmounts[idx]);
|
|
184
|
+
// Use EXACT_OUTPUT for egress: we know the output amount, calculate required USDC
|
|
185
|
+
// Pass the output amount directly (in human-readable format)
|
|
186
|
+
// estimateLive will convert it to micro units and use EXACT_OUTPUT tradeType
|
|
187
|
+
return estimateLiveEgress('egress', destAsset.caip19, destChainId, destTokenAddress, outputAmount, // Output amount in human-readable format (destination token)
|
|
188
|
+
destinationPrices[idx] || 1, // Destination token price for USD conversion
|
|
189
|
+
abortController.signal, recipientAddress, // recipientAddress
|
|
190
|
+
true, // isReverseCalculation
|
|
191
|
+
outputAmount).catch((err) => {
|
|
192
|
+
if (err instanceof Error && err.name === 'AbortError')
|
|
193
|
+
throw err;
|
|
194
|
+
console.warn(`Egress estimate failed (reverse) for ${destAsset.caip19}:`, err);
|
|
195
|
+
return null;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// Normal calculation: calculate from input
|
|
200
|
+
const splitDiff = idx === 0 ? splits[0] : splits[idx] - splits[idx - 1];
|
|
201
|
+
const splitUsdValue = usdValueSrc * splitDiff;
|
|
202
|
+
// For egress quotes, pass USD value directly (matches Svelte behavior)
|
|
203
|
+
// Svelte passes USD value to estimate_live, which then converts to USDC micro units
|
|
204
|
+
// In Svelte: amount = splitDiff * x_usd_value_src (USD value)
|
|
205
|
+
// Then estimate_live calls: asset_get(S_CAIP19_USDC_AVALANCHE)?.humanToUnit(zx_amount)
|
|
206
|
+
// Since USDC price ≈ 1, USD value ≈ USDC amount in human-readable format
|
|
207
|
+
// estimateLive will convert this to micro units using shiftedBy(6) for USDC
|
|
208
|
+
// So we pass the USD value directly, and estimateLive treats it as human-readable USDC
|
|
209
|
+
const usdcAmountHumanReadable = splitUsdValue; // USD value ≈ USDC amount when price = 1
|
|
210
|
+
// Use egress-specific estimateLive which uses EVM address (since egress always starts from Avalanche)
|
|
211
|
+
return estimateLiveEgress('egress', destAsset.caip19, destChainId, // Already a number from parseSolanaCaip19 (7565164) or parseEvmCaip19
|
|
212
|
+
destTokenAddress, usdcAmountHumanReadable, usdcPrice, abortController.signal, recipientAddress).catch((err) => {
|
|
213
|
+
if (err instanceof Error && err.name === 'AbortError')
|
|
214
|
+
throw err;
|
|
215
|
+
console.warn(`Egress estimate failed for ${destAsset.caip19}:`, err);
|
|
216
|
+
return null;
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
})),
|
|
220
|
+
]);
|
|
221
|
+
if (abortController.signal.aborted) {
|
|
222
|
+
console.log('[OrderEstimates] Request aborted');
|
|
223
|
+
throw new Error('Request aborted');
|
|
224
|
+
}
|
|
225
|
+
console.log('[OrderEstimates] Step 6: Estimates received', {
|
|
226
|
+
hasIngressResult: !!ingressResult,
|
|
227
|
+
egressResultsCount: egressResults.length,
|
|
228
|
+
});
|
|
229
|
+
// Handle ingress result
|
|
230
|
+
if (!ingressResult) {
|
|
231
|
+
throw new Error('Ingress estimate failed');
|
|
232
|
+
}
|
|
233
|
+
const ingressEstimate = ingressResult.estimate;
|
|
234
|
+
const ingressQuote = ingressResult.response;
|
|
235
|
+
const ingressGasAmount = ingressResult.gasAmount;
|
|
236
|
+
console.log('[OrderEstimates] Step 7: Processing ingress result', {
|
|
237
|
+
ingressRate: ingressResult.estimate?.samples?.[0]?.retention,
|
|
238
|
+
ingressGasAmount: ingressGasAmount.toString(),
|
|
239
|
+
calculatedInputAmount: ingressResult.calculatedInputAmount,
|
|
240
|
+
});
|
|
241
|
+
// Handle egress results first (needed for both normal and reverse calculation)
|
|
242
|
+
console.log('[OrderEstimates] Step 8: Processing egress results', {
|
|
243
|
+
destinationsCount: destinationAssets.length,
|
|
244
|
+
egressResultsCount: egressResults.length,
|
|
245
|
+
});
|
|
246
|
+
const egressEstimates = [];
|
|
247
|
+
const egressQuotes = [];
|
|
248
|
+
const egressRetentions = [];
|
|
249
|
+
for (let idx = 0; idx < destinationAssets.length; idx++) {
|
|
250
|
+
const egressResult = egressResults[idx];
|
|
251
|
+
if (calculationDirection === 'output-to-input' && outputAmounts && outputAmounts[idx]) {
|
|
252
|
+
// For reverse calculation, use output USD value for interpolation
|
|
253
|
+
const outputAmount = parseFloat(outputAmounts[idx]);
|
|
254
|
+
const destPrice = destinationPrices[idx] || 1;
|
|
255
|
+
const outputUsdValue = outputAmount * destPrice;
|
|
256
|
+
console.log('[OrderEstimates] Step 8: Processing egress result (reverse)', {
|
|
257
|
+
index: idx,
|
|
258
|
+
outputAmount,
|
|
259
|
+
outputUsdValue,
|
|
260
|
+
hasResult: !!egressResult,
|
|
261
|
+
});
|
|
262
|
+
if (egressResult) {
|
|
263
|
+
egressEstimates.push(egressResult.estimate);
|
|
264
|
+
egressQuotes.push(egressResult.response);
|
|
265
|
+
// Interpolate egress retention rate using output USD value
|
|
266
|
+
const egressRetention = interpolate(egressResult.estimate.samples, outputUsdValue);
|
|
267
|
+
egressRetentions.push(egressRetention);
|
|
268
|
+
}
|
|
269
|
+
else {
|
|
270
|
+
// Fallback: use 1.0 retention if egress estimate failed
|
|
271
|
+
egressEstimates.push({
|
|
272
|
+
gasFee: 0,
|
|
273
|
+
samples: [
|
|
274
|
+
{
|
|
275
|
+
baseline: Infinity,
|
|
276
|
+
retention: 1,
|
|
277
|
+
source: 'none',
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
});
|
|
281
|
+
egressQuotes.push({});
|
|
282
|
+
egressRetentions.push(1.0);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
else {
|
|
286
|
+
// Normal calculation: use split USD value
|
|
287
|
+
const splitDiff = idx === 0 ? splits[0] : splits[idx] - splits[idx - 1];
|
|
288
|
+
const splitUsdValue = usdValueSrc * splitDiff;
|
|
289
|
+
console.log('[OrderEstimates] Step 8: Processing egress result (normal)', {
|
|
290
|
+
index: idx,
|
|
291
|
+
splitDiff,
|
|
292
|
+
splitUsdValue,
|
|
293
|
+
hasResult: !!egressResult,
|
|
294
|
+
});
|
|
295
|
+
if (egressResult) {
|
|
296
|
+
egressEstimates.push(egressResult.estimate);
|
|
297
|
+
egressQuotes.push(egressResult.response);
|
|
298
|
+
// Interpolate egress retention rate for this split
|
|
299
|
+
const egressRetention = interpolate(egressResult.estimate.samples, splitUsdValue);
|
|
300
|
+
egressRetentions.push(egressRetention);
|
|
301
|
+
}
|
|
302
|
+
else {
|
|
303
|
+
// Fallback: use 1.0 retention if egress estimate failed
|
|
304
|
+
// This matches Svelte behavior where missing estimates use cached values
|
|
305
|
+
egressEstimates.push({
|
|
306
|
+
gasFee: 0,
|
|
307
|
+
samples: [
|
|
308
|
+
{
|
|
309
|
+
baseline: Infinity,
|
|
310
|
+
retention: 1,
|
|
311
|
+
source: 'none',
|
|
312
|
+
},
|
|
313
|
+
],
|
|
314
|
+
});
|
|
315
|
+
egressQuotes.push({});
|
|
316
|
+
egressRetentions.push(1.0);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
console.log('[OrderEstimates] Step 8: Egress results processed', {
|
|
321
|
+
egressRetentions,
|
|
322
|
+
});
|
|
323
|
+
// For reverse calculation, extract calculated input amount from ingress result
|
|
324
|
+
if (calculationDirection === 'output-to-input' && ingressResult.calculatedInputAmount) {
|
|
325
|
+
console.log('[OrderEstimates] Step 9: Reverse calculation - interpolating retention with calculated input', {
|
|
326
|
+
calculatedInputAmount: ingressResult.calculatedInputAmount,
|
|
327
|
+
});
|
|
328
|
+
calculatedInputAmount = ingressResult.calculatedInputAmount;
|
|
329
|
+
// Use the calculated input amount for retention rate interpolation
|
|
330
|
+
const calculatedInputUsd = calculatedInputAmount * sourcePrice;
|
|
331
|
+
const ingressRetention = interpolate(ingressEstimate.samples, calculatedInputUsd);
|
|
332
|
+
const result = {
|
|
333
|
+
ingressGasAmount,
|
|
334
|
+
ingressRate: ingressRetention,
|
|
335
|
+
egressRates: egressRetentions,
|
|
336
|
+
ingressEstimate,
|
|
337
|
+
egressEstimates,
|
|
338
|
+
ingressQuote,
|
|
339
|
+
egressQuotes,
|
|
340
|
+
calculatedInputAmount,
|
|
341
|
+
};
|
|
342
|
+
console.log('[OrderEstimates] Step 10: estimateOrder completed (reverse)', {
|
|
343
|
+
ingressRate: ingressRetention,
|
|
344
|
+
egressRates: egressRetentions,
|
|
345
|
+
calculatedInputAmount,
|
|
346
|
+
});
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
// Normal calculation: interpolate ingress retention rate
|
|
350
|
+
console.log('[OrderEstimates] Step 9: Normal calculation - interpolating ingress retention', { usdValueSrc });
|
|
351
|
+
const ingressRetention = interpolate(ingressEstimate.samples, usdValueSrc);
|
|
352
|
+
const result = {
|
|
353
|
+
ingressGasAmount,
|
|
354
|
+
ingressRate: ingressRetention,
|
|
355
|
+
egressRates: egressRetentions,
|
|
356
|
+
ingressEstimate,
|
|
357
|
+
egressEstimates,
|
|
358
|
+
ingressQuote,
|
|
359
|
+
egressQuotes,
|
|
360
|
+
};
|
|
361
|
+
console.log('[OrderEstimates] Step 10: estimateOrder completed (normal)', {
|
|
362
|
+
ingressRate: ingressRetention,
|
|
363
|
+
egressRates: egressRetentions,
|
|
364
|
+
});
|
|
365
|
+
return result;
|
|
366
|
+
}
|
|
367
|
+
catch (err) {
|
|
368
|
+
if (err instanceof Error && err.name === 'AbortError') {
|
|
369
|
+
console.log('[OrderEstimates] Request aborted');
|
|
370
|
+
throw err;
|
|
371
|
+
}
|
|
372
|
+
const error = err instanceof Error ? err : new Error('Failed to estimate order');
|
|
373
|
+
console.error('[OrderEstimates] Error: Failed to estimate order', error);
|
|
374
|
+
setError(error);
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
})();
|
|
378
|
+
// Store the in-flight request
|
|
379
|
+
inFlightRequestRef.current = requestPromise;
|
|
380
|
+
// Clear in-flight request when promise completes (success or error)
|
|
381
|
+
requestPromise.finally(() => {
|
|
382
|
+
if (inFlightRequestRef.current === requestPromise) {
|
|
383
|
+
inFlightRequestRef.current = null;
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
return requestPromise;
|
|
387
|
+
}, [estimateLiveIngress, estimateLiveEgress, interpolate, ingressAddress, egressAddress, sourceAsset, solAddress, evmAddress, maxImpactPercent]);
|
|
388
|
+
return {
|
|
389
|
+
estimateOrder,
|
|
390
|
+
isLoading: !!inFlightRequestRef.current,
|
|
391
|
+
error,
|
|
392
|
+
};
|
|
393
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { AssetInfo } from '@silentswap/sdk';
|
|
2
|
+
import type { Destination } from './useSwap.js';
|
|
3
|
+
/**
|
|
4
|
+
* Hook to calculate output asset info and secondary value for swap transaction card
|
|
5
|
+
* Used in InputPlate and OutputSwapTransactionCard
|
|
6
|
+
*/
|
|
7
|
+
export declare function useOutputAssetInfo(destinations: Destination[], tokenOut: AssetInfo | null): {
|
|
8
|
+
outputAssetInfo: AssetInfo | null | undefined;
|
|
9
|
+
outputAmount: string;
|
|
10
|
+
outputAssetPrice: number | undefined;
|
|
11
|
+
outputSecondaryValue: string | null;
|
|
12
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { useMemo } from 'react';
|
|
2
|
+
import { getAssetByCaip19 } from '@silentswap/sdk';
|
|
3
|
+
import { useAssetPrice } from './useAssetPrice.js';
|
|
4
|
+
import { useUsdValue } from './useUsdValue.js';
|
|
5
|
+
import { formatSecondaryValue } from '../utils/formatters.js';
|
|
6
|
+
/**
|
|
7
|
+
* Hook to calculate output asset info and secondary value for swap transaction card
|
|
8
|
+
* Used in InputPlate and OutputSwapTransactionCard
|
|
9
|
+
*/
|
|
10
|
+
export function useOutputAssetInfo(destinations, tokenOut) {
|
|
11
|
+
// Get output asset info for swap transaction card
|
|
12
|
+
const outputAssetInfo = useMemo(() => {
|
|
13
|
+
if (destinations.length > 0 && destinations[0].asset) {
|
|
14
|
+
return getAssetByCaip19(destinations[0].asset);
|
|
15
|
+
}
|
|
16
|
+
return tokenOut;
|
|
17
|
+
}, [destinations, tokenOut]);
|
|
18
|
+
const outputAmount = useMemo(() => {
|
|
19
|
+
if (destinations.length > 0 && destinations[0].amount) {
|
|
20
|
+
return destinations[0].amount;
|
|
21
|
+
}
|
|
22
|
+
return '';
|
|
23
|
+
}, [destinations]);
|
|
24
|
+
// Calculate secondary value for output asset (USD equivalent)
|
|
25
|
+
const outputAssetPrice = useAssetPrice(outputAssetInfo || null, destinations[0]?.priceUsd);
|
|
26
|
+
const { usdValue: outputUsdValue } = useUsdValue(outputAmount, outputAssetPrice);
|
|
27
|
+
const outputSecondaryValue = useMemo(() => {
|
|
28
|
+
if (!outputAssetPrice || !outputAmount)
|
|
29
|
+
return null;
|
|
30
|
+
return formatSecondaryValue(outputAssetInfo || null, outputUsdValue);
|
|
31
|
+
}, [outputAssetPrice, outputAmount, outputUsdValue, outputAssetInfo]);
|
|
32
|
+
return {
|
|
33
|
+
outputAssetInfo,
|
|
34
|
+
outputAmount,
|
|
35
|
+
outputAssetPrice,
|
|
36
|
+
outputSecondaryValue,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import BigNumber from 'bignumber.js';
|
|
2
|
+
import type { AssetInfo } from '@silentswap/sdk';
|
|
3
|
+
export type CachedPrice = {
|
|
4
|
+
priceUsd: number;
|
|
5
|
+
published: number;
|
|
6
|
+
expiration: number;
|
|
7
|
+
source: string;
|
|
8
|
+
};
|
|
9
|
+
export interface usePricesOptions {
|
|
10
|
+
/** Maximum age for cached prices in milliseconds (default: 15000) */
|
|
11
|
+
maxPriceAge?: number;
|
|
12
|
+
/** CoinGecko Pro API key (optional, uses default if not provided) */
|
|
13
|
+
coingeckoApiKey?: string;
|
|
14
|
+
}
|
|
15
|
+
export interface usePricesReturn {
|
|
16
|
+
/** Get USD price for a single asset */
|
|
17
|
+
getPrice: (asset: AssetInfo, maxAge?: number) => Promise<number>;
|
|
18
|
+
/** Get prices for multiple assets (batch fetch) */
|
|
19
|
+
getPrices: (assets: AssetInfo[], maxAge?: number) => Promise<number[]>;
|
|
20
|
+
/** Get USD value for atomic units */
|
|
21
|
+
getUnitValueUsd: (asset: AssetInfo, amount: bigint) => Promise<number>;
|
|
22
|
+
/** Get USD value for human-readable amounts */
|
|
23
|
+
getWholeValueUsd: (asset: AssetInfo, amount: number | string | BigNumber) => Promise<number>;
|
|
24
|
+
/** Check if price is currently loading */
|
|
25
|
+
isPriceLoading: (caip19: string) => boolean;
|
|
26
|
+
/** Get cached price without fetching */
|
|
27
|
+
getCachedPrice: (caip19: string, maxAge?: number) => number | null;
|
|
28
|
+
/** Whether any prices are currently loading */
|
|
29
|
+
loadingPrices: boolean;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* React hook for fetching and caching asset prices from CoinGecko
|
|
33
|
+
*
|
|
34
|
+
* Provides price fetching functionality similar to the Svelte Asset.priceUsd() method
|
|
35
|
+
* with caching and batch fetching support.
|
|
36
|
+
*
|
|
37
|
+
* @param options - Configuration options
|
|
38
|
+
* @returns Object with price fetching methods and state
|
|
39
|
+
*
|
|
40
|
+
* @example
|
|
41
|
+
* ```typescript
|
|
42
|
+
* import { usePrices } from '@silentswap/react';
|
|
43
|
+
* import { getAssetByCaip19 } from '@silentswap/sdk';
|
|
44
|
+
*
|
|
45
|
+
* function MyComponent() {
|
|
46
|
+
* const { getPrice, getPrices } = usePrices();
|
|
47
|
+
*
|
|
48
|
+
* const handleGetPrice = async () => {
|
|
49
|
+
* const asset = getAssetByCaip19('eip155:1/erc20:0xA0b8...');
|
|
50
|
+
* if (asset) {
|
|
51
|
+
* const price = await getPrice(asset);
|
|
52
|
+
* console.log('Price:', price);
|
|
53
|
+
* }
|
|
54
|
+
* };
|
|
55
|
+
*
|
|
56
|
+
* return <button onClick={handleGetPrice}>Get Price</button>;
|
|
57
|
+
* }
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
export declare function usePrices(options?: usePricesOptions): usePricesReturn;
|