@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.
Files changed (86) hide show
  1. package/README.md +48 -0
  2. package/dist/contexts/AssetsContext.d.ts +24 -0
  3. package/dist/contexts/AssetsContext.js +83 -0
  4. package/dist/contexts/BalancesContext.d.ts +28 -0
  5. package/dist/contexts/BalancesContext.js +533 -0
  6. package/dist/contexts/OrdersContext.d.ts +53 -0
  7. package/dist/contexts/OrdersContext.js +240 -0
  8. package/dist/contexts/PricesContext.d.ts +12 -0
  9. package/dist/contexts/PricesContext.js +109 -0
  10. package/dist/contexts/SilentSwapContext.d.ts +58 -0
  11. package/dist/contexts/SilentSwapContext.js +205 -0
  12. package/dist/hooks/silent/orderTrackingWebSocketManager.d.ts +48 -0
  13. package/dist/hooks/silent/orderTrackingWebSocketManager.js +284 -0
  14. package/dist/hooks/silent/solana-transaction.d.ts +60 -0
  15. package/dist/hooks/silent/solana-transaction.js +236 -0
  16. package/dist/hooks/silent/useAuth.d.ts +90 -0
  17. package/dist/hooks/silent/useAuth.js +269 -0
  18. package/dist/hooks/silent/useBridgeExecution.d.ts +39 -0
  19. package/dist/hooks/silent/useBridgeExecution.js +877 -0
  20. package/dist/hooks/silent/useOrderSigning.d.ts +34 -0
  21. package/dist/hooks/silent/useOrderSigning.js +133 -0
  22. package/dist/hooks/silent/useOrderTracking.d.ts +174 -0
  23. package/dist/hooks/silent/useOrderTracking.js +524 -0
  24. package/dist/hooks/silent/useQuoteCalculation.d.ts +50 -0
  25. package/dist/hooks/silent/useQuoteCalculation.js +331 -0
  26. package/dist/hooks/silent/useQuoteFetching.d.ts +18 -0
  27. package/dist/hooks/silent/useQuoteFetching.js +54 -0
  28. package/dist/hooks/silent/useRefund.d.ts +26 -0
  29. package/dist/hooks/silent/useRefund.js +134 -0
  30. package/dist/hooks/silent/useSilentClient.d.ts +16 -0
  31. package/dist/hooks/silent/useSilentClient.js +32 -0
  32. package/dist/hooks/silent/useSilentOrders.d.ts +174 -0
  33. package/dist/hooks/silent/useSilentOrders.js +73 -0
  34. package/dist/hooks/silent/useSilentQuote.d.ts +88 -0
  35. package/dist/hooks/silent/useSilentQuote.js +381 -0
  36. package/dist/hooks/silent/useWallet.d.ts +76 -0
  37. package/dist/hooks/silent/useWallet.js +203 -0
  38. package/dist/hooks/useAssetPrice.d.ts +8 -0
  39. package/dist/hooks/useAssetPrice.js +47 -0
  40. package/dist/hooks/useContacts.d.ts +52 -0
  41. package/dist/hooks/useContacts.js +259 -0
  42. package/dist/hooks/useEgressEstimates.d.ts +32 -0
  43. package/dist/hooks/useEgressEstimates.js +230 -0
  44. package/dist/hooks/useHiddenSwapFees.d.ts +22 -0
  45. package/dist/hooks/useHiddenSwapFees.js +81 -0
  46. package/dist/hooks/useOrderEstimates.d.ts +37 -0
  47. package/dist/hooks/useOrderEstimates.js +393 -0
  48. package/dist/hooks/useOutputAssetInfo.d.ts +12 -0
  49. package/dist/hooks/useOutputAssetInfo.js +38 -0
  50. package/dist/hooks/usePrices.d.ts +60 -0
  51. package/dist/hooks/usePrices.js +188 -0
  52. package/dist/hooks/useQuote.d.ts +73 -0
  53. package/dist/hooks/useQuote.js +507 -0
  54. package/dist/hooks/useResetSwapForm.d.ts +16 -0
  55. package/dist/hooks/useResetSwapForm.js +68 -0
  56. package/dist/hooks/useSlippageUsd.d.ts +11 -0
  57. package/dist/hooks/useSlippageUsd.js +19 -0
  58. package/dist/hooks/useSolanaAdapter.d.ts +15 -0
  59. package/dist/hooks/useSolanaAdapter.js +55 -0
  60. package/dist/hooks/useStatus.d.ts +25 -0
  61. package/dist/hooks/useStatus.js +60 -0
  62. package/dist/hooks/useSwap.d.ts +67 -0
  63. package/dist/hooks/useSwap.js +285 -0
  64. package/dist/hooks/useTransaction.d.ts +119 -0
  65. package/dist/hooks/useTransaction.js +353 -0
  66. package/dist/hooks/useTransactionAddress.d.ts +11 -0
  67. package/dist/hooks/useTransactionAddress.js +26 -0
  68. package/dist/hooks/useUsdValue.d.ts +7 -0
  69. package/dist/hooks/useUsdValue.js +19 -0
  70. package/dist/index.d.ts +54 -0
  71. package/dist/index.js +41 -0
  72. package/dist/stories/SilentSwapOverview.stories.d.ts +10 -0
  73. package/dist/stories/SilentSwapOverview.stories.js +364 -0
  74. package/dist/stories/useAuth.stories.d.ts +6 -0
  75. package/dist/stories/useAuth.stories.js +55 -0
  76. package/dist/stories/useSilentClient.stories.d.ts +9 -0
  77. package/dist/stories/useSilentClient.stories.js +39 -0
  78. package/dist/stories/useSilentOrders.stories.d.ts +1 -0
  79. package/dist/stories/useSilentOrders.stories.js +1 -0
  80. package/dist/stories/useSilentQuote.stories.d.ts +6 -0
  81. package/dist/stories/useSilentQuote.stories.js +267 -0
  82. package/dist/stories/useTransaction.stories.d.ts +6 -0
  83. package/dist/stories/useTransaction.stories.js +121 -0
  84. package/dist/utils/formatters.d.ts +33 -0
  85. package/dist/utils/formatters.js +82 -0
  86. package/package.json +67 -0
@@ -0,0 +1,533 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext, useState, useCallback, useMemo, useEffect } from 'react';
4
+ import { erc20Abi, createPublicClient, http, getAddress } from 'viem';
5
+ import { createSolanaRpc } from '@solana/rpc';
6
+ import { address } from '@solana/kit';
7
+ import { getAssociatedTokenAddress } from '@solana/spl-token';
8
+ import { PublicKey } from '@solana/web3.js';
9
+ import { useAssetsContext } from './AssetsContext.js';
10
+ import { usePrices } from '../hooks/usePrices.js';
11
+ import { isSolanaAsset, parseSolanaCaip19, isSolanaNativeToken, isSplToken, isEvmNativeToken, SB58_CHAIN_ID_SOLANA_MAINNET, A_VIEM_CHAINS, } from '@silentswap/sdk';
12
+ const BalancesContext = createContext(undefined);
13
+ // Custom RPC endpoints from 0xrpc.io (fast, free, private public RPC)
14
+ // Reference: https://0xrpc.io
15
+ const CUSTOM_RPC_ENDPOINTS = {
16
+ 1: 'https://red-old-fog.quiknode.pro/7a0bdad6750af42fb7fb312cabd90f196594e700/', // Ethereum Mainnet
17
+ 10: 'https://cold-white-mountain.optimism.quiknode.pro/dd00719b96a7c719aa6065c476ef928e465c6799/', // Optimism L2 Mainnet
18
+ };
19
+ // Utility: Create viem client with custom RPC endpoints where available
20
+ const createViemClient = (chain) => {
21
+ const customRpc = CUSTOM_RPC_ENDPOINTS[chain.id];
22
+ const chainWithCustomRpc = customRpc
23
+ ? {
24
+ ...chain,
25
+ rpcUrls: {
26
+ ...chain.rpcUrls,
27
+ default: {
28
+ http: [customRpc],
29
+ },
30
+ },
31
+ }
32
+ : chain;
33
+ return createPublicClient({
34
+ chain: chainWithCustomRpc,
35
+ transport: http(), // viem http transport with default timeout
36
+ });
37
+ };
38
+ // Utility: Separate EVM assets into ERC-20 and native
39
+ const separateEvmAssets = (chainAssets) => {
40
+ const a_assets_erc20 = [];
41
+ const a_assets_native = [];
42
+ for (const asset of chainAssets) {
43
+ if (asset.caip19.includes('/erc20:')) {
44
+ a_assets_erc20.push(asset);
45
+ }
46
+ else if (isEvmNativeToken(asset.caip19)) {
47
+ a_assets_native.push(asset);
48
+ }
49
+ }
50
+ return { a_assets_erc20, a_assets_native };
51
+ };
52
+ // Utility: Separate Solana assets into native and SPL tokens
53
+ const separateSolanaAssets = (solanaAssets) => {
54
+ const a_assets_native = [];
55
+ const a_assets_tokens = [];
56
+ for (const asset of solanaAssets) {
57
+ const parsed = parseSolanaCaip19(asset.caip19);
58
+ if (!parsed)
59
+ continue;
60
+ if (parsed.chainId !== SB58_CHAIN_ID_SOLANA_MAINNET)
61
+ continue;
62
+ if (isSolanaNativeToken(asset.caip19)) {
63
+ a_assets_native.push(asset);
64
+ }
65
+ else if (isSplToken(asset.caip19)) {
66
+ a_assets_tokens.push(asset);
67
+ }
68
+ }
69
+ return { a_assets_native, a_assets_tokens };
70
+ };
71
+ // Utility: Fetch ERC-20 balance for a single token (fallback when multicall not available)
72
+ const fetchSingleErc20Balance = async (y_client, s0x_account, k_asset, getUnitValueUsd) => {
73
+ try {
74
+ const tokenAddress = k_asset.caip19.split('/erc20:')[1];
75
+ const xg_balance = await y_client.readContract({
76
+ address: tokenAddress,
77
+ abi: erc20Abi,
78
+ functionName: 'balanceOf',
79
+ args: [s0x_account],
80
+ });
81
+ const g_update = {
82
+ asset: k_asset,
83
+ balance: BigInt(xg_balance),
84
+ usdValue: 0,
85
+ };
86
+ try {
87
+ const x_value_usd = await getUnitValueUsd(k_asset, g_update.balance);
88
+ g_update.usdValue = x_value_usd;
89
+ }
90
+ catch (error) {
91
+ // USD calculation failed, but we still have the balance
92
+ }
93
+ return g_update;
94
+ }
95
+ catch (error) {
96
+ // Individual token fetch failed - return null to skip this token
97
+ if (process.env.NODE_ENV === 'development') {
98
+ console.warn(`Failed to fetch balance for ${k_asset.caip19}:`, error);
99
+ }
100
+ return null;
101
+ }
102
+ };
103
+ // Utility: Fetch ERC-20 balances for a chain using multicall (preferred method)
104
+ const fetchEvmErc20Balances = async (y_client, s0x_account, a_assets_erc20, getUnitValueUsd) => {
105
+ const updatedBalances = {};
106
+ if (a_assets_erc20.length === 0)
107
+ return updatedBalances;
108
+ try {
109
+ const a_balances = await y_client.multicall({
110
+ contracts: a_assets_erc20.map((k_asset) => ({
111
+ address: k_asset.caip19.split('/erc20:')[1],
112
+ abi: erc20Abi,
113
+ functionName: 'balanceOf',
114
+ args: [s0x_account],
115
+ })),
116
+ });
117
+ await Promise.all(a_assets_erc20.slice(0, a_balances.length).map(async (k_asset, i_call) => {
118
+ const g_result = a_balances[i_call];
119
+ const si_caip19 = k_asset.caip19;
120
+ const g_update = {
121
+ asset: k_asset,
122
+ balance: 0n,
123
+ usdValue: 0,
124
+ };
125
+ if (g_result.status === 'success' && g_result.result) {
126
+ const xg_balance = BigInt(g_result.result);
127
+ try {
128
+ const x_value_usd = await getUnitValueUsd(k_asset, xg_balance);
129
+ g_update.balance = xg_balance;
130
+ g_update.usdValue = x_value_usd;
131
+ }
132
+ catch (error) {
133
+ g_update.balance = xg_balance;
134
+ }
135
+ }
136
+ updatedBalances[si_caip19] = g_update;
137
+ }));
138
+ }
139
+ catch (error) {
140
+ // Log error for debugging but don't throw - allow other chains to continue
141
+ if (process.env.NODE_ENV === 'development') {
142
+ console.warn(`Failed to fetch ERC20 balances via multicall:`, error);
143
+ }
144
+ }
145
+ return updatedBalances;
146
+ };
147
+ // Utility: Fetch ERC-20 balances one by one (fallback when multicall not supported)
148
+ const fetchEvmErc20BalancesOneByOne = async (y_client, s0x_account, a_assets_erc20, getUnitValueUsd) => {
149
+ const updatedBalances = {};
150
+ if (a_assets_erc20.length === 0)
151
+ return updatedBalances;
152
+ // Fetch balances one by one with Promise.allSettled to handle individual failures
153
+ const results = await Promise.allSettled(a_assets_erc20.map((k_asset) => fetchSingleErc20Balance(y_client, s0x_account, k_asset, getUnitValueUsd)));
154
+ results.forEach((result, index) => {
155
+ if (result.status === 'fulfilled' && result.value) {
156
+ updatedBalances[a_assets_erc20[index].caip19] = result.value;
157
+ }
158
+ });
159
+ return updatedBalances;
160
+ };
161
+ // Utility: Fetch native EVM balances for a chain
162
+ const fetchEvmNativeBalances = async (y_client, s0x_account, a_assets_native, getUnitValueUsd) => {
163
+ const updatedBalances = {};
164
+ if (a_assets_native.length === 0)
165
+ return updatedBalances;
166
+ try {
167
+ const xg_balance = await y_client.getBalance({ address: s0x_account });
168
+ await Promise.all(a_assets_native.map(async (k_asset) => {
169
+ let x_value_usd = 0;
170
+ try {
171
+ x_value_usd = await getUnitValueUsd(k_asset, xg_balance);
172
+ }
173
+ catch (error) {
174
+ // console.warn(`Failed to calculate USD value for native ${k_asset.caip19}:`, error);
175
+ }
176
+ updatedBalances[k_asset.caip19] = {
177
+ asset: k_asset,
178
+ balance: xg_balance,
179
+ usdValue: x_value_usd,
180
+ };
181
+ }));
182
+ }
183
+ catch (error) {
184
+ // Log error for debugging but don't throw - allow other chains to continue
185
+ if (process.env.NODE_ENV === 'development') {
186
+ console.warn(`Failed to fetch native balance:`, error);
187
+ }
188
+ }
189
+ return updatedBalances;
190
+ };
191
+ // Utility: Fetch balances for a single EVM chain
192
+ const fetchEvmChainBalances = async (g_chain, s0x_account, assets, getUnitValueUsd) => {
193
+ try {
194
+ const y_client = createViemClient(g_chain);
195
+ const si_chain = `${g_chain.id}`;
196
+ const chainAssets = Object.values(assets).filter((asset) => asset.caip19.startsWith(`eip155:${si_chain}/`));
197
+ if (chainAssets.length === 0)
198
+ return { balances: {} };
199
+ const { a_assets_erc20, a_assets_native } = separateEvmAssets(chainAssets);
200
+ const hasMulticall = !!g_chain.contracts?.multicall3;
201
+ // Use multicall if available, otherwise fetch one by one
202
+ const erc20BalancePromise = hasMulticall
203
+ ? fetchEvmErc20Balances(y_client, s0x_account, a_assets_erc20, getUnitValueUsd)
204
+ : fetchEvmErc20BalancesOneByOne(y_client, s0x_account, a_assets_erc20, getUnitValueUsd);
205
+ // Use Promise.allSettled to handle partial failures gracefully
206
+ const [erc20Result, nativeResult] = await Promise.allSettled([
207
+ erc20BalancePromise,
208
+ fetchEvmNativeBalances(y_client, s0x_account, a_assets_native, getUnitValueUsd),
209
+ ]);
210
+ const erc20Balances = erc20Result.status === 'fulfilled' ? erc20Result.value : {};
211
+ const nativeBalances = nativeResult.status === 'fulfilled' ? nativeResult.value : {};
212
+ // Collect errors if any
213
+ const errors = [];
214
+ if (erc20Result.status === 'rejected') {
215
+ errors.push(`ERC20: ${erc20Result.reason?.message || 'Unknown error'}`);
216
+ }
217
+ if (nativeResult.status === 'rejected') {
218
+ errors.push(`Native: ${nativeResult.reason?.message || 'Unknown error'}`);
219
+ }
220
+ if (!hasMulticall && a_assets_erc20.length > 0) {
221
+ // Note that we're using fallback method (not an error, just informational)
222
+ if (process.env.NODE_ENV === 'development') {
223
+ console.info(`Chain ${g_chain.id} (${g_chain.name}) doesn't support multicall, fetching ERC20 balances one by one`);
224
+ }
225
+ }
226
+ return {
227
+ balances: { ...erc20Balances, ...nativeBalances },
228
+ error: errors.length > 0 ? errors.join('; ') : undefined,
229
+ };
230
+ }
231
+ catch (error) {
232
+ const errorMsg = `Failed to fetch balances for chain ${g_chain.id}: ${error instanceof Error ? error.message : String(error)}`;
233
+ if (process.env.NODE_ENV === 'development') {
234
+ console.error(errorMsg, error);
235
+ }
236
+ return { balances: {}, error: errorMsg };
237
+ }
238
+ };
239
+ // Utility: Fetch Solana native balances
240
+ const fetchSolanaNativeBalances = async (rpc, ownerAddress, a_assets_native, getUnitValueUsd) => {
241
+ const updatedBalances = {};
242
+ if (a_assets_native.length === 0)
243
+ return updatedBalances;
244
+ try {
245
+ const response = await rpc.getBalance(address(ownerAddress), { commitment: 'confirmed' }).send();
246
+ const x_balance_native = response.value;
247
+ const xg_balance = BigInt(x_balance_native);
248
+ await Promise.all(a_assets_native.map(async (k_asset) => {
249
+ let x_value_usd = 0;
250
+ try {
251
+ x_value_usd = await getUnitValueUsd(k_asset, xg_balance);
252
+ }
253
+ catch (error) {
254
+ // console.warn(`Failed to calculate USD value for native ${k_asset.caip19}:`, error);
255
+ }
256
+ updatedBalances[k_asset.caip19] = {
257
+ asset: k_asset,
258
+ balance: xg_balance,
259
+ usdValue: x_value_usd,
260
+ };
261
+ }));
262
+ }
263
+ catch (error) {
264
+ // Log error for debugging but don't throw - allow other operations to continue
265
+ if (process.env.NODE_ENV === 'development') {
266
+ console.warn(`Failed to fetch native SOL balance:`, error);
267
+ }
268
+ }
269
+ return updatedBalances;
270
+ };
271
+ // Utility: Fetch Solana SPL token balances
272
+ const fetchSolanaTokenBalances = async (rpc, ownerAddress, a_assets_tokens, getUnitValueUsd) => {
273
+ const updatedBalances = {};
274
+ if (a_assets_tokens.length === 0)
275
+ return updatedBalances;
276
+ const ownerPubkey = new PublicKey(ownerAddress);
277
+ const ataPromises = a_assets_tokens.map(async (k_asset) => {
278
+ try {
279
+ const parsed = parseSolanaCaip19(k_asset.caip19);
280
+ if (!parsed || !parsed.tokenAddress)
281
+ return null;
282
+ const mintPubkey = new PublicKey(parsed.tokenAddress);
283
+ const ataAddress = await getAssociatedTokenAddress(mintPubkey, ownerPubkey);
284
+ return { asset: k_asset, ata: ataAddress };
285
+ }
286
+ catch (error) {
287
+ return null;
288
+ }
289
+ });
290
+ const ataResults = await Promise.all(ataPromises);
291
+ await Promise.all(ataResults.map(async (result) => {
292
+ if (!result)
293
+ return;
294
+ const { asset: k_asset, ata: ataAddress } = result;
295
+ try {
296
+ const response = await rpc.getTokenAccountBalance(address(ataAddress.toBase58()), { commitment: 'confirmed' }).send();
297
+ if (response.value && Number(response.value.amount) > 0) {
298
+ const xg_balance = BigInt(response.value.amount);
299
+ let x_value_usd = 0;
300
+ try {
301
+ x_value_usd = await getUnitValueUsd(k_asset, xg_balance);
302
+ }
303
+ catch (error) {
304
+ // console.warn(`Failed to calculate USD value for token ${k_asset.caip19}:`, error);
305
+ }
306
+ updatedBalances[k_asset.caip19] = {
307
+ asset: k_asset,
308
+ balance: xg_balance,
309
+ usdValue: x_value_usd,
310
+ };
311
+ }
312
+ }
313
+ catch (error) {
314
+ const isAccountNotFound = error?.code === 32602 ||
315
+ (typeof error?.message === 'string' && error.message.toLowerCase().includes('could not find account'));
316
+ if (!isAccountNotFound && process.env.NODE_ENV === 'development') {
317
+ console.warn(`Failed to fetch token balance for ${k_asset.caip19}:`, error);
318
+ }
319
+ }
320
+ }));
321
+ return updatedBalances;
322
+ };
323
+ // Utility: Fetch all Solana balances
324
+ const fetchSolanaBalances = async (solAddress, solanaRpcUrl, assets, getUnitValueUsd) => {
325
+ try {
326
+ const rpc = createSolanaRpc(solanaRpcUrl);
327
+ const solanaAssets = Object.values(assets).filter((asset) => isSolanaAsset(asset.caip19));
328
+ const { a_assets_native, a_assets_tokens } = separateSolanaAssets(solanaAssets);
329
+ // Use Promise.allSettled to handle partial failures gracefully
330
+ const [nativeResult, tokenResult] = await Promise.allSettled([
331
+ fetchSolanaNativeBalances(rpc, solAddress, a_assets_native, getUnitValueUsd),
332
+ fetchSolanaTokenBalances(rpc, solAddress, a_assets_tokens, getUnitValueUsd),
333
+ ]);
334
+ const nativeBalances = nativeResult.status === 'fulfilled' ? nativeResult.value : {};
335
+ const tokenBalances = tokenResult.status === 'fulfilled' ? tokenResult.value : {};
336
+ // Collect errors if any
337
+ const errors = [];
338
+ if (nativeResult.status === 'rejected') {
339
+ errors.push(`Native: ${nativeResult.reason?.message || 'Unknown error'}`);
340
+ }
341
+ if (tokenResult.status === 'rejected') {
342
+ errors.push(`Tokens: ${tokenResult.reason?.message || 'Unknown error'}`);
343
+ }
344
+ return {
345
+ balances: { ...nativeBalances, ...tokenBalances },
346
+ error: errors.length > 0 ? errors.join('; ') : undefined,
347
+ };
348
+ }
349
+ catch (error) {
350
+ const errorMsg = `Failed to fetch Solana balances: ${error instanceof Error ? error.message : String(error)}`;
351
+ if (process.env.NODE_ENV === 'development') {
352
+ console.error(errorMsg, error);
353
+ }
354
+ return { balances: {}, error: errorMsg };
355
+ }
356
+ };
357
+ /**
358
+ * Provider for user balances across all supported chains
359
+ */
360
+ export const BalancesProvider = ({ children, evmAddress, solAddress, solanaRpcUrl }) => {
361
+ const { assets, loading: assetsLoading } = useAssetsContext();
362
+ const { getUnitValueUsd } = usePrices();
363
+ const [balances, setBalances] = useState({});
364
+ const [loading, setLoading] = useState(false);
365
+ const [errors, setErrors] = useState({});
366
+ const updateBalances = useCallback(async () => {
367
+ if (assetsLoading)
368
+ return;
369
+ setLoading(true);
370
+ setErrors({}); // Clear previous errors
371
+ try {
372
+ // Track promises with their chain IDs for better error handling
373
+ const balanceTasks = [];
374
+ // Process EVM balances if EVM address is available
375
+ if (evmAddress) {
376
+ const s0x_account = getAddress(evmAddress);
377
+ A_VIEM_CHAINS.forEach((g_chain) => {
378
+ balanceTasks.push({
379
+ chainId: g_chain.id,
380
+ promise: fetchEvmChainBalances(g_chain, s0x_account, assets, getUnitValueUsd),
381
+ });
382
+ });
383
+ }
384
+ // Process Solana balances if Solana address is available
385
+ if (solAddress && solanaRpcUrl) {
386
+ balanceTasks.push({
387
+ chainId: 'solana',
388
+ promise: fetchSolanaBalances(solAddress, solanaRpcUrl, assets, getUnitValueUsd),
389
+ });
390
+ }
391
+ // Use Promise.allSettled to handle partial failures gracefully
392
+ // This ensures one chain failure doesn't prevent others from updating
393
+ const results = await Promise.allSettled(balanceTasks.map((task) => task.promise));
394
+ const updatedBalances = {};
395
+ const newErrors = {};
396
+ results.forEach((result, index) => {
397
+ const { chainId } = balanceTasks[index];
398
+ if (result.status === 'fulfilled') {
399
+ const { balances: chainBalances, error } = result.value;
400
+ Object.assign(updatedBalances, chainBalances);
401
+ if (error) {
402
+ newErrors[String(chainId)] = error;
403
+ }
404
+ }
405
+ else {
406
+ // If the promise itself was rejected, track the error with chain ID
407
+ const errorMsg = result.reason?.message || 'Unknown error';
408
+ newErrors[String(chainId)] = errorMsg;
409
+ if (process.env.NODE_ENV === 'development') {
410
+ console.error(`Failed to fetch balances for chain ${chainId}:`, result.reason);
411
+ }
412
+ }
413
+ });
414
+ // Merge with existing balances to preserve balances from other chains
415
+ setBalances((prev) => ({ ...prev, ...updatedBalances }));
416
+ setErrors(newErrors);
417
+ }
418
+ catch (error) {
419
+ if (process.env.NODE_ENV === 'development') {
420
+ console.error('Failed to update balances:', error);
421
+ }
422
+ setErrors({ global: error instanceof Error ? error.message : 'Failed to update balances' });
423
+ }
424
+ finally {
425
+ setLoading(false);
426
+ }
427
+ }, [evmAddress, solAddress, solanaRpcUrl, assets, assetsLoading, getUnitValueUsd]);
428
+ // Refetch balances for specific chains only
429
+ const refetchChains = useCallback(async (chainIds) => {
430
+ if (assetsLoading || chainIds.length === 0)
431
+ return;
432
+ setLoading(true);
433
+ try {
434
+ // Track promises with their chain IDs for better error handling
435
+ const balanceTasks = [];
436
+ // Separate EVM and Solana chain IDs
437
+ const evmChainIds = chainIds.filter((id) => typeof id === 'number');
438
+ const shouldRefetchSolana = chainIds.includes('solana');
439
+ // Process EVM balances for specific chains
440
+ if (evmAddress && evmChainIds.length > 0) {
441
+ const s0x_account = getAddress(evmAddress);
442
+ const chainsToFetch = A_VIEM_CHAINS.filter((g_chain) => evmChainIds.includes(g_chain.id));
443
+ chainsToFetch.forEach((g_chain) => {
444
+ balanceTasks.push({
445
+ chainId: g_chain.id,
446
+ promise: fetchEvmChainBalances(g_chain, s0x_account, assets, getUnitValueUsd),
447
+ });
448
+ });
449
+ }
450
+ // Process Solana balances if requested
451
+ if (solAddress && solanaRpcUrl && shouldRefetchSolana) {
452
+ balanceTasks.push({
453
+ chainId: 'solana',
454
+ promise: fetchSolanaBalances(solAddress, solanaRpcUrl, assets, getUnitValueUsd),
455
+ });
456
+ }
457
+ // Use Promise.allSettled to handle partial failures gracefully
458
+ const results = await Promise.allSettled(balanceTasks.map((task) => task.promise));
459
+ const newBalances = {};
460
+ const newErrors = {};
461
+ results.forEach((result, index) => {
462
+ const { chainId } = balanceTasks[index];
463
+ if (result.status === 'fulfilled') {
464
+ const { balances: chainBalances, error } = result.value;
465
+ Object.assign(newBalances, chainBalances);
466
+ if (error) {
467
+ newErrors[String(chainId)] = error;
468
+ }
469
+ }
470
+ else {
471
+ const errorMsg = result.reason?.message || 'Unknown error';
472
+ newErrors[String(chainId)] = errorMsg;
473
+ if (process.env.NODE_ENV === 'development') {
474
+ console.error(`Failed to refetch balances for chain ${chainId}:`, result.reason);
475
+ }
476
+ }
477
+ });
478
+ // Merge with existing balances (only update the chains we refetched)
479
+ setBalances((prev) => ({ ...prev, ...newBalances }));
480
+ // Update errors only for refetched chains
481
+ setErrors((prev) => {
482
+ const updated = { ...prev };
483
+ chainIds.forEach((id) => {
484
+ const key = String(id);
485
+ if (newErrors[key]) {
486
+ updated[key] = newErrors[key];
487
+ }
488
+ else {
489
+ delete updated[key]; // Clear error if refetch succeeded
490
+ }
491
+ });
492
+ return updated;
493
+ });
494
+ }
495
+ catch (error) {
496
+ if (process.env.NODE_ENV === 'development') {
497
+ console.error('Failed to refetch chain balances:', error);
498
+ }
499
+ }
500
+ finally {
501
+ setLoading(false);
502
+ }
503
+ }, [evmAddress, solAddress, solanaRpcUrl, assets, assetsLoading, getUnitValueUsd]);
504
+ // Calculate total USD value
505
+ const totalUsdValue = useMemo(() => {
506
+ return Object.values(balances).reduce((total, balance) => total + balance.usdValue, 0);
507
+ }, [balances]);
508
+ // Auto-update balances when address, solAddress, or assets change
509
+ useEffect(() => {
510
+ if ((evmAddress || solAddress) && !assetsLoading) {
511
+ updateBalances();
512
+ }
513
+ }, [evmAddress, solAddress, solanaRpcUrl, assetsLoading, updateBalances]);
514
+ const value = useMemo(() => ({
515
+ balances,
516
+ loading: loading || assetsLoading,
517
+ totalUsdValue,
518
+ errors,
519
+ refetch: updateBalances,
520
+ refetchChains,
521
+ }), [balances, loading, assetsLoading, totalUsdValue, errors, updateBalances, refetchChains]);
522
+ return _jsx(BalancesContext.Provider, { value: value, children: children });
523
+ };
524
+ /**
525
+ * Hook to access user balances
526
+ */
527
+ export const useBalancesContext = () => {
528
+ const context = useContext(BalancesContext);
529
+ if (context === undefined) {
530
+ throw new Error('useBalancesContext must be used within a BalancesProvider');
531
+ }
532
+ return context;
533
+ };
@@ -0,0 +1,53 @@
1
+ import React from 'react';
2
+ import type { NaiveBase58, RefundEligibility } from '@silentswap/sdk';
3
+ import type { SilentSwapWallet } from '../hooks/silent/useWallet.js';
4
+ export type OrdersContextOrderMetadata = {
5
+ sourceAsset?: {
6
+ caip19: string;
7
+ amount: string;
8
+ };
9
+ sourceSender?: {
10
+ contactId: string;
11
+ };
12
+ };
13
+ export type OrdersContextOrder = {
14
+ orderId: string;
15
+ status: string;
16
+ modified?: number;
17
+ metadata?: OrdersContextOrderMetadata;
18
+ deposit?: {
19
+ amount: string;
20
+ timestamp: number;
21
+ };
22
+ auth?: NaiveBase58;
23
+ };
24
+ export type FacilitatorGroup = () => Promise<{
25
+ viewer: () => Promise<{
26
+ evmSigner: () => Promise<{
27
+ address: string;
28
+ }>;
29
+ }>;
30
+ }>;
31
+ export declare const useWalletFacilitatorGroups: (wallet: SilentSwapWallet | null, setFacilitatorGroups: (groups: FacilitatorGroup[]) => void) => void;
32
+ export type OrdersContextType = {
33
+ orders: OrdersContextOrder[];
34
+ loading: boolean;
35
+ facilitatorGroups: FacilitatorGroup[];
36
+ orderIdToViewingAuth: Record<string, NaiveBase58>;
37
+ orderIdToDefaultPublicKey: Record<string, string>;
38
+ addFacilitatorGroup: (group: FacilitatorGroup) => void;
39
+ setFacilitatorGroups: (groups: FacilitatorGroup[]) => void;
40
+ clearFacilitatorGroups: () => void;
41
+ setOrderDefaultPublicKey: (orderId: string, publicKey: string) => void;
42
+ refreshOrders: () => void;
43
+ getOrderAgeText: (modified?: number) => string;
44
+ getStatusInfo: (status: string, refundEligibility?: RefundEligibility | null) => {
45
+ text: string;
46
+ pulsing: boolean;
47
+ color: string;
48
+ };
49
+ };
50
+ export declare const OrdersProvider: React.FC<{
51
+ children: React.ReactNode;
52
+ }>;
53
+ export declare const useOrdersContext: () => OrdersContextType;