@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,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;
|