@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,381 @@
|
|
|
1
|
+
import { useCallback, useMemo, useState } from 'react';
|
|
2
|
+
import { hexToBase58, isSolanaAsset, parseEvmCaip19, S_CAIP19_USDC_AVALANCHE, NI_CHAIN_ID_AVALANCHE, getAssetByCaip19, } from '@silentswap/sdk';
|
|
3
|
+
import { getAddress } from 'viem';
|
|
4
|
+
import { BigNumber } from 'bignumber.js';
|
|
5
|
+
import { useQuoteFetching } from './useQuoteFetching.js';
|
|
6
|
+
import { useOrderSigning } from './useOrderSigning.js';
|
|
7
|
+
import { useBridgeExecution } from './useBridgeExecution.js';
|
|
8
|
+
import { useTransaction } from '../useTransaction.js';
|
|
9
|
+
import { useQuoteCalculation } from './useQuoteCalculation.js';
|
|
10
|
+
export function useSilentQuote({ client, address, evmAddress, solAddress, walletClient, connector, wallet, walletLoading = false, onStatus, solanaConnector, solanaConnection, solanaRpcUrl, getPrice, setDestinations, }) {
|
|
11
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
12
|
+
const [currentStep, setCurrentStep] = useState('');
|
|
13
|
+
const [quote, setQuote] = useState(null);
|
|
14
|
+
const [error, setError] = useState(null);
|
|
15
|
+
const [orderId, setOrderId] = useState(null);
|
|
16
|
+
const [viewingAuth, setViewingAuth] = useState(null);
|
|
17
|
+
const [isSwapping, setIsSwapping] = useState(false);
|
|
18
|
+
// Normalize address - only normalize EVM addresses
|
|
19
|
+
// Note: For Solana swaps, we still need an EVM address for facilitator operations
|
|
20
|
+
// The address parameter should be EVM for facilitator operations, but can be Solana for quote requests
|
|
21
|
+
const normalizedAddress = useMemo(() => {
|
|
22
|
+
if (!address)
|
|
23
|
+
return null;
|
|
24
|
+
// Check if it's an EVM address (starts with 0x)
|
|
25
|
+
if (typeof address === 'string' && address.startsWith('0x')) {
|
|
26
|
+
try {
|
|
27
|
+
return getAddress(address);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
// If getAddress fails, it might be invalid, but we'll keep it as-is for now
|
|
31
|
+
return address;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
// For Solana addresses, return null (they're handled separately for transactions)
|
|
35
|
+
// But facilitator operations still require EVM address
|
|
36
|
+
return null;
|
|
37
|
+
}, [address]);
|
|
38
|
+
// Keep raw address for quote requests (can be EVM or Solana)
|
|
39
|
+
const rawAddress = useMemo(() => address || null, [address]);
|
|
40
|
+
// Get environment-based depositor address from client
|
|
41
|
+
const depositorAddress = useMemo(() => client.s0xDepositorAddress, [client]);
|
|
42
|
+
// Initialize specialized hooks
|
|
43
|
+
const { getQuote: getQuoteInternal } = useQuoteFetching(client, rawAddress, setIsLoading, setCurrentStep, setError, onStatus);
|
|
44
|
+
const { signAuthorizations, createOrder } = useOrderSigning(walletClient, connector, client, setCurrentStep, onStatus);
|
|
45
|
+
const { executeSolanaBridge, executeEvmBridge } = useBridgeExecution(walletClient, connector, solanaConnector, solanaConnection, solanaRpcUrl, setCurrentStep, depositorAddress, onStatus);
|
|
46
|
+
const { executeSwapTransaction, approveTokenSpending } = useTransaction({
|
|
47
|
+
address: normalizedAddress || '0x0000000000000000000000000000000000000000',
|
|
48
|
+
walletClient,
|
|
49
|
+
connector,
|
|
50
|
+
solanaConnector,
|
|
51
|
+
solanaConnection,
|
|
52
|
+
solanaRpcUrl,
|
|
53
|
+
setCurrentStep,
|
|
54
|
+
onStatus,
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Get a quote for a swap
|
|
58
|
+
*
|
|
59
|
+
* Wrapper around the quote fetching hook that also stores the quote in state.
|
|
60
|
+
*/
|
|
61
|
+
const getQuote = useCallback(async (request) => {
|
|
62
|
+
const quoteResponse = await getQuoteInternal(request);
|
|
63
|
+
if (quoteResponse) {
|
|
64
|
+
setQuote(quoteResponse);
|
|
65
|
+
}
|
|
66
|
+
return quoteResponse;
|
|
67
|
+
}, [getQuoteInternal]);
|
|
68
|
+
// Use the quote calculation hook
|
|
69
|
+
const { calculateQuote, loadingAmounts, depositAmountUsdc } = useQuoteCalculation({
|
|
70
|
+
address: rawAddress || '0x0000000000000000000000000000000000000000',
|
|
71
|
+
evmAddress,
|
|
72
|
+
wallet,
|
|
73
|
+
depositorAddress,
|
|
74
|
+
getQuote,
|
|
75
|
+
getPrice,
|
|
76
|
+
setDestinations,
|
|
77
|
+
});
|
|
78
|
+
/**
|
|
79
|
+
* Execute the complete swap flow
|
|
80
|
+
*
|
|
81
|
+
* Orchestrates the entire swap process:
|
|
82
|
+
* 1. Fetches quote and calculates provider/allowance/usdcAmount
|
|
83
|
+
* 2. Determines swap type (Solana, EVM bridge, or direct deposit)
|
|
84
|
+
* 3. Creates and signs the order
|
|
85
|
+
* 4. Executes bridge transactions if needed
|
|
86
|
+
* 5. Executes deposit transaction
|
|
87
|
+
* 6. Returns swap result with order ID and transaction hashes
|
|
88
|
+
*/
|
|
89
|
+
const executeSwap = useCallback(async ({ sourceAsset, sourceAmount, destinations, splits, senderContactId, }) => {
|
|
90
|
+
if (!rawAddress) {
|
|
91
|
+
throw new Error('Address required for swap execution');
|
|
92
|
+
}
|
|
93
|
+
if (!walletClient) {
|
|
94
|
+
throw new Error('Wallet client required for swap execution');
|
|
95
|
+
}
|
|
96
|
+
// Check if wallet is being generated - wait a bit and check again
|
|
97
|
+
if (walletLoading) {
|
|
98
|
+
throw new Error('Wallet is being generated. Please wait for wallet generation to complete before executing swap.');
|
|
99
|
+
}
|
|
100
|
+
setIsLoading(true);
|
|
101
|
+
setError(null);
|
|
102
|
+
try {
|
|
103
|
+
setIsSwapping(true);
|
|
104
|
+
setCurrentStep('Fetching quote...');
|
|
105
|
+
onStatus?.('Fetching quote...');
|
|
106
|
+
const quoteResult = await calculateQuote(sourceAsset, sourceAmount, destinations, splits);
|
|
107
|
+
if (!quoteResult) {
|
|
108
|
+
// Ensure loadingAmounts is reset if handleGetQuote failed
|
|
109
|
+
throw new Error('Failed to get quote');
|
|
110
|
+
}
|
|
111
|
+
const { quote: quoteResponse, usdcAmount: effectiveUsdcAmount, facilitatorGroup: passedFacilitatorGroup, bridgeProvider: effectiveProvider, allowanceTarget: effectiveAllowanceTarget, } = quoteResult;
|
|
112
|
+
setCurrentStep('Executing swap...');
|
|
113
|
+
onStatus?.('Executing swap...');
|
|
114
|
+
// Convert source amount to units
|
|
115
|
+
const assetInfo = getAssetByCaip19(sourceAsset);
|
|
116
|
+
if (!assetInfo) {
|
|
117
|
+
throw new Error(`Asset not found: ${sourceAsset}`);
|
|
118
|
+
}
|
|
119
|
+
const sourceAmountBN = BigNumber(sourceAmount);
|
|
120
|
+
console.log('sourceAmountBN', sourceAmountBN);
|
|
121
|
+
const sourceAmountInUnits = sourceAmountBN.shiftedBy(assetInfo.decimals).toFixed(0);
|
|
122
|
+
console.log('sourceAmountInUnits', sourceAmountInUnits, assetInfo);
|
|
123
|
+
// Determine provider for bridge swap
|
|
124
|
+
const isDirectDeposit = sourceAsset === S_CAIP19_USDC_AVALANCHE;
|
|
125
|
+
let providerForSwap = undefined;
|
|
126
|
+
if (effectiveUsdcAmount && !isDirectDeposit) {
|
|
127
|
+
// When usdcAmount is provided and not direct deposit, we're doing a bridge swap and MUST have a provider
|
|
128
|
+
if (effectiveProvider === 'relay' || effectiveProvider === 'debridge') {
|
|
129
|
+
providerForSwap = effectiveProvider;
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
throw new Error(`Provider is required for bridge swap. ` +
|
|
133
|
+
`usdcAmount is set but provider is '${effectiveProvider}'. ` +
|
|
134
|
+
`Please ensure a valid quote has been fetched.`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
const resolvedGroup = passedFacilitatorGroup;
|
|
138
|
+
if (!resolvedGroup) {
|
|
139
|
+
throw new Error('Facilitator group not available. Please ensure wallet is connected and authenticated.');
|
|
140
|
+
}
|
|
141
|
+
// Check swap type
|
|
142
|
+
const isSourceSolana = isSolanaAsset(sourceAsset);
|
|
143
|
+
const isDepositingDirectly = sourceAsset === S_CAIP19_USDC_AVALANCHE;
|
|
144
|
+
const isSourceEvm = !isSourceSolana && !isDepositingDirectly && sourceAsset.startsWith('eip155:');
|
|
145
|
+
// Generate viewing authorization
|
|
146
|
+
const viewer = await resolvedGroup.viewer();
|
|
147
|
+
const evmSigner = await viewer.evmSigner();
|
|
148
|
+
const viewingAuth = hexToBase58(evmSigner.address);
|
|
149
|
+
// CRITICAL: Use user's wallet address for deposit calldata (matches Svelte's s0x_signer)
|
|
150
|
+
// In Svelte: s0x_signer = await contact_from_id(UserState.senderAtNamespace('eip155')).address()
|
|
151
|
+
// This is the user's EVM wallet address, NOT the facilitator group's viewer EVM signer
|
|
152
|
+
// The signer address in quote request (already set above) and deposit calldata must match
|
|
153
|
+
if (!evmAddress) {
|
|
154
|
+
throw new Error('EVM address required for swap execution');
|
|
155
|
+
}
|
|
156
|
+
const evmSignerAddress = getAddress(evmAddress); // Use user's wallet address, not facilitator group's viewer EVM signer
|
|
157
|
+
// Handle Solana swaps
|
|
158
|
+
if (isSourceSolana) {
|
|
159
|
+
const result = await executeSolanaSwap(quoteResponse, sourceAsset, sourceAmountInUnits, effectiveUsdcAmount, senderContactId, solanaConnector, evmSignerAddress, // Use EVM signer address, not normalizedAddress
|
|
160
|
+
viewingAuth, createOrder, executeSolanaBridge, resolvedGroup);
|
|
161
|
+
setOrderId(result.orderId);
|
|
162
|
+
setViewingAuth(result.viewingAuth);
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
// Sign authorizations (for EVM swaps)
|
|
166
|
+
const signedAuths = await signAuthorizations(quoteResponse.authorizations, isDepositingDirectly);
|
|
167
|
+
// Create order with resolved facilitator group
|
|
168
|
+
// Pass the resolved group as override parameter (matches Svelte behavior where
|
|
169
|
+
// sender_resolve_group() is called right before swap)
|
|
170
|
+
const orderResponse = await createOrder(quoteResponse, signedAuths, {
|
|
171
|
+
sourceAsset: {
|
|
172
|
+
caip19: sourceAsset,
|
|
173
|
+
amount: `${BigInt(sourceAmountInUnits)}`,
|
|
174
|
+
},
|
|
175
|
+
sourceSender: {
|
|
176
|
+
contactId: senderContactId,
|
|
177
|
+
},
|
|
178
|
+
}, resolvedGroup);
|
|
179
|
+
// Handle ERC-20 approvals if needed
|
|
180
|
+
if (effectiveAllowanceTarget && !isDepositingDirectly && evmSignerAddress) {
|
|
181
|
+
const sourceAssetMatch = sourceAsset.match(/eip155:(\d+):erc20:(0x[a-fA-F0-9]+)/);
|
|
182
|
+
if (sourceAssetMatch) {
|
|
183
|
+
const sourceChainId = parseInt(sourceAssetMatch[1]);
|
|
184
|
+
const tokenAddress = sourceAssetMatch[2];
|
|
185
|
+
await approveTokenSpending(sourceChainId, tokenAddress, effectiveAllowanceTarget, sourceAmountInUnits, evmSignerAddress);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
// Handle EVM bridge swaps
|
|
189
|
+
if (isSourceEvm) {
|
|
190
|
+
// Use the USDC amount from solveOptimalUsdcAmount (matches Svelte behavior)
|
|
191
|
+
// In Svelte: zg_amount_src_usdc is passed directly to bridge execution (line 1028)
|
|
192
|
+
const result = await executeEvmSwap(sourceAsset, sourceAmountInUnits, effectiveUsdcAmount, // Use amount from solveOptimalUsdcAmount (matches Svelte)
|
|
193
|
+
providerForSwap, orderResponse, evmSignerAddress, // Use EVM signer address, not normalizedAddress
|
|
194
|
+
viewingAuth, executeEvmBridge, senderContactId);
|
|
195
|
+
setOrderId(result.orderId);
|
|
196
|
+
setViewingAuth(result.viewingAuth);
|
|
197
|
+
return result;
|
|
198
|
+
}
|
|
199
|
+
// Execute direct deposit transaction (for direct USDC deposits)
|
|
200
|
+
const depositTx = await executeSwapTransaction(orderResponse);
|
|
201
|
+
const resultOrderId = orderResponse.response.orderId;
|
|
202
|
+
setOrderId(resultOrderId);
|
|
203
|
+
setViewingAuth(viewingAuth);
|
|
204
|
+
return {
|
|
205
|
+
orderId: resultOrderId,
|
|
206
|
+
viewingAuth,
|
|
207
|
+
depositTransaction: depositTx,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
const error = err instanceof Error ? err : new Error('Swap execution failed');
|
|
212
|
+
console.error('Swap execution failed:', {
|
|
213
|
+
sourceAsset,
|
|
214
|
+
sourceAmount,
|
|
215
|
+
destinationsCount: destinations.length,
|
|
216
|
+
error: error.message,
|
|
217
|
+
stack: error.stack,
|
|
218
|
+
});
|
|
219
|
+
setError(error);
|
|
220
|
+
// Ensure all loading states are reset on error
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
finally {
|
|
224
|
+
setIsSwapping(false);
|
|
225
|
+
setIsLoading(false);
|
|
226
|
+
setCurrentStep('');
|
|
227
|
+
}
|
|
228
|
+
}, [
|
|
229
|
+
rawAddress,
|
|
230
|
+
evmAddress,
|
|
231
|
+
walletClient,
|
|
232
|
+
walletLoading,
|
|
233
|
+
solanaConnector,
|
|
234
|
+
signAuthorizations,
|
|
235
|
+
createOrder,
|
|
236
|
+
approveTokenSpending,
|
|
237
|
+
executeSolanaBridge,
|
|
238
|
+
executeEvmBridge,
|
|
239
|
+
executeSwapTransaction,
|
|
240
|
+
calculateQuote,
|
|
241
|
+
onStatus,
|
|
242
|
+
]);
|
|
243
|
+
/**
|
|
244
|
+
* Clear current quote
|
|
245
|
+
*/
|
|
246
|
+
const clearQuote = useCallback(() => {
|
|
247
|
+
setQuote(null);
|
|
248
|
+
setError(null);
|
|
249
|
+
setOrderId(null);
|
|
250
|
+
setViewingAuth(null);
|
|
251
|
+
}, []);
|
|
252
|
+
return {
|
|
253
|
+
// State
|
|
254
|
+
isLoading,
|
|
255
|
+
isSwapping,
|
|
256
|
+
currentStep,
|
|
257
|
+
quote,
|
|
258
|
+
error,
|
|
259
|
+
orderId,
|
|
260
|
+
viewingAuth,
|
|
261
|
+
// Methods
|
|
262
|
+
executeSwap,
|
|
263
|
+
clearQuote,
|
|
264
|
+
depositAmountUsdc,
|
|
265
|
+
loadingAmounts,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Execute Solana swap flow
|
|
270
|
+
*
|
|
271
|
+
* Handles the complete flow for Solana-based swaps:
|
|
272
|
+
* 1. Validates Solana connection
|
|
273
|
+
* 2. Creates order (with empty authorizations for Solana)
|
|
274
|
+
* 3. Executes bridge transaction
|
|
275
|
+
* 4. Returns swap result
|
|
276
|
+
*/
|
|
277
|
+
async function executeSolanaSwap(quoteResponse, sourceAsset, sourceAmount, usdcAmount, senderContactId, solanaConnector, evmSignerAddress, // EVM signer address (matches Svelte's s0x_signer)
|
|
278
|
+
viewingAuth, createOrder, executeSolanaBridge, facilitatorGroup) {
|
|
279
|
+
if (!solanaConnector) {
|
|
280
|
+
throw new Error('Solana connector required for Solana swaps');
|
|
281
|
+
}
|
|
282
|
+
if (!solanaConnector.publicKey) {
|
|
283
|
+
throw new Error('Solana wallet not connected');
|
|
284
|
+
}
|
|
285
|
+
// Get Solana sender address (base58)
|
|
286
|
+
const solanaSenderAddress = solanaConnector.publicKey.toString() || '';
|
|
287
|
+
if (!solanaSenderAddress) {
|
|
288
|
+
throw new Error('Failed to get Solana sender address');
|
|
289
|
+
}
|
|
290
|
+
// Create order with empty authorizations (Solana doesn't use EIP-3009)
|
|
291
|
+
// Pass resolved facilitator group (matches Svelte behavior)
|
|
292
|
+
const orderResponse = await createOrder(quoteResponse, quoteResponse.authorizations.map((auth) => ({
|
|
293
|
+
...auth,
|
|
294
|
+
signature: '0x', // No EIP-3009 deposit for Solana
|
|
295
|
+
})), {
|
|
296
|
+
sourceAsset: {
|
|
297
|
+
caip19: sourceAsset,
|
|
298
|
+
amount: `${BigInt(sourceAmount)}`,
|
|
299
|
+
},
|
|
300
|
+
sourceSender: {
|
|
301
|
+
contactId: senderContactId,
|
|
302
|
+
},
|
|
303
|
+
}, facilitatorGroup);
|
|
304
|
+
// Get deposit parameters from order
|
|
305
|
+
const depositParams = orderResponse.transaction.metadata?.params;
|
|
306
|
+
if (!depositParams) {
|
|
307
|
+
throw new Error('Missing deposit parameters in order response');
|
|
308
|
+
}
|
|
309
|
+
// Execute bridge transaction
|
|
310
|
+
// Use EVM signer address for deposit calldata (matches Svelte's s0x_signer)
|
|
311
|
+
// Pass depositParams so executeSolanaBridge can encode the actual deposit calldata
|
|
312
|
+
// Use usdcAmount from solveOptimalUsdcAmount (matches Svelte: zg_amount_src_usdc is passed directly)
|
|
313
|
+
const bridgeResult = await executeSolanaBridge(sourceAsset, sourceAmount, usdcAmount, // Use amount from solveOptimalUsdcAmount (matches Svelte line 1028)
|
|
314
|
+
solanaSenderAddress, evmSignerAddress, // Use EVM signer address, not normalizedAddress
|
|
315
|
+
depositParams);
|
|
316
|
+
const resultOrderId = orderResponse.response.orderId;
|
|
317
|
+
// Note: setOrderId is not available in this function scope,
|
|
318
|
+
// but the orderId will be set by the calling executeSwap function
|
|
319
|
+
return {
|
|
320
|
+
orderId: resultOrderId,
|
|
321
|
+
viewingAuth,
|
|
322
|
+
depositTransaction: {
|
|
323
|
+
hash: bridgeResult.depositTxHash,
|
|
324
|
+
chainId: NI_CHAIN_ID_AVALANCHE,
|
|
325
|
+
status: 'confirmed',
|
|
326
|
+
confirmations: 1,
|
|
327
|
+
},
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
/**
|
|
331
|
+
* Execute EVM swap flow
|
|
332
|
+
*
|
|
333
|
+
* Handles the complete flow for EVM-based bridge swaps:
|
|
334
|
+
* 1. Parses source asset to get chain ID and token address
|
|
335
|
+
* 2. Gets deposit parameters from order
|
|
336
|
+
* 3. Executes bridge transaction
|
|
337
|
+
* 4. Returns swap result
|
|
338
|
+
*/
|
|
339
|
+
async function executeEvmSwap(sourceAsset, sourceAmount, usdcAmount, provider, orderResponse, evmSignerAddress, // EVM signer address (matches Svelte's s0x_signer)
|
|
340
|
+
viewingAuth, executeEvmBridge, senderContactId) {
|
|
341
|
+
// Parse source asset to get chain ID and token address
|
|
342
|
+
const sourceEvmParsed = parseEvmCaip19(sourceAsset);
|
|
343
|
+
if (!sourceEvmParsed) {
|
|
344
|
+
throw new Error(`Failed to parse EVM source asset: ${sourceAsset}`);
|
|
345
|
+
}
|
|
346
|
+
const sourceChainId = sourceEvmParsed.chainId;
|
|
347
|
+
const sourceTokenAddress = sourceEvmParsed.tokenAddress || '0x0000000000000000000000000000000000000000';
|
|
348
|
+
// Get deposit parameters from order
|
|
349
|
+
const depositParams = orderResponse.transaction.metadata?.params;
|
|
350
|
+
if (!depositParams) {
|
|
351
|
+
throw new Error('Missing deposit parameters in order response');
|
|
352
|
+
}
|
|
353
|
+
// Extract sender address from senderContactId (matches Svelte's s0x_sender)
|
|
354
|
+
// Format: caip10:eip155:*:0x... or caip10:eip155:chainId:0x...
|
|
355
|
+
let evmSenderAddress = evmSignerAddress; // Default to signer address
|
|
356
|
+
const senderMatch = senderContactId.match(/caip10:eip155(?::\*|:\d+):(0x[a-fA-F0-9]+)/i);
|
|
357
|
+
if (senderMatch) {
|
|
358
|
+
evmSenderAddress = getAddress(senderMatch[1]);
|
|
359
|
+
}
|
|
360
|
+
// Execute bridge transaction
|
|
361
|
+
// Use EVM sender address for bridge quotes (matches Svelte's s0x_sender)
|
|
362
|
+
// Use EVM signer address for deposit calldata (matches Svelte's s0x_signer)
|
|
363
|
+
// Use usdcAmount from solveOptimalUsdcAmount (matches Svelte: zg_amount_src_usdc is passed directly at line 1028)
|
|
364
|
+
const bridgeResult = await executeEvmBridge(sourceChainId, sourceTokenAddress, sourceAmount, usdcAmount, // Use amount from solveOptimalUsdcAmount (matches Svelte)
|
|
365
|
+
depositParams, evmSignerAddress, // Use EVM signer address for deposit calldata
|
|
366
|
+
evmSenderAddress, // Use EVM sender address for bridge quotes
|
|
367
|
+
provider);
|
|
368
|
+
const resultOrderId = orderResponse.response.orderId;
|
|
369
|
+
// Note: setOrderId is not available in this function scope,
|
|
370
|
+
// but the orderId will be set by the calling executeSwap function
|
|
371
|
+
return {
|
|
372
|
+
orderId: resultOrderId,
|
|
373
|
+
viewingAuth,
|
|
374
|
+
depositTransaction: {
|
|
375
|
+
hash: bridgeResult.depositTxHash,
|
|
376
|
+
chainId: NI_CHAIN_ID_AVALANCHE,
|
|
377
|
+
status: 'confirmed',
|
|
378
|
+
confirmations: 1,
|
|
379
|
+
},
|
|
380
|
+
};
|
|
381
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { type HdFacilitatorGroup, type AuthResponse, SilentSwapClient } from '@silentswap/sdk';
|
|
2
|
+
import type { WalletClient } from 'viem';
|
|
3
|
+
import type { Connector } from 'wagmi';
|
|
4
|
+
export interface FacilitatorAccount {
|
|
5
|
+
group: () => Promise<HdFacilitatorGroup>;
|
|
6
|
+
nonce: number;
|
|
7
|
+
}
|
|
8
|
+
export interface SilentSwapWallet {
|
|
9
|
+
entropy: Uint8Array;
|
|
10
|
+
accounts: FacilitatorAccount[];
|
|
11
|
+
}
|
|
12
|
+
export interface useWalletOptions {
|
|
13
|
+
/** SilentSwap client */
|
|
14
|
+
client: SilentSwapClient;
|
|
15
|
+
/** The user's EVM address */
|
|
16
|
+
address: `0x${string}`;
|
|
17
|
+
/** Authentication response from SIWE */
|
|
18
|
+
auth?: AuthResponse;
|
|
19
|
+
/** Wallet client for signing operations */
|
|
20
|
+
walletClient?: WalletClient;
|
|
21
|
+
/** Wagmi connector */
|
|
22
|
+
connector?: Connector;
|
|
23
|
+
/** Whether to generate all deposit accounts or just the latest */
|
|
24
|
+
allDeposits?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface useWalletReturn {
|
|
27
|
+
wallet: SilentSwapWallet | null;
|
|
28
|
+
isLoading: boolean;
|
|
29
|
+
error: Error | null;
|
|
30
|
+
generateWallet: () => Promise<void>;
|
|
31
|
+
getCachedWallet: () => SilentSwapWallet | null;
|
|
32
|
+
clearWallet: () => void;
|
|
33
|
+
refreshWallet: () => Promise<void>;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* React hook for managing SilentSwap facilitator wallets and entropy generation
|
|
37
|
+
*
|
|
38
|
+
* This hook handles:
|
|
39
|
+
* - Generating and caching wallet entropy from SIWE authentication
|
|
40
|
+
* - Creating facilitator groups for different deposit nonces
|
|
41
|
+
* - Session storage persistence for wallet data
|
|
42
|
+
* - Automatic wallet refresh when deposits change
|
|
43
|
+
*
|
|
44
|
+
* @param options - Configuration options for wallet generation
|
|
45
|
+
* @returns Object with wallet state and management methods
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```typescript
|
|
49
|
+
* import { useWallet, useAuth } from '@silentswap/react';
|
|
50
|
+
* import { useWalletClient } from 'wagmi';
|
|
51
|
+
*
|
|
52
|
+
* function MyComponent() {
|
|
53
|
+
* const { data: walletClient } = useWalletClient();
|
|
54
|
+
* const { auth } = useAuth();
|
|
55
|
+
* const { wallet, generateWallet, isLoading } = useWallet({
|
|
56
|
+
* address: '0x123...',
|
|
57
|
+
* auth,
|
|
58
|
+
* walletClient: walletClient as any,
|
|
59
|
+
* });
|
|
60
|
+
*
|
|
61
|
+
* useEffect(() => {
|
|
62
|
+
* if (auth && walletClient && !wallet) {
|
|
63
|
+
* generateWallet();
|
|
64
|
+
* }
|
|
65
|
+
* }, [auth, walletClient, wallet, generateWallet]);
|
|
66
|
+
*
|
|
67
|
+
* return (
|
|
68
|
+
* <div>
|
|
69
|
+
* {isLoading && <div>Generating wallet...</div>}
|
|
70
|
+
* {wallet && <div>Wallet ready with {wallet.accounts.length} accounts</div>}
|
|
71
|
+
* </div>
|
|
72
|
+
* );
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export declare function useWallet({ client, address, auth, walletClient, connector, allDeposits, }: useWalletOptions): useWalletReturn;
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { useCallback, useState, useEffect } from 'react';
|
|
2
|
+
import { createHdFacilitatorGroupFromEntropy, createEip712DocForWalletGeneration, queryDepositCount, loadWalletData, saveWalletData, clearWalletData, XT_TTL_SESSION_CACHE, ensureChain, base93ToBytes, bytesToBase93, } from '@silentswap/sdk';
|
|
3
|
+
import { getAddress, hexToBytes } from 'viem';
|
|
4
|
+
/**
|
|
5
|
+
* React hook for managing SilentSwap facilitator wallets and entropy generation
|
|
6
|
+
*
|
|
7
|
+
* This hook handles:
|
|
8
|
+
* - Generating and caching wallet entropy from SIWE authentication
|
|
9
|
+
* - Creating facilitator groups for different deposit nonces
|
|
10
|
+
* - Session storage persistence for wallet data
|
|
11
|
+
* - Automatic wallet refresh when deposits change
|
|
12
|
+
*
|
|
13
|
+
* @param options - Configuration options for wallet generation
|
|
14
|
+
* @returns Object with wallet state and management methods
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* import { useWallet, useAuth } from '@silentswap/react';
|
|
19
|
+
* import { useWalletClient } from 'wagmi';
|
|
20
|
+
*
|
|
21
|
+
* function MyComponent() {
|
|
22
|
+
* const { data: walletClient } = useWalletClient();
|
|
23
|
+
* const { auth } = useAuth();
|
|
24
|
+
* const { wallet, generateWallet, isLoading } = useWallet({
|
|
25
|
+
* address: '0x123...',
|
|
26
|
+
* auth,
|
|
27
|
+
* walletClient: walletClient as any,
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* useEffect(() => {
|
|
31
|
+
* if (auth && walletClient && !wallet) {
|
|
32
|
+
* generateWallet();
|
|
33
|
+
* }
|
|
34
|
+
* }, [auth, walletClient, wallet, generateWallet]);
|
|
35
|
+
*
|
|
36
|
+
* return (
|
|
37
|
+
* <div>
|
|
38
|
+
* {isLoading && <div>Generating wallet...</div>}
|
|
39
|
+
* {wallet && <div>Wallet ready with {wallet.accounts.length} accounts</div>}
|
|
40
|
+
* </div>
|
|
41
|
+
* );
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export function useWallet({ client, address, auth, walletClient, connector, allDeposits = false, }) {
|
|
46
|
+
const [wallet, setWallet] = useState(null);
|
|
47
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
48
|
+
const [error, setError] = useState(null);
|
|
49
|
+
// Normalize address
|
|
50
|
+
const normalizedAddress = address ? getAddress(address) : null;
|
|
51
|
+
/**
|
|
52
|
+
* Get cached wallet from sessionStorage
|
|
53
|
+
*/
|
|
54
|
+
const getCachedWallet = useCallback(() => {
|
|
55
|
+
if (!normalizedAddress)
|
|
56
|
+
return null;
|
|
57
|
+
const cached = loadWalletData(normalizedAddress);
|
|
58
|
+
if (!cached)
|
|
59
|
+
return null;
|
|
60
|
+
try {
|
|
61
|
+
// Decode entropy
|
|
62
|
+
const entropy = base93ToBytes(cached.entropy);
|
|
63
|
+
// Create accounts
|
|
64
|
+
const accounts = cached.accounts.map(({ nonce }) => ({
|
|
65
|
+
group: () => createHdFacilitatorGroupFromEntropy(entropy, nonce),
|
|
66
|
+
nonce,
|
|
67
|
+
}));
|
|
68
|
+
return {
|
|
69
|
+
entropy,
|
|
70
|
+
accounts,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (err) {
|
|
74
|
+
console.warn('Failed to load cached wallet:', err);
|
|
75
|
+
clearWalletData(normalizedAddress);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
}, [normalizedAddress]);
|
|
79
|
+
/**
|
|
80
|
+
* Generate wallet entropy from authentication
|
|
81
|
+
*/
|
|
82
|
+
const generateEntropy = useCallback(async () => {
|
|
83
|
+
if (!auth) {
|
|
84
|
+
throw new Error('Authentication required for wallet generation');
|
|
85
|
+
}
|
|
86
|
+
if (!walletClient) {
|
|
87
|
+
throw new Error('Wallet client required for entropy generation');
|
|
88
|
+
}
|
|
89
|
+
if (!connector) {
|
|
90
|
+
throw new Error('Connector required for chain switching');
|
|
91
|
+
}
|
|
92
|
+
// Ensure we're on Ethereum mainnet for entropy generation (asked core devs about this in tg)
|
|
93
|
+
await ensureChain(1, walletClient, connector);
|
|
94
|
+
// Create EIP-712 document for wallet generation
|
|
95
|
+
const eip712Doc = createEip712DocForWalletGeneration(`eip155:43114:${address}`, // Gateway contract scope (Avalanche)
|
|
96
|
+
auth.secretToken);
|
|
97
|
+
// Fix chainId to be bigint
|
|
98
|
+
if (eip712Doc.domain && typeof eip712Doc.domain.chainId === 'number') {
|
|
99
|
+
eip712Doc.domain.chainId = BigInt(eip712Doc.domain.chainId);
|
|
100
|
+
}
|
|
101
|
+
// Sign the document to generate entropy
|
|
102
|
+
const signature = await walletClient.signTypedData({
|
|
103
|
+
...eip712Doc,
|
|
104
|
+
account: walletClient.account,
|
|
105
|
+
});
|
|
106
|
+
return hexToBytes(signature);
|
|
107
|
+
}, [auth, walletClient, connector, address]);
|
|
108
|
+
/**
|
|
109
|
+
* Generate new wallet with facilitator accounts
|
|
110
|
+
*/
|
|
111
|
+
const generateWallet = useCallback(async () => {
|
|
112
|
+
if (!normalizedAddress) {
|
|
113
|
+
throw new Error('Address required for wallet generation');
|
|
114
|
+
}
|
|
115
|
+
setIsLoading(true);
|
|
116
|
+
setError(null);
|
|
117
|
+
try {
|
|
118
|
+
// Try to get cached wallet first
|
|
119
|
+
const cachedWallet = getCachedWallet();
|
|
120
|
+
if (cachedWallet) {
|
|
121
|
+
setWallet(cachedWallet);
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
// Query the actual deposit count from the blockchain
|
|
125
|
+
const depositCount = await queryDepositCount(normalizedAddress, client.s0xGatewayAddress);
|
|
126
|
+
console.log('[useWallet] depositCount', depositCount);
|
|
127
|
+
// Generate new entropy
|
|
128
|
+
const entropy = await generateEntropy();
|
|
129
|
+
// Create accounts based on deposit count
|
|
130
|
+
const accounts = [];
|
|
131
|
+
// Match the Svelte implementation: if not allDeposits, start from depositCount (not depositCount-1)
|
|
132
|
+
// and use <= comparison to include the last account
|
|
133
|
+
const startNonce = allDeposits ? 0 : Number(depositCount);
|
|
134
|
+
const endNonce = Number(depositCount);
|
|
135
|
+
for (let nonce = startNonce; nonce <= endNonce; nonce++) {
|
|
136
|
+
accounts.push({
|
|
137
|
+
group: () => createHdFacilitatorGroupFromEntropy(entropy, nonce),
|
|
138
|
+
nonce,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// Ensure we always have at least one account (for new users with depositCount = 0)
|
|
142
|
+
// This guarantees wallet.accounts[0] exists
|
|
143
|
+
if (accounts.length === 0) {
|
|
144
|
+
accounts.push({
|
|
145
|
+
group: () => createHdFacilitatorGroupFromEntropy(entropy, 0),
|
|
146
|
+
nonce: 0,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
const newWallet = {
|
|
150
|
+
entropy,
|
|
151
|
+
accounts,
|
|
152
|
+
};
|
|
153
|
+
// Cache wallet data
|
|
154
|
+
saveWalletData(normalizedAddress, {
|
|
155
|
+
entropy: bytesToBase93(entropy),
|
|
156
|
+
accounts: accounts.map(({ nonce }) => ({ nonce })),
|
|
157
|
+
}, XT_TTL_SESSION_CACHE);
|
|
158
|
+
setWallet(newWallet);
|
|
159
|
+
}
|
|
160
|
+
catch (err) {
|
|
161
|
+
const error = err instanceof Error ? err : new Error('Failed to generate wallet');
|
|
162
|
+
setError(error);
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
setIsLoading(false);
|
|
167
|
+
}
|
|
168
|
+
}, [normalizedAddress, getCachedWallet, queryDepositCount, generateEntropy, allDeposits, connector, client.s0xGatewayAddress]);
|
|
169
|
+
/**
|
|
170
|
+
* Clear cached wallet
|
|
171
|
+
*/
|
|
172
|
+
const clearWallet = useCallback(() => {
|
|
173
|
+
if (!normalizedAddress)
|
|
174
|
+
return;
|
|
175
|
+
clearWalletData(normalizedAddress);
|
|
176
|
+
setWallet(null);
|
|
177
|
+
}, [normalizedAddress]);
|
|
178
|
+
/**
|
|
179
|
+
* Refresh wallet (regenerate if needed)
|
|
180
|
+
*/
|
|
181
|
+
const refreshWallet = useCallback(async () => {
|
|
182
|
+
clearWallet();
|
|
183
|
+
await generateWallet();
|
|
184
|
+
}, [clearWallet, generateWallet]);
|
|
185
|
+
// Auto-load cached wallet on mount
|
|
186
|
+
useEffect(() => {
|
|
187
|
+
if (normalizedAddress && !wallet && !isLoading) {
|
|
188
|
+
const cached = getCachedWallet();
|
|
189
|
+
if (cached) {
|
|
190
|
+
setWallet(cached);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}, [normalizedAddress, wallet, isLoading, getCachedWallet]);
|
|
194
|
+
return {
|
|
195
|
+
wallet,
|
|
196
|
+
isLoading,
|
|
197
|
+
error,
|
|
198
|
+
generateWallet,
|
|
199
|
+
getCachedWallet,
|
|
200
|
+
clearWallet,
|
|
201
|
+
refreshWallet,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { AssetInfo } from '@silentswap/sdk';
|
|
2
|
+
/**
|
|
3
|
+
* Hook to fetch and manage asset price from the global context.
|
|
4
|
+
* Handles price fetching with optional override.
|
|
5
|
+
* This hook now uses the PricesContext to prevent duplicate API calls.
|
|
6
|
+
* Automatically updates when prices are refreshed (after 10 seconds).
|
|
7
|
+
*/
|
|
8
|
+
export declare function useAssetPrice(asset: AssetInfo | null, priceOverride?: number): number | undefined;
|