@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,240 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useState, useEffect, useCallback, useMemo, useRef } from 'react';
|
|
4
|
+
import { bytes_to_base58, DepositStatus, hex_to_bytes } from '@silentswap/sdk';
|
|
5
|
+
import { useLocalStorage } from 'usehooks-ts';
|
|
6
|
+
import { useSilentSwap } from './SilentSwapContext.js';
|
|
7
|
+
export const useWalletFacilitatorGroups = (wallet, setFacilitatorGroups) => {
|
|
8
|
+
// Automatically set facilitator groups from wallet (matches Svelte behavior)
|
|
9
|
+
// In Svelte: UserState.facilitatorGroups = a_accounts.map(g => g.group) when wallet loads
|
|
10
|
+
// This ensures orders can be fetched even after page refresh, since wallet data is in sessionStorage
|
|
11
|
+
// The wallet entropy is persisted in sessionStorage, and groups are recreated from it
|
|
12
|
+
useEffect(() => {
|
|
13
|
+
if (wallet?.accounts && wallet.accounts.length > 0) {
|
|
14
|
+
// Replace facilitator groups with wallet accounts (matches Svelte assignment behavior)
|
|
15
|
+
// This prevents duplication and ensures groups match the current wallet state
|
|
16
|
+
setFacilitatorGroups(wallet.accounts.map((account) => account.group));
|
|
17
|
+
}
|
|
18
|
+
else if (!wallet) {
|
|
19
|
+
// Clear groups when wallet is null (matches Svelte reset behavior)
|
|
20
|
+
setFacilitatorGroups([]);
|
|
21
|
+
}
|
|
22
|
+
}, [wallet, setFacilitatorGroups]);
|
|
23
|
+
};
|
|
24
|
+
const OrdersContext = createContext(undefined);
|
|
25
|
+
export const OrdersProvider = ({ children }) => {
|
|
26
|
+
const [orders, setOrders] = useState([]);
|
|
27
|
+
const [loading, setLoading] = useState(false);
|
|
28
|
+
const [facilitatorGroups, setFacilitatorGroupsState] = useState([]);
|
|
29
|
+
const [orderIdToViewingAuth, setOrderIdToViewingAuth] = useLocalStorage('orderIdToViewingAuth', {}, { initializeWithValue: true });
|
|
30
|
+
const [orderIdToDefaultPublicKey, setOrderIdToDefaultPublicKey] = useLocalStorage('orderIdToDefaultPublicKey', {}, { initializeWithValue: true });
|
|
31
|
+
// Generate viewing auth token from facilitator group
|
|
32
|
+
const facilitator_group_authorize_order_view = useCallback(async (groupGetter) => {
|
|
33
|
+
try {
|
|
34
|
+
const k_group = await groupGetter();
|
|
35
|
+
const k_viewer = await k_group.viewer();
|
|
36
|
+
const y_signer_viewer = await k_viewer.evmSigner();
|
|
37
|
+
// Reformat its 20-byte address to base58 for compactness
|
|
38
|
+
return bytes_to_base58(hex_to_bytes(y_signer_viewer.address.replace(/^0x/, '')));
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
console.warn('Failed to authorize order view:', error);
|
|
42
|
+
return '';
|
|
43
|
+
}
|
|
44
|
+
}, []);
|
|
45
|
+
// Get baseUrl from environment
|
|
46
|
+
const { config } = useSilentSwap();
|
|
47
|
+
const baseUrl = config.baseUrl;
|
|
48
|
+
// Fetch recent orders from API (matches Svelte implementation)
|
|
49
|
+
const request_recent_orders = useCallback(async (sb58_auth_view) => {
|
|
50
|
+
try {
|
|
51
|
+
// Request current orders from backend using facilitator auth
|
|
52
|
+
const d_res = await fetch(`${baseUrl}/recent?${new URLSearchParams({
|
|
53
|
+
auth: sb58_auth_view,
|
|
54
|
+
})}`);
|
|
55
|
+
// Parse response body
|
|
56
|
+
const sx_res = await d_res.text();
|
|
57
|
+
// Attempt to parse JSON (with safe parsing like Svelte)
|
|
58
|
+
let a_orders;
|
|
59
|
+
try {
|
|
60
|
+
const parsed = JSON.parse(sx_res);
|
|
61
|
+
if (Array.isArray(parsed)) {
|
|
62
|
+
a_orders = parsed.filter((order) => order !== null && typeof order === 'object' && 'orderId' in order);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
throw new Error('Response is not an array');
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
catch (parseError) {
|
|
69
|
+
throw new Error('Unable to retrieve recent orders');
|
|
70
|
+
}
|
|
71
|
+
// Add order ID to viewing auth map (same as Svelte)
|
|
72
|
+
// Use functional update to avoid dependency on orderIdToViewingAuth
|
|
73
|
+
setOrderIdToViewingAuth((prevMapping) => {
|
|
74
|
+
const newMapping = { ...prevMapping };
|
|
75
|
+
for (const g_order of a_orders) {
|
|
76
|
+
newMapping[g_order.orderId] = sb58_auth_view;
|
|
77
|
+
}
|
|
78
|
+
return newMapping;
|
|
79
|
+
});
|
|
80
|
+
return a_orders;
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
console.warn('Failed to fetch recent orders:', error);
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
}, [baseUrl, setOrderIdToViewingAuth]);
|
|
87
|
+
// Store facilitatorGroups in a ref for use in loadOrders
|
|
88
|
+
const facilitatorGroupsRef = useRef(facilitatorGroups);
|
|
89
|
+
useEffect(() => {
|
|
90
|
+
facilitatorGroupsRef.current = facilitatorGroups;
|
|
91
|
+
}, [facilitatorGroups]);
|
|
92
|
+
const loadOrders = useCallback(async () => {
|
|
93
|
+
// Get fresh facilitatorGroups from ref to avoid stale closures
|
|
94
|
+
const currentGroups = facilitatorGroupsRef.current;
|
|
95
|
+
if (currentGroups.length === 0)
|
|
96
|
+
return;
|
|
97
|
+
setLoading(true);
|
|
98
|
+
try {
|
|
99
|
+
const a_all_orders = [];
|
|
100
|
+
// Fetch orders for each facilitator group
|
|
101
|
+
await Promise.all(currentGroups.map(async (f_group) => {
|
|
102
|
+
try {
|
|
103
|
+
const sb58_auth_view = await facilitator_group_authorize_order_view(f_group);
|
|
104
|
+
const a_recents = await request_recent_orders(sb58_auth_view);
|
|
105
|
+
// At least one is complete - convert all others in INIT state to REPLACED
|
|
106
|
+
const b_complete = !!a_recents?.find((g) => 'COMPLETE' === g.status);
|
|
107
|
+
if (b_complete) {
|
|
108
|
+
for (const g of a_recents) {
|
|
109
|
+
if ('INIT' === g.status)
|
|
110
|
+
g.status = 'REPLACED';
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Update orders; augmenting with auth
|
|
114
|
+
a_all_orders.push(...a_recents.map((g) => ({ ...g, auth: sb58_auth_view })));
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
console.warn('Failed to load orders for facilitator group:', error);
|
|
118
|
+
}
|
|
119
|
+
}));
|
|
120
|
+
// Sort by modification time (newest first)
|
|
121
|
+
a_all_orders.sort((g_a, g_b) => (g_b.modified ?? 0) - (g_a.modified ?? 0));
|
|
122
|
+
setOrders(a_all_orders);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
console.error('Failed to load orders:', error);
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
setLoading(false);
|
|
129
|
+
}
|
|
130
|
+
}, [facilitator_group_authorize_order_view, request_recent_orders]);
|
|
131
|
+
// Store loadOrders in a ref to avoid dependency issues
|
|
132
|
+
const loadOrdersRef = useRef(loadOrders);
|
|
133
|
+
useEffect(() => {
|
|
134
|
+
loadOrdersRef.current = loadOrders;
|
|
135
|
+
}, [loadOrders]);
|
|
136
|
+
// Poll for recent orders when facilitator groups change (matching Svelte $effect behavior)
|
|
137
|
+
// In Svelte: $effect(() => { ... }) watches UserState.facilitatorGroups array reference
|
|
138
|
+
useEffect(() => {
|
|
139
|
+
if (facilitatorGroups.length === 0)
|
|
140
|
+
return;
|
|
141
|
+
// Initial load when groups change (matches Svelte $effect behavior)
|
|
142
|
+
loadOrdersRef.current();
|
|
143
|
+
}, [facilitatorGroups]); // Depend on array reference to trigger when groups are replaced
|
|
144
|
+
const addFacilitatorGroup = useCallback((group) => {
|
|
145
|
+
// Add a single facilitator group (for manual additions, e.g., after swap)
|
|
146
|
+
setFacilitatorGroupsState((prev) => {
|
|
147
|
+
// Check if group already exists to prevent duplicates
|
|
148
|
+
// Since groups are functions, we can't easily compare them, so we'll just append
|
|
149
|
+
// The main use case is setFacilitatorGroups which replaces the array
|
|
150
|
+
return [...prev, group];
|
|
151
|
+
});
|
|
152
|
+
}, []);
|
|
153
|
+
const setFacilitatorGroups = useCallback((groups) => {
|
|
154
|
+
// Replace facilitator groups (matches Svelte UserState.facilitatorGroups = ... behavior)
|
|
155
|
+
setFacilitatorGroupsState(groups);
|
|
156
|
+
}, []);
|
|
157
|
+
const setOrderDefaultPublicKey = useCallback((orderId, publicKey) => {
|
|
158
|
+
setOrderIdToDefaultPublicKey((prev) => ({
|
|
159
|
+
...prev,
|
|
160
|
+
[orderId]: publicKey,
|
|
161
|
+
}));
|
|
162
|
+
}, [setOrderIdToDefaultPublicKey]);
|
|
163
|
+
const clearFacilitatorGroups = useCallback(() => {
|
|
164
|
+
setFacilitatorGroupsState([]);
|
|
165
|
+
setOrders([]);
|
|
166
|
+
setOrderIdToViewingAuth({});
|
|
167
|
+
setOrderIdToDefaultPublicKey({});
|
|
168
|
+
}, [setOrderIdToViewingAuth, setOrderIdToDefaultPublicKey]);
|
|
169
|
+
const refreshOrders = useCallback(() => {
|
|
170
|
+
loadOrders();
|
|
171
|
+
}, [loadOrders]);
|
|
172
|
+
// Helper function to get order age text
|
|
173
|
+
const getOrderAgeText = useCallback((modified) => {
|
|
174
|
+
if (!modified)
|
|
175
|
+
return 'Unknown';
|
|
176
|
+
const xt_age = Date.now() - modified;
|
|
177
|
+
const xt_minutes = Math.round(xt_age / 60000);
|
|
178
|
+
const xt_hours = Math.min(999, Math.round(xt_age / 3600000));
|
|
179
|
+
if (xt_age < 90 * 60 * 1000) {
|
|
180
|
+
// Less than 90 minutes
|
|
181
|
+
return xt_minutes <= 1 ? '1 min ago' : `${xt_minutes} min ago`;
|
|
182
|
+
}
|
|
183
|
+
else {
|
|
184
|
+
return xt_hours <= 1 ? '1 hr ago' : `${xt_hours} hr ago`;
|
|
185
|
+
}
|
|
186
|
+
}, []);
|
|
187
|
+
// Helper function to get status display info
|
|
188
|
+
const getStatusInfo = useCallback((status, refundEligibility) => {
|
|
189
|
+
if (!refundEligibility?.refundable && refundEligibility?.code === DepositStatus.ABORTED) {
|
|
190
|
+
return { text: 'Aborted', pulsing: false, color: 'text-gray-400' };
|
|
191
|
+
}
|
|
192
|
+
switch (status) {
|
|
193
|
+
case 'INIT':
|
|
194
|
+
return { text: 'Waiting for deposit', pulsing: true, color: 'text-white' };
|
|
195
|
+
case 'REPLACED':
|
|
196
|
+
return { text: 'Replaced', pulsing: false, color: 'text-gray-400' };
|
|
197
|
+
case 'IN_PROGRESS':
|
|
198
|
+
return { text: 'In progress', pulsing: true, color: 'text-white' };
|
|
199
|
+
case 'COMPLETE':
|
|
200
|
+
return { text: 'Completed', pulsing: false, color: 'text-green-400' };
|
|
201
|
+
default:
|
|
202
|
+
return { text: status, pulsing: false, color: 'text-white' };
|
|
203
|
+
}
|
|
204
|
+
}, []);
|
|
205
|
+
const value = useMemo(() => ({
|
|
206
|
+
orders,
|
|
207
|
+
loading,
|
|
208
|
+
facilitatorGroups,
|
|
209
|
+
orderIdToViewingAuth,
|
|
210
|
+
orderIdToDefaultPublicKey,
|
|
211
|
+
addFacilitatorGroup,
|
|
212
|
+
setFacilitatorGroups,
|
|
213
|
+
clearFacilitatorGroups,
|
|
214
|
+
setOrderDefaultPublicKey,
|
|
215
|
+
refreshOrders,
|
|
216
|
+
getOrderAgeText,
|
|
217
|
+
getStatusInfo,
|
|
218
|
+
}), [
|
|
219
|
+
orders,
|
|
220
|
+
loading,
|
|
221
|
+
facilitatorGroups,
|
|
222
|
+
orderIdToViewingAuth,
|
|
223
|
+
orderIdToDefaultPublicKey,
|
|
224
|
+
addFacilitatorGroup,
|
|
225
|
+
setFacilitatorGroups,
|
|
226
|
+
clearFacilitatorGroups,
|
|
227
|
+
setOrderDefaultPublicKey,
|
|
228
|
+
refreshOrders,
|
|
229
|
+
getOrderAgeText,
|
|
230
|
+
getStatusInfo,
|
|
231
|
+
]);
|
|
232
|
+
return _jsx(OrdersContext.Provider, { value: value, children: children });
|
|
233
|
+
};
|
|
234
|
+
export const useOrdersContext = () => {
|
|
235
|
+
const context = useContext(OrdersContext);
|
|
236
|
+
if (context === undefined) {
|
|
237
|
+
throw new Error('useOrdersContext must be used within an OrdersProvider');
|
|
238
|
+
}
|
|
239
|
+
return context;
|
|
240
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { AssetInfo } from '@silentswap/sdk';
|
|
3
|
+
export type PricesContextType = {
|
|
4
|
+
prices: Record<string, number | undefined>;
|
|
5
|
+
getPrice: (asset: AssetInfo | null) => number | undefined;
|
|
6
|
+
isLoading: (caip19: string) => boolean;
|
|
7
|
+
refetchPrice: (asset: AssetInfo) => Promise<void>;
|
|
8
|
+
};
|
|
9
|
+
export declare const PricesProvider: React.FC<{
|
|
10
|
+
children: React.ReactNode;
|
|
11
|
+
}>;
|
|
12
|
+
export declare const usePricesContext: () => PricesContextType;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useState, useCallback, useMemo } from 'react';
|
|
4
|
+
import { usePrices } from '../hooks/usePrices.js';
|
|
5
|
+
const PRICE_STALE_AGE = 10 * 1000; // 10 seconds in milliseconds
|
|
6
|
+
const PricesContext = createContext(undefined);
|
|
7
|
+
export const PricesProvider = ({ children }) => {
|
|
8
|
+
const { getPrice: fetchPrice } = usePrices();
|
|
9
|
+
const [priceData, setPriceData] = useState({});
|
|
10
|
+
const [loadingAssets, setLoadingAssets] = useState(new Set());
|
|
11
|
+
// Fetch price for an asset and cache it with timestamp
|
|
12
|
+
const fetchAndCachePrice = useCallback(async (asset) => {
|
|
13
|
+
const caip19 = asset.caip19;
|
|
14
|
+
// Skip if already loading
|
|
15
|
+
if (loadingAssets.has(caip19)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
setLoadingAssets((prev) => new Set(prev).add(caip19));
|
|
19
|
+
try {
|
|
20
|
+
const price = await fetchPrice(asset);
|
|
21
|
+
const now = Date.now();
|
|
22
|
+
setPriceData((prev) => ({
|
|
23
|
+
...prev,
|
|
24
|
+
[caip19]: {
|
|
25
|
+
price,
|
|
26
|
+
timestamp: now,
|
|
27
|
+
},
|
|
28
|
+
}));
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
console.warn(`Failed to fetch price for ${caip19}:`, error);
|
|
32
|
+
const now = Date.now();
|
|
33
|
+
setPriceData((prev) => ({
|
|
34
|
+
...prev,
|
|
35
|
+
[caip19]: {
|
|
36
|
+
price: undefined,
|
|
37
|
+
timestamp: now,
|
|
38
|
+
},
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
setLoadingAssets((prev) => {
|
|
43
|
+
const next = new Set(prev);
|
|
44
|
+
next.delete(caip19);
|
|
45
|
+
return next;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}, [fetchPrice, loadingAssets]);
|
|
49
|
+
// Check if price is stale (older than 10 seconds)
|
|
50
|
+
const isPriceStale = useCallback((caip19) => {
|
|
51
|
+
const data = priceData[caip19];
|
|
52
|
+
if (!data)
|
|
53
|
+
return true;
|
|
54
|
+
const age = Date.now() - data.timestamp;
|
|
55
|
+
return age > PRICE_STALE_AGE;
|
|
56
|
+
}, [priceData]);
|
|
57
|
+
// Get price from cache or trigger fetch
|
|
58
|
+
const getPrice = useCallback((asset) => {
|
|
59
|
+
if (!asset)
|
|
60
|
+
return undefined;
|
|
61
|
+
const caip19 = asset.caip19;
|
|
62
|
+
const data = priceData[caip19];
|
|
63
|
+
// If no cached data, trigger fetch
|
|
64
|
+
if (!data) {
|
|
65
|
+
if (!loadingAssets.has(caip19)) {
|
|
66
|
+
fetchAndCachePrice(asset);
|
|
67
|
+
}
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
// If price is stale, trigger refetch in background
|
|
71
|
+
if (isPriceStale(caip19)) {
|
|
72
|
+
if (!loadingAssets.has(caip19)) {
|
|
73
|
+
fetchAndCachePrice(asset);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// Return cached price (even if stale, to avoid flickering)
|
|
77
|
+
return data.price;
|
|
78
|
+
}, [priceData, loadingAssets, fetchAndCachePrice, isPriceStale]);
|
|
79
|
+
// Check if price is loading
|
|
80
|
+
const isLoading = useCallback((caip19) => {
|
|
81
|
+
return loadingAssets.has(caip19);
|
|
82
|
+
}, [loadingAssets]);
|
|
83
|
+
// Refetch price for an asset
|
|
84
|
+
const refetchPrice = useCallback(async (asset) => {
|
|
85
|
+
await fetchAndCachePrice(asset);
|
|
86
|
+
}, [fetchAndCachePrice]);
|
|
87
|
+
// Convert priceData to prices record for backward compatibility
|
|
88
|
+
const prices = useMemo(() => {
|
|
89
|
+
const result = {};
|
|
90
|
+
Object.entries(priceData).forEach(([caip19, data]) => {
|
|
91
|
+
result[caip19] = data.price;
|
|
92
|
+
});
|
|
93
|
+
return result;
|
|
94
|
+
}, [priceData]);
|
|
95
|
+
const value = useMemo(() => ({
|
|
96
|
+
prices,
|
|
97
|
+
getPrice,
|
|
98
|
+
isLoading,
|
|
99
|
+
refetchPrice,
|
|
100
|
+
}), [prices, getPrice, isLoading, refetchPrice]);
|
|
101
|
+
return _jsx(PricesContext.Provider, { value: value, children: children });
|
|
102
|
+
};
|
|
103
|
+
export const usePricesContext = () => {
|
|
104
|
+
const context = useContext(PricesContext);
|
|
105
|
+
if (context === undefined) {
|
|
106
|
+
throw new Error('usePricesContext must be used within a PricesProvider');
|
|
107
|
+
}
|
|
108
|
+
return context;
|
|
109
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type Connector } from 'wagmi';
|
|
3
|
+
import { type WalletClient } from 'viem';
|
|
4
|
+
import { ENVIRONMENT, type SilentSwapClient, type SilentSwapClientConfig, type AuthResponse, type QuoteResponse } from '@silentswap/sdk';
|
|
5
|
+
import { type ExecuteSwapParams, type SwapResult } from '../hooks/silent/useSilentQuote.js';
|
|
6
|
+
import { type OutputStatus } from '../hooks/silent/useOrderTracking.js';
|
|
7
|
+
import { type SolanaWalletConnector, type SolanaConnection } from '../hooks/silent/solana-transaction.js';
|
|
8
|
+
import { type SilentSwapWallet } from '../hooks/silent/useWallet.js';
|
|
9
|
+
export interface SilentSwapContextType {
|
|
10
|
+
client: SilentSwapClient;
|
|
11
|
+
environment: ENVIRONMENT;
|
|
12
|
+
config: SilentSwapClientConfig;
|
|
13
|
+
wallet: SilentSwapWallet | null;
|
|
14
|
+
walletLoading: boolean;
|
|
15
|
+
auth: AuthResponse | null;
|
|
16
|
+
authLoading: boolean;
|
|
17
|
+
executeSwap: (params: ExecuteSwapParams) => Promise<SwapResult | null>;
|
|
18
|
+
swapLoading: boolean;
|
|
19
|
+
isSwapping: boolean;
|
|
20
|
+
currentStep?: string;
|
|
21
|
+
swapError: Error | null;
|
|
22
|
+
quote: QuoteResponse | null;
|
|
23
|
+
orderId: string | null;
|
|
24
|
+
viewingAuth: string | null;
|
|
25
|
+
clearQuote: () => void;
|
|
26
|
+
depositAmountUsdc: number;
|
|
27
|
+
loadingAmounts: boolean;
|
|
28
|
+
orderProgresses: (number | undefined)[];
|
|
29
|
+
orderStatusTexts: string[];
|
|
30
|
+
orderComplete: boolean;
|
|
31
|
+
orderTrackingError: Error | null;
|
|
32
|
+
orderOutputs: OutputStatus[];
|
|
33
|
+
serviceFeeUsd: number;
|
|
34
|
+
bridgeFeeIngressUsd: number;
|
|
35
|
+
bridgeFeeEgressUsd: number;
|
|
36
|
+
slippageUsd: number;
|
|
37
|
+
serviceFeeRate: number;
|
|
38
|
+
overheadUsd: number;
|
|
39
|
+
egressEstimatesLoading: boolean;
|
|
40
|
+
fetchEstimates: (direction?: 'input-to-output' | 'output-to-input') => Promise<void>;
|
|
41
|
+
handleNewSwap: () => void;
|
|
42
|
+
solanaRpcUrl?: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function useSilentSwap(): SilentSwapContextType;
|
|
45
|
+
export declare function SilentSwapProvider({ children, client, evmAddress, solAddress, connector, isConnected, solanaConnector, solanaConnection, environment, baseUrl, solanaRpcUrl, walletClient, }: {
|
|
46
|
+
children: React.ReactNode;
|
|
47
|
+
client: SilentSwapClient;
|
|
48
|
+
evmAddress?: string;
|
|
49
|
+
solAddress?: string;
|
|
50
|
+
connector?: Connector;
|
|
51
|
+
isConnected?: boolean;
|
|
52
|
+
walletClient?: WalletClient;
|
|
53
|
+
solanaConnector?: SolanaWalletConnector;
|
|
54
|
+
solanaConnection?: SolanaConnection;
|
|
55
|
+
environment?: ENVIRONMENT;
|
|
56
|
+
baseUrl?: string;
|
|
57
|
+
solanaRpcUrl?: string;
|
|
58
|
+
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useContext, useEffect, useMemo } from 'react';
|
|
4
|
+
import { useAuth } from '../hooks/silent/useAuth.js';
|
|
5
|
+
import { useWallet } from '../hooks/silent/useWallet.js';
|
|
6
|
+
import { useSilentQuote } from '../hooks/silent/useSilentQuote.js';
|
|
7
|
+
import { useOrderTracking } from '../hooks/silent/useOrderTracking.js';
|
|
8
|
+
import { usePrices } from '../hooks/usePrices.js';
|
|
9
|
+
import { useSwap } from '../hooks/useSwap.js';
|
|
10
|
+
import { useEgressEstimates } from '../hooks/useEgressEstimates.js';
|
|
11
|
+
import { useStatus } from '../hooks/useStatus.js';
|
|
12
|
+
import { useHiddenSwapFees } from '../hooks/useHiddenSwapFees.js';
|
|
13
|
+
import { useSlippageUsd } from '../hooks/useSlippageUsd.js';
|
|
14
|
+
import { useResetSwapForm } from '../hooks/useResetSwapForm.js';
|
|
15
|
+
import { useTransactionAddress } from '../hooks/useTransactionAddress.js';
|
|
16
|
+
import { PricesProvider } from './PricesContext.js';
|
|
17
|
+
import { AssetsProvider } from './AssetsContext.js';
|
|
18
|
+
import { BalancesProvider } from './BalancesContext.js';
|
|
19
|
+
import { OrdersProvider, useOrdersContext, useWalletFacilitatorGroups } from './OrdersContext.js';
|
|
20
|
+
import { ENVIRONMENT, ENVIRONMENT_CONFIGS, } from '@silentswap/sdk';
|
|
21
|
+
const DEFAULT_CONTEXT = {
|
|
22
|
+
client: {}, // Placeholder
|
|
23
|
+
environment: ENVIRONMENT.STAGING,
|
|
24
|
+
config: {
|
|
25
|
+
environment: ENVIRONMENT.STAGING,
|
|
26
|
+
baseUrl: ENVIRONMENT_CONFIGS[ENVIRONMENT.STAGING].baseUrl,
|
|
27
|
+
},
|
|
28
|
+
wallet: null,
|
|
29
|
+
walletLoading: false,
|
|
30
|
+
auth: null,
|
|
31
|
+
authLoading: false,
|
|
32
|
+
executeSwap: async () => null,
|
|
33
|
+
swapLoading: false,
|
|
34
|
+
isSwapping: false,
|
|
35
|
+
currentStep: '',
|
|
36
|
+
swapError: null,
|
|
37
|
+
quote: null,
|
|
38
|
+
orderId: null,
|
|
39
|
+
viewingAuth: null,
|
|
40
|
+
clearQuote: () => { },
|
|
41
|
+
depositAmountUsdc: 0,
|
|
42
|
+
loadingAmounts: false,
|
|
43
|
+
orderProgresses: [],
|
|
44
|
+
orderStatusTexts: [],
|
|
45
|
+
orderComplete: false,
|
|
46
|
+
orderTrackingError: null,
|
|
47
|
+
orderOutputs: [],
|
|
48
|
+
serviceFeeUsd: 0,
|
|
49
|
+
bridgeFeeIngressUsd: 0,
|
|
50
|
+
bridgeFeeEgressUsd: 0,
|
|
51
|
+
slippageUsd: 0,
|
|
52
|
+
serviceFeeRate: 0,
|
|
53
|
+
overheadUsd: 0,
|
|
54
|
+
egressEstimatesLoading: false,
|
|
55
|
+
fetchEstimates: async (_direction) => { },
|
|
56
|
+
handleNewSwap: () => { },
|
|
57
|
+
};
|
|
58
|
+
const SilentSwapContext = createContext(DEFAULT_CONTEXT);
|
|
59
|
+
export function useSilentSwap() {
|
|
60
|
+
const context = useContext(SilentSwapContext);
|
|
61
|
+
if (!context) {
|
|
62
|
+
throw new Error('useSilentSwap must be used within a SilentSwapProvider');
|
|
63
|
+
}
|
|
64
|
+
return context;
|
|
65
|
+
}
|
|
66
|
+
function SilentSwapInnerProvider({ children, client, evmAddress, solAddress, solanaConnector, solanaConnection, environment, config, solanaRpcUrl, connector, isConnected = false, walletClient, }) {
|
|
67
|
+
// Authentication hook - only for EVM
|
|
68
|
+
const { auth, isLoading: authLoading } = useAuth({
|
|
69
|
+
client,
|
|
70
|
+
address: evmAddress,
|
|
71
|
+
walletClient: walletClient,
|
|
72
|
+
autoAuthenticate: true,
|
|
73
|
+
});
|
|
74
|
+
// Wallet management hook
|
|
75
|
+
const { wallet, generateWallet, refreshWallet, isLoading: walletLoading, } = useWallet({
|
|
76
|
+
client,
|
|
77
|
+
address: evmAddress,
|
|
78
|
+
auth: auth || undefined,
|
|
79
|
+
walletClient: walletClient,
|
|
80
|
+
connector: connector,
|
|
81
|
+
allDeposits: true,
|
|
82
|
+
});
|
|
83
|
+
const { setFacilitatorGroups } = useOrdersContext();
|
|
84
|
+
useWalletFacilitatorGroups(wallet, setFacilitatorGroups);
|
|
85
|
+
const tokenIn = useSwap((state) => state.tokenIn);
|
|
86
|
+
const inputAmount = useSwap((state) => state.inputAmount);
|
|
87
|
+
const setInputAmount = useSwap((state) => state.setInputAmount);
|
|
88
|
+
const destinations = useSwap((state) => state.destinations);
|
|
89
|
+
const slippage = useSwap((state) => state.slippage);
|
|
90
|
+
const setDestinations = useSwap((state) => state.setDestinations);
|
|
91
|
+
const updateDestinationAmount = useSwap((state) => state.updateDestinationAmount);
|
|
92
|
+
const splits = useSwap((state) => state.splits);
|
|
93
|
+
const transactionAddress = useTransactionAddress(tokenIn, evmAddress, solAddress);
|
|
94
|
+
const effectiveQuoteAddress = transactionAddress;
|
|
95
|
+
const { getPrice } = usePrices();
|
|
96
|
+
const { serviceFeeRate, overheadUsd } = useStatus();
|
|
97
|
+
const { fetchEstimates, isLoading: egressEstimatesLoading, egressQuotes: egressQuotesFromEstimates, ingressQuote: ingressQuoteFromEstimates, depositAmountUsd: depositAmountUsdFromEstimates, bridgeProviderFromQuote, usdcPrice: usdcPriceFromEstimates, } = useEgressEstimates({
|
|
98
|
+
evmAddress,
|
|
99
|
+
solAddress,
|
|
100
|
+
quoteAddress: effectiveQuoteAddress,
|
|
101
|
+
tokenIn,
|
|
102
|
+
inputAmount,
|
|
103
|
+
destinations,
|
|
104
|
+
splits,
|
|
105
|
+
updateDestinationAmount,
|
|
106
|
+
serviceFeeRate,
|
|
107
|
+
overheadUsd,
|
|
108
|
+
setInputAmount, // Pass setInputAmount for reverse calculation
|
|
109
|
+
});
|
|
110
|
+
useEffect(() => {
|
|
111
|
+
if (auth && walletClient && connector && !wallet && !walletLoading) {
|
|
112
|
+
generateWallet().catch((err) => {
|
|
113
|
+
console.error('Failed to generate wallet:', err);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
}, [auth, walletClient, connector, wallet, walletLoading, generateWallet]);
|
|
117
|
+
const { executeSwap, isLoading: swapLoading, isSwapping, currentStep, error: swapError, quote, orderId, viewingAuth, clearQuote, depositAmountUsdc, loadingAmounts, } = useSilentQuote({
|
|
118
|
+
client,
|
|
119
|
+
address: effectiveQuoteAddress,
|
|
120
|
+
evmAddress: evmAddress,
|
|
121
|
+
solAddress: solAddress,
|
|
122
|
+
setDestinations,
|
|
123
|
+
wallet,
|
|
124
|
+
walletLoading,
|
|
125
|
+
walletClient,
|
|
126
|
+
connector,
|
|
127
|
+
solanaConnector,
|
|
128
|
+
solanaConnection,
|
|
129
|
+
getPrice,
|
|
130
|
+
});
|
|
131
|
+
const { handleNewSwap } = useResetSwapForm({
|
|
132
|
+
clearQuote,
|
|
133
|
+
isConnected,
|
|
134
|
+
wallet,
|
|
135
|
+
setFacilitatorGroups,
|
|
136
|
+
refreshWallet,
|
|
137
|
+
});
|
|
138
|
+
const effectiveDepositAmountUsd = useMemo(() => {
|
|
139
|
+
return depositAmountUsdFromEstimates > 0 ? depositAmountUsdFromEstimates : depositAmountUsdc;
|
|
140
|
+
}, [depositAmountUsdFromEstimates, depositAmountUsdc]);
|
|
141
|
+
const usdcPrice = usdcPriceFromEstimates || 1;
|
|
142
|
+
const { serviceFeeUsd, bridgeFeeIngressUsd, bridgeFeeEgressUsd } = useHiddenSwapFees({
|
|
143
|
+
depositAmountUsdc: effectiveDepositAmountUsd,
|
|
144
|
+
bridgeProvider: bridgeProviderFromQuote,
|
|
145
|
+
serviceFeeRate,
|
|
146
|
+
overheadUsd,
|
|
147
|
+
usdcPrice,
|
|
148
|
+
bridgeFeeRate: 0.001,
|
|
149
|
+
ingressQuote: ingressQuoteFromEstimates,
|
|
150
|
+
egressQuotes: egressQuotesFromEstimates,
|
|
151
|
+
});
|
|
152
|
+
const slippageUsd = useSlippageUsd({
|
|
153
|
+
inputUsdValue: effectiveDepositAmountUsd,
|
|
154
|
+
slippage,
|
|
155
|
+
inputAmount,
|
|
156
|
+
token: tokenIn,
|
|
157
|
+
});
|
|
158
|
+
const { progresses: orderProgresses, statusTexts: orderStatusTexts, isComplete: orderComplete, error: orderTrackingError, outputs: orderOutputs, } = useOrderTracking({
|
|
159
|
+
client,
|
|
160
|
+
orderId: orderId || undefined,
|
|
161
|
+
viewingAuth: viewingAuth || undefined,
|
|
162
|
+
});
|
|
163
|
+
return _jsx(SilentSwapContext.Provider, { value: {
|
|
164
|
+
client,
|
|
165
|
+
environment,
|
|
166
|
+
config,
|
|
167
|
+
wallet,
|
|
168
|
+
walletLoading,
|
|
169
|
+
auth,
|
|
170
|
+
authLoading,
|
|
171
|
+
executeSwap,
|
|
172
|
+
swapLoading,
|
|
173
|
+
isSwapping,
|
|
174
|
+
currentStep,
|
|
175
|
+
swapError,
|
|
176
|
+
quote,
|
|
177
|
+
orderId,
|
|
178
|
+
viewingAuth,
|
|
179
|
+
clearQuote,
|
|
180
|
+
depositAmountUsdc,
|
|
181
|
+
loadingAmounts,
|
|
182
|
+
orderProgresses,
|
|
183
|
+
orderStatusTexts,
|
|
184
|
+
orderComplete,
|
|
185
|
+
orderTrackingError,
|
|
186
|
+
orderOutputs,
|
|
187
|
+
serviceFeeUsd,
|
|
188
|
+
bridgeFeeIngressUsd,
|
|
189
|
+
bridgeFeeEgressUsd,
|
|
190
|
+
slippageUsd,
|
|
191
|
+
serviceFeeRate,
|
|
192
|
+
overheadUsd,
|
|
193
|
+
egressEstimatesLoading,
|
|
194
|
+
fetchEstimates,
|
|
195
|
+
handleNewSwap,
|
|
196
|
+
solanaRpcUrl,
|
|
197
|
+
}, children: children });
|
|
198
|
+
}
|
|
199
|
+
export function SilentSwapProvider({ children, client, evmAddress, solAddress, connector, isConnected, solanaConnector, solanaConnection, environment = ENVIRONMENT.STAGING, baseUrl, solanaRpcUrl, walletClient, }) {
|
|
200
|
+
const config = useMemo(() => ({
|
|
201
|
+
environment,
|
|
202
|
+
baseUrl: baseUrl ?? ENVIRONMENT_CONFIGS[environment].baseUrl,
|
|
203
|
+
}), [environment, baseUrl]);
|
|
204
|
+
return (_jsx(AssetsProvider, { children: _jsx(PricesProvider, { children: _jsx(BalancesProvider, { evmAddress: evmAddress, solAddress: solAddress, solanaRpcUrl: solanaRpcUrl, children: _jsx(OrdersProvider, { children: _jsx(SilentSwapInnerProvider, { client: client, connector: connector, isConnected: isConnected, evmAddress: evmAddress, solAddress: solAddress, solanaConnector: solanaConnector, solanaConnection: solanaConnection, environment: environment, config: config, solanaRpcUrl: solanaRpcUrl, walletClient: walletClient, children: children }) }) }) }) }));
|
|
205
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type { SilentSwapClient } from '@silentswap/sdk';
|
|
2
|
+
import type { OrderStatus, StatusUpdate } from './useOrderTracking.js';
|
|
3
|
+
/**
|
|
4
|
+
* Shared WebSocket connection manager for order tracking
|
|
5
|
+
* Maintains one connection per client/baseUrl and handles multiple order subscriptions
|
|
6
|
+
*/
|
|
7
|
+
declare class OrderTrackingWebSocketManager {
|
|
8
|
+
private connections;
|
|
9
|
+
/**
|
|
10
|
+
* Get connection key from client
|
|
11
|
+
*/
|
|
12
|
+
private getConnectionKey;
|
|
13
|
+
/**
|
|
14
|
+
* Get WebSocket URL from client
|
|
15
|
+
*/
|
|
16
|
+
private getWebSocketUrl;
|
|
17
|
+
/**
|
|
18
|
+
* Get or create connection state
|
|
19
|
+
*/
|
|
20
|
+
private getConnectionState;
|
|
21
|
+
/**
|
|
22
|
+
* Connect WebSocket if not already connected
|
|
23
|
+
*/
|
|
24
|
+
private connectWebSocket;
|
|
25
|
+
/**
|
|
26
|
+
* Schedule reconnection with exponential backoff
|
|
27
|
+
*/
|
|
28
|
+
private scheduleReconnect;
|
|
29
|
+
/**
|
|
30
|
+
* Subscribe to an order
|
|
31
|
+
*/
|
|
32
|
+
private subscribeOrder;
|
|
33
|
+
/**
|
|
34
|
+
* Subscribe to order tracking
|
|
35
|
+
*/
|
|
36
|
+
subscribe(client: SilentSwapClient | undefined, orderId: string, auth: string, handlers: {
|
|
37
|
+
onStatusUpdate: (status: OrderStatus) => void;
|
|
38
|
+
onStatusChange: (update: StatusUpdate) => void;
|
|
39
|
+
onError: (error: Error) => void;
|
|
40
|
+
onComplete: () => void;
|
|
41
|
+
}): () => void;
|
|
42
|
+
/**
|
|
43
|
+
* Get connection status
|
|
44
|
+
*/
|
|
45
|
+
isConnected(client?: SilentSwapClient): boolean;
|
|
46
|
+
}
|
|
47
|
+
export declare const orderTrackingWebSocketManager: OrderTrackingWebSocketManager;
|
|
48
|
+
export {};
|