@runesx/api-client 0.0.1

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.
@@ -0,0 +1,447 @@
1
+ // src/utils/swapUtils.mjs
2
+ import { BigNumber } from 'bignumber.js';
3
+
4
+ export const validatePositiveNumber = (value, fieldName) => {
5
+ const num = new BigNumber(value);
6
+ if (num.isNaN() || num.lte(0)) {
7
+ throw new Error(`Invalid ${fieldName}: ${value} must be a positive number`);
8
+ }
9
+ return num;
10
+ };
11
+
12
+ // Get RUNES price in USD using RUNES/USDC pool
13
+ export const getRunesPriceUSD = async (pools) => {
14
+ try {
15
+ const runesUsdcPool = pools.find((p) => (p.coinA.ticker === 'RUNES' && p.coinB.ticker === 'USDC'));
16
+
17
+ if (!runesUsdcPool) {
18
+ console.warn('RUNES/USDC pool not found, using fallback price of $0.01');
19
+ return '0.01';
20
+ }
21
+
22
+ const reserveA = new BigNumber(runesUsdcPool.reserveA).shiftedBy(-runesUsdcPool.coinA.dp);
23
+ const reserveB = new BigNumber(runesUsdcPool.reserveB).shiftedBy(-runesUsdcPool.coinB.dp);
24
+
25
+ if (reserveA.isZero() || reserveB.isZero()) {
26
+ console.warn('RUNES/USDC pool has zero reserves, using fallback price of $0.01');
27
+ return '0.01';
28
+ }
29
+
30
+ let runesPriceUSD;
31
+ if (runesUsdcPool.coinA.ticker === 'RUNES' && runesUsdcPool.coinB.ticker === 'USDC') {
32
+ runesPriceUSD = reserveB.div(reserveA);
33
+ } else {
34
+ runesPriceUSD = reserveA.div(reserveB);
35
+ }
36
+
37
+ if (runesPriceUSD.isNaN() || runesPriceUSD.lte(0)) {
38
+ console.warn('Invalid RUNES/USDC price calculated, using fallback price of $0.01');
39
+ return '0.01';
40
+ }
41
+
42
+ return runesPriceUSD.toString();
43
+ } catch (error) {
44
+ console.error('Error calculating RUNES/USD price:', error);
45
+ return '0.01';
46
+ }
47
+ };
48
+
49
+ // Get token price in RUNES
50
+ export const getTokenPriceInRunes = (token, pools) => {
51
+ if (token.ticker === 'RUNES') {return '1';}
52
+ const pool = pools.find(
53
+ (p) =>
54
+ (p.coinA.ticker === 'RUNES' && p.coinB.ticker === token.ticker) ||
55
+ (p.coinB.ticker === 'RUNES' && p.coinA.ticker === token.ticker)
56
+ );
57
+ if (!pool || new BigNumber(pool.reserveA).isZero() || new BigNumber(pool.reserveB).isZero()) {
58
+ return '0';
59
+ }
60
+ const isRunesA = pool.coinA.ticker === 'RUNES';
61
+
62
+ const reserveA = new BigNumber(pool.reserveA).shiftedBy(-pool.coinA.dp);
63
+ const reserveB = new BigNumber(pool.reserveB).shiftedBy(-pool.coinB.dp);
64
+
65
+ const priceInRunes = isRunesA
66
+ ? reserveA.div(reserveB).toString() // RUNES/TOKEN: price = reserveB (TOKEN) / reserveA (RUNES)
67
+ : reserveB.div(reserveA).toString(); // TOKEN/RUNES: price = reserveA (TOKEN) / reserveB (RUNES)
68
+
69
+ return priceInRunes;
70
+ };
71
+
72
+ // DFS-based pathfinding
73
+ const findAllPathsDFS = (startCoin, endCoin, pools, maxHops = 6) => {
74
+ const paths = [];
75
+ const visited = new Set();
76
+
77
+ function dfs(currentCoin, currentPath, hops) {
78
+ if (hops > maxHops) {return;}
79
+ if (currentCoin.ticker === endCoin.ticker) {
80
+ paths.push([...currentPath]);
81
+ return;
82
+ }
83
+
84
+ for (const pool of pools) {
85
+ if (!pool.runesCompliant) {continue;}
86
+ const isCoinA = pool.coinA.ticker === currentCoin.ticker;
87
+ const isCoinB = pool.coinB.ticker === currentCoin.ticker;
88
+ if (!isCoinA && !isCoinB) {continue;}
89
+
90
+ const nextCoin = isCoinA ? pool.coinB : pool.coinA;
91
+ const poolKey = pool.id;
92
+
93
+ if (visited.has(poolKey)) {continue;}
94
+ visited.add(poolKey);
95
+
96
+ currentPath.push({
97
+ from: currentCoin.ticker,
98
+ to: nextCoin.ticker,
99
+ });
100
+
101
+ dfs(nextCoin, currentPath, hops + 1);
102
+
103
+ currentPath.pop();
104
+ visited.delete(poolKey);
105
+ }
106
+ }
107
+
108
+ dfs(startCoin, [], 0);
109
+ return paths;
110
+ };
111
+
112
+ // BFS-based pathfinding
113
+ const findAllPathsBFS = (startCoin, endCoin, pools, maxHops = 6, maxPaths = 20) => {
114
+ const paths = [];
115
+ const queue = [{ coin: startCoin, path: [], hops: 0 }];
116
+ const visited = new Set();
117
+
118
+ // Build pool map for faster lookup
119
+ const poolMap = new Map();
120
+ pools.forEach((pool) => {
121
+ if (!pool.runesCompliant || new BigNumber(pool.reserveA).isZero() || new BigNumber(pool.reserveB).isZero()) {return;}
122
+ const keyA = pool.coinA.ticker;
123
+ const keyB = pool.coinB.ticker;
124
+ if (!poolMap.has(keyA)) {poolMap.set(keyA, []);}
125
+ if (!poolMap.has(keyB)) {poolMap.set(keyB, []);}
126
+ poolMap.get(keyA).push({ pool, nextCoin: pool.coinB });
127
+ poolMap.get(keyB).push({ pool, nextCoin: pool.coinA });
128
+ });
129
+
130
+ while (queue.length && paths.length < maxPaths) {
131
+ const { coin, path, hops } = queue.shift();
132
+ if (hops > maxHops) {continue;}
133
+ if (coin.ticker === endCoin.ticker) {
134
+ paths.push(path);
135
+ continue;
136
+ }
137
+
138
+ const connectedPools = poolMap.get(coin.ticker) || [];
139
+ for (const { pool, nextCoin } of connectedPools) {
140
+ const poolKey = `${coin.ticker}-${pool.id}`;
141
+ if (visited.has(poolKey)) {continue;}
142
+ visited.add(poolKey);
143
+
144
+ queue.push({
145
+ coin: nextCoin,
146
+ path: [...path, { from: coin.ticker, to: nextCoin.ticker }],
147
+ hops: hops + 1,
148
+ });
149
+ }
150
+ }
151
+
152
+ return paths;
153
+ };
154
+
155
+ // Find all possible swap paths (select DFS or BFS)
156
+ export const findAllPaths = (startCoin, endCoin, pools, maxHops = 6, algorithm = 'dfs') => {
157
+ if (algorithm === 'bfs') {
158
+ return findAllPathsBFS(startCoin, endCoin, pools, maxHops, 20); // Limit to 20 paths for BFS
159
+ }
160
+ return findAllPathsDFS(startCoin, endCoin, pools, maxHops);
161
+ };
162
+
163
+ // Simulate a single swap in a pool (aligned with backend's swapSingle)
164
+ export const simulateSwap = (pool, inputCoin, amountInBN, isCoinAInput) => {
165
+ if (!pool.runesCompliant) {return null;}
166
+
167
+ if (amountInBN.lt(1)) {return null;}
168
+
169
+ const reserveABN = new BigNumber(pool.reserveA);
170
+ const reserveBBN = new BigNumber(pool.reserveB);
171
+ if (reserveABN.isZero() || reserveBBN.isZero()) {return null;}
172
+
173
+ const lpFeeRate = new BigNumber(pool.lpFeeRate).div(100);
174
+ const treasuryFeeRate = new BigNumber(pool.treasuryFeeRate).div(100);
175
+ const totalFeeRate = lpFeeRate.plus(treasuryFeeRate);
176
+
177
+ // Calculate total fee and split proportionally
178
+ const totalFeeAmount = amountInBN.times(totalFeeRate).integerValue(BigNumber.ROUND_DOWN);
179
+ let treasuryFeeAmount = new BigNumber(0);
180
+ let lpFeeAmount = new BigNumber(0);
181
+ if (!totalFeeRate.eq(0)) {
182
+ treasuryFeeAmount = totalFeeAmount.times(treasuryFeeRate).div(totalFeeRate).integerValue(BigNumber.ROUND_DOWN);
183
+ lpFeeAmount = totalFeeAmount.minus(treasuryFeeAmount);
184
+ }
185
+
186
+ const amountInForPool = amountInBN.minus(totalFeeAmount);
187
+ if (amountInForPool.lt(1)) {return null;}
188
+
189
+ // const outputCoin = isCoinAInput ? pool.coinB : pool.coinA;
190
+ // const outputCoinDp = outputCoin.dp || 8;
191
+
192
+ let amountOutBN;
193
+ if (isCoinAInput) {
194
+ amountOutBN = amountInForPool.times(reserveBBN).div(reserveABN.plus(amountInForPool));
195
+ } else {
196
+ amountOutBN = amountInForPool.times(reserveABN).div(reserveBBN.plus(amountInForPool));
197
+ }
198
+ amountOutBN = amountOutBN.integerValue(BigNumber.ROUND_DOWN);
199
+
200
+ if (amountOutBN.isNaN() || amountOutBN.lt(1)) {return null;}
201
+
202
+ // Simulate reserve updates (mimicking backend)
203
+ const updatedPool = { ...pool };
204
+ if (isCoinAInput) {
205
+ updatedPool.reserveA = reserveABN.plus(amountInForPool).plus(lpFeeAmount).toString();
206
+ updatedPool.reserveB = reserveBBN.minus(amountOutBN).toString();
207
+ } else {
208
+ updatedPool.reserveB = reserveBBN.plus(amountInForPool).plus(lpFeeAmount).toString();
209
+ updatedPool.reserveA = reserveABN.minus(amountOutBN).toString();
210
+ }
211
+
212
+ return {
213
+ amountOut: amountOutBN,
214
+ updatedPool,
215
+ lpFeeAmount,
216
+ treasuryFeeAmount,
217
+ };
218
+ };
219
+
220
+ // Estimate a single path
221
+ export const estimatePath = async (pools, path, inputCoin, amountIn, coins) => {
222
+ let currentAmount = new BigNumber(amountIn).shiftedBy(inputCoin.dp || 8);
223
+ let currentCoin = inputCoin;
224
+ let priceImpact = 0;
225
+ const intermediateAmounts = [];
226
+ let updatedPools = [...pools]; // Clone pools to simulate reserve updates
227
+
228
+ for (const step of path) {
229
+ const poolIndex = updatedPools.findIndex(
230
+ (p) =>
231
+ (p.coinA.ticker === step.from && p.coinB.ticker === step.to) ||
232
+ (p.coinB.ticker === step.from && p.coinA.ticker === step.to)
233
+ );
234
+ if (poolIndex === -1 || !updatedPools[poolIndex].runesCompliant) {return null;}
235
+
236
+ const pool = updatedPools[poolIndex];
237
+ const isCoinAInput = pool.coinA.ticker === step.from;
238
+ const outputCoin = coins.find((c) => c.ticker === step.to);
239
+ if (!outputCoin) {return null;}
240
+
241
+ const currentCoinDp = currentCoin.dp || 8;
242
+ const smallestUnit = new BigNumber(1).shiftedBy(-currentCoinDp);
243
+ if (new BigNumber(currentAmount).shiftedBy(-currentCoinDp).lt(smallestUnit)) {
244
+ return null;
245
+ }
246
+
247
+ const swapResult = simulateSwap(pool, currentCoin, currentAmount, isCoinAInput);
248
+ if (!swapResult || swapResult.amountOut.lte(0)) {return null;}
249
+
250
+ const { amountOut: amountOutBN, updatedPool } = swapResult;
251
+
252
+ // Update the pool in the cloned pools array
253
+ updatedPools[poolIndex] = updatedPool;
254
+
255
+ const reserveA = new BigNumber(pool.reserveA);
256
+ const reserveB = new BigNumber(pool.reserveB);
257
+ const spotPrice = isCoinAInput ? reserveB.div(reserveA) : reserveA.div(reserveB);
258
+ const totalFeeRate = new BigNumber(pool.lpFeeRate).plus(pool.treasuryFeeRate).div(100);
259
+ const effectivePrice = amountOutBN.div(
260
+ currentAmount.times(new BigNumber(1).minus(totalFeeRate))
261
+ );
262
+ const stepPriceImpact = spotPrice.minus(effectivePrice).div(spotPrice).abs().toNumber();
263
+ priceImpact += stepPriceImpact;
264
+
265
+ currentAmount = amountOutBN;
266
+ currentCoin = outputCoin;
267
+ if (step !== path[path.length - 1]) {
268
+ intermediateAmounts.push({
269
+ ticker: step.to,
270
+ amount: currentAmount.shiftedBy(-(outputCoin.dp || 8)).toString(),
271
+ });
272
+ }
273
+ }
274
+
275
+ const outputCoin = coins.find((c) => c.ticker === path[path.length - 1].to);
276
+ const outputCoinDp = outputCoin.dp || 8;
277
+ if (
278
+ new BigNumber(currentAmount)
279
+ .shiftedBy(-outputCoinDp)
280
+ .lt(new BigNumber(1).shiftedBy(-outputCoinDp))
281
+ ) {
282
+ return null;
283
+ }
284
+
285
+ return {
286
+ amountOut: currentAmount,
287
+ priceImpact: priceImpact / path.length,
288
+ intermediateAmounts,
289
+ updatedPools,
290
+ };
291
+ };
292
+
293
+ // Main estimateSwap function
294
+ export const estimateSwap = async (
295
+ inputCoin,
296
+ outputCoin,
297
+ amountIn,
298
+ pools,
299
+ coins,
300
+ maxHops = 6,
301
+ algorithm = 'dfs' // Default to DFS
302
+ ) => {
303
+ // Validate inputs
304
+ if (!inputCoin || !outputCoin) {
305
+ throw new Error('Input or output coin not provided');
306
+ }
307
+ validatePositiveNumber(amountIn, 'amountIn');
308
+ validatePositiveNumber(maxHops, 'maxHops');
309
+ if (maxHops < 1 || maxHops > 14) {
310
+ throw new Error('Max hops must be between 1 and 14');
311
+ }
312
+ if (!['dfs', 'bfs'].includes(algorithm)) {
313
+ throw new Error('Invalid algorithm: must be "dfs" or "bfs"');
314
+ }
315
+
316
+ const inputCoinData = coins.find((c) => c.ticker === inputCoin.ticker);
317
+ const outputCoinData = coins.find((c) => c.ticker === outputCoin.ticker);
318
+ if (!inputCoinData || !outputCoinData) {
319
+ throw new Error(`Coin not found: ${inputCoin.ticker} or ${outputCoin.ticker}`);
320
+ }
321
+
322
+ const inputCoinDp = inputCoinData.dp || 8;
323
+ const amountInBN = new BigNumber(amountIn);
324
+ if (amountInBN.decimalPlaces() > inputCoinDp) {
325
+ throw new Error(
326
+ `Input amount ${amountIn} ${inputCoin.ticker} has ${amountInBN.decimalPlaces()} decimal places, exceeding allowed ${inputCoinDp}`
327
+ );
328
+ }
329
+
330
+ const smallestUnit = new BigNumber(1).shiftedBy(-inputCoinDp);
331
+ if (amountInBN.lt(smallestUnit)) {
332
+ throw new Error(
333
+ `Input amount ${amountIn} ${inputCoin.ticker} is less than the smallest unit (10^-${inputCoinDp})`
334
+ );
335
+ }
336
+
337
+ // Find all paths using the specified algorithm
338
+ const paths = findAllPaths(inputCoinData, outputCoinData, pools, maxHops, algorithm);
339
+ if (!paths.length) {
340
+ throw new Error('No valid swap paths found with RUNES-compliant pools');
341
+ }
342
+
343
+ // Estimate each path
344
+ let bestPath = null;
345
+ let maxAmountOut = new BigNumber(0);
346
+ let bestResult = null;
347
+
348
+ for (const path of paths) {
349
+ const result = await estimatePath(pools, path, inputCoinData, amountIn, coins);
350
+ if (result && result.amountOut.gt(maxAmountOut)) {
351
+ maxAmountOut = result.amountOut;
352
+ bestPath = path;
353
+ bestResult = result;
354
+ }
355
+ }
356
+
357
+ if (!bestPath) {
358
+ throw new Error('No valid swap path with positive output using RUNES-compliant pools');
359
+ }
360
+
361
+ // Calculate USD prices
362
+ const runesPriceUSD = await getRunesPriceUSD(pools);
363
+ const priceAInRunes = getTokenPriceInRunes(inputCoinData, pools);
364
+ const priceBInRunes = getTokenPriceInRunes(outputCoinData, pools);
365
+ if (!priceAInRunes || !priceBInRunes) {
366
+ throw new Error('Pool not initialized or invalid token');
367
+ }
368
+
369
+ const priceAUSD = new BigNumber(priceAInRunes).times(runesPriceUSD).toString();
370
+ const priceBUSD = new BigNumber(priceBInRunes).times(runesPriceUSD).toString();
371
+ const inputValueUSD = new BigNumber(amountIn).times(priceAUSD).toString();
372
+
373
+ const outputValueUSD = maxAmountOut
374
+ .shiftedBy(-outputCoinData.dp)
375
+ .times(priceBUSD)
376
+ .toString();
377
+
378
+ // Simulate after-swap prices
379
+ let priceAInRunesAfter = new BigNumber(priceAInRunes);
380
+ let priceBInRunesAfter = new BigNumber(priceBInRunes);
381
+ let currentAmount = new BigNumber(amountIn).shiftedBy(inputCoinData.dp || 8);
382
+ let currentCoin = inputCoinData;
383
+ let updatedPools = [...pools]; // Clone pools for after-swap price simulation
384
+
385
+ for (const step of bestPath) {
386
+ const poolIndex = updatedPools.findIndex(
387
+ (p) =>
388
+ (p.coinA.ticker === step.from && p.coinB.ticker === step.to) ||
389
+ (p.coinB.ticker === step.from && p.coinA.ticker === step.to)
390
+ );
391
+ const pool = updatedPools[poolIndex];
392
+ const isCoinAInput = pool.coinA.ticker === step.from;
393
+ const nextCoin = coins.find((c) => c.ticker === step.to);
394
+ const swapResult = simulateSwap(pool, currentCoin, currentAmount, isCoinAInput);
395
+
396
+ const reserveA = new BigNumber(pool.reserveA);
397
+ const reserveB = new BigNumber(pool.reserveB);
398
+ if (isCoinAInput) {
399
+ const newRunesReserve = reserveA.plus(currentAmount);
400
+ const newTokenReserve = reserveB.minus(swapResult.amountOut);
401
+ if (step.from === 'RUNES') {
402
+ priceAInRunesAfter = newRunesReserve.div(newTokenReserve);
403
+ } else if (step.to === 'RUNES') {
404
+ priceBInRunesAfter = newRunesReserve.div(newTokenReserve);
405
+ }
406
+ } else {
407
+ const newTokenReserve = reserveB.plus(currentAmount);
408
+ const newRunesReserve = reserveA.minus(swapResult.amountOut);
409
+ if (step.from === 'RUNES') {
410
+ priceAInRunesAfter = newRunesReserve.div(newTokenReserve);
411
+ } else if (step.to === 'RUNES') {
412
+ priceBInRunesAfter = newRunesReserve.div(newTokenReserve);
413
+ }
414
+ }
415
+
416
+ currentAmount = swapResult.amountOut;
417
+ currentCoin = nextCoin;
418
+ updatedPools[poolIndex] = swapResult.updatedPool; // Update pool for next iteration
419
+ }
420
+
421
+ return {
422
+ input: {
423
+ token: inputCoin.ticker,
424
+ amount: amountIn,
425
+ priceUSD: priceAUSD,
426
+ valueUSD: inputValueUSD,
427
+ priceInRunes: priceAInRunes,
428
+ },
429
+ output: {
430
+ token: outputCoin.ticker,
431
+ amount: maxAmountOut.shiftedBy(-outputCoinData.dp).toString(),
432
+ priceUSD: priceBUSD,
433
+ valueUSD: outputValueUSD,
434
+ priceInRunes: priceBInRunes,
435
+ },
436
+ slippage: {
437
+ priceImpact: bestResult.priceImpact * 100,
438
+ intermediateAmounts: bestResult.intermediateAmounts,
439
+ },
440
+ afterSwapPrices: {
441
+ [inputCoin.ticker]: priceAInRunesAfter.times(runesPriceUSD).toString(),
442
+ [outputCoin.ticker]: priceBInRunesAfter.times(runesPriceUSD).toString(),
443
+ },
444
+ path: bestPath,
445
+ algorithm, // Include algorithm in the response
446
+ };
447
+ };
@@ -0,0 +1,167 @@
1
+ import { poolStore, getPools } from './store/poolStore.mjs';
2
+ import { coinStore, getCoins } from './store/coinStore.mjs';
3
+ import { walletStore, getWallets } from './store/walletStore.mjs';
4
+ import { userSharesStore, getUserShares } from './store/userSharesStore.mjs';
5
+
6
+ export function waitForStores(socket) {
7
+ // Function to wait for poolStore to be populated
8
+ const waitForPools = () => {
9
+ return new Promise((resolve, reject) => {
10
+ if (poolStore.isInitialReceived) {
11
+ const pools = getPools();
12
+ console.log('Initial pool data already received:', pools.length, 'pools', pools);
13
+ resolve(pools);
14
+ return;
15
+ }
16
+
17
+ const timeout = setTimeout(() => {
18
+ reject(new Error('Timeout waiting for initial pool data'));
19
+ }, 30000); // 30-second timeout
20
+
21
+ socket.on('pools_updated', ({ isInitial, pools }) => {
22
+ console.log('Received pools_updated:', { isInitial, pools });
23
+ if (isInitial) {
24
+ const pools = getPools();
25
+ console.log('Initial pool data received:', pools.length, 'pools', pools);
26
+ clearTimeout(timeout);
27
+ resolve(pools);
28
+ }
29
+ });
30
+
31
+ socket.on('connect_error', (err) => {
32
+ console.error('Socket connect error:', err.message);
33
+ clearTimeout(timeout);
34
+ reject(new Error('Socket connection failed'));
35
+ });
36
+
37
+ socket.on('disconnect', (reason) => {
38
+ console.error('Socket disconnected:', reason);
39
+ clearTimeout(timeout);
40
+ reject(new Error('Socket disconnected before receiving initial pool data'));
41
+ });
42
+ });
43
+ };
44
+
45
+ // Function to wait for coinStore to be populated
46
+ const waitForCoins = () => {
47
+ return new Promise((resolve, reject) => {
48
+ if (coinStore.isInitialReceived) {
49
+ const coins = getCoins();
50
+ console.log('Initial coin data already received:', coins.length, 'coins', coins);
51
+ resolve(coins);
52
+ return;
53
+ }
54
+
55
+ const timeout = setTimeout(() => {
56
+ reject(new Error('Timeout waiting for initial coin data'));
57
+ }, 30000); // 30-second timeout
58
+
59
+ socket.on('coins_updated', ({ isInitial, coins }) => {
60
+ console.log('Received coins_updated:', { isInitial, coins });
61
+ if (isInitial) {
62
+ const coins = getCoins();
63
+ console.log('Initial coin data received:', coins.length, 'coins', coins);
64
+ clearTimeout(timeout);
65
+ resolve(coins);
66
+ }
67
+ });
68
+
69
+ socket.on('connect_error', (err) => {
70
+ console.error('Socket connect error:', err.message);
71
+ clearTimeout(timeout);
72
+ reject(new Error('Socket connection failed'));
73
+ });
74
+
75
+ socket.on('disconnect', (reason) => {
76
+ console.error('Socket disconnected:', reason);
77
+ clearTimeout(timeout);
78
+ reject(new Error('Socket disconnected before receiving initial coin data'));
79
+ });
80
+ });
81
+ };
82
+
83
+ // Function to wait for walletStore to be populated
84
+ const waitForWallets = () => {
85
+ return new Promise((resolve, reject) => {
86
+ if (walletStore.isInitialReceived) {
87
+ const wallets = getWallets();
88
+ console.log('Initial wallet data already received:', wallets.length, 'wallets', wallets);
89
+ resolve(wallets);
90
+ return;
91
+ }
92
+
93
+ const timeout = setTimeout(() => {
94
+ reject(new Error('Timeout waiting for initial wallet data'));
95
+ }, 30000); // 30-second timeout
96
+
97
+ socket.on('wallets_updated', ({ isInitial, wallets }) => {
98
+ console.log('Received wallets_updated:', { isInitial, wallets });
99
+ if (isInitial) {
100
+ const wallets = getWallets();
101
+ console.log('Initial wallet data received:', wallets.length, 'wallets', wallets);
102
+ clearTimeout(timeout);
103
+ resolve(wallets);
104
+ }
105
+ });
106
+
107
+ socket.on('connect_error', (err) => {
108
+ console.error('Socket connect error:', err.message);
109
+ clearTimeout(timeout);
110
+ reject(new Error('Socket connection failed'));
111
+ });
112
+
113
+ socket.on('disconnect', (reason) => {
114
+ console.error('Socket disconnected:', reason);
115
+ clearTimeout(timeout);
116
+ reject(new Error('Socket disconnected before receiving initial wallet data'));
117
+ });
118
+ });
119
+ };
120
+
121
+ // Function to wait for userSharesStore to be populated
122
+ const waitForUserShares = () => {
123
+ return new Promise((resolve, reject) => {
124
+ if (userSharesStore.isInitialReceived) {
125
+ const userShares = getUserShares();
126
+ console.log('Initial user shares data already received:', userShares.length, 'user shares', userShares);
127
+ resolve(userShares);
128
+ return;
129
+ }
130
+
131
+ const timeout = setTimeout(() => {
132
+ reject(new Error('Timeout waiting for initial user shares data'));
133
+ }, 30000); // 30-second timeout
134
+
135
+ socket.on('user_shares_updated', ({ isInitial, userShares }) => {
136
+ console.log('Received user_shares_updated:', { isInitial, userShares });
137
+ if (isInitial) {
138
+ const userShares = getUserShares();
139
+ console.log('Initial user shares data received:', userShares.length, 'user shares', userShares);
140
+ clearTimeout(timeout);
141
+ resolve(userShares);
142
+ }
143
+ });
144
+
145
+ socket.on('connect_error', (err) => {
146
+ console.error('Socket connect error:', err.message);
147
+ clearTimeout(timeout);
148
+ reject(new Error('Socket connection failed'));
149
+ });
150
+
151
+ socket.on('disconnect', (reason) => {
152
+ console.error('Socket disconnected:', reason);
153
+ clearTimeout(timeout);
154
+ reject(new Error('Socket disconnected before receiving initial user shares data'));
155
+ });
156
+ });
157
+ };
158
+
159
+ // Return a promise that resolves when all stores are populated
160
+ return Promise.all([waitForPools(), waitForCoins(), waitForWallets(), waitForUserShares()])
161
+ .then(([pools, coins, wallets, userShares]) => ({
162
+ pools,
163
+ coins,
164
+ wallets,
165
+ userShares,
166
+ }));
167
+ }
@@ -0,0 +1,14 @@
1
+ // src/workers/swapWorker.mjs
2
+ import { parentPort } from 'worker_threads';
3
+
4
+ import { estimateSwap } from '../utils/swapUtils.mjs';
5
+
6
+ parentPort.on('message', async (data) => {
7
+ const { inputCoin, outputCoin, amountIn, pools, coins, maxHops, algorithm } = data;
8
+ try {
9
+ const result = await estimateSwap(inputCoin, outputCoin, amountIn, pools, coins, maxHops, algorithm);
10
+ parentPort.postMessage({ result });
11
+ } catch (err) {
12
+ parentPort.postMessage({ error: err.message });
13
+ }
14
+ });