@runesx/api-client 0.4.0 → 0.5.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.
- package/package.json +7 -7
- package/src/api.mjs +73 -0
- package/src/index.mjs +19 -3
- package/src/socket.mjs +53 -0
- package/src/store/coinStore.mjs +1 -1
- package/src/store/exchangeConfigStore.mjs +48 -0
- package/src/store/marketStore.mjs +57 -0
- package/src/store/orderbookStore.mjs +99 -0
- package/src/store/poolStore.mjs +1 -1
- package/src/store/userSharesStore.mjs +1 -1
- package/src/store/walletStore.mjs +1 -1
- package/src/utils/liquidityUtils.mjs +2 -3
- package/src/utils/priceUtils.mjs +2 -1
- package/src/utils/safeBigNumber.mjs +11 -0
- package/src/utils/swapUtils.mjs +1159 -129
- package/src/waitForStores.mjs +41 -6
- package/src/workers/swapWorker.mjs +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@runesx/api-client",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "A Node.js client for interacting with the RunesX platform API and WebSocket",
|
|
5
5
|
"main": "src/index.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -13,23 +13,23 @@
|
|
|
13
13
|
"test": "jest"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"axios": "^1.13.
|
|
17
|
-
"bignumber.js": "^
|
|
16
|
+
"axios": "^1.13.6",
|
|
17
|
+
"bignumber.js": "^10.0.2",
|
|
18
18
|
"socket.io-client": "^4.8.3"
|
|
19
19
|
},
|
|
20
20
|
"devDependencies": {
|
|
21
21
|
"@eslint/js": "^9.11.1",
|
|
22
22
|
"@semantic-release/changelog": "^6.0.3",
|
|
23
23
|
"@semantic-release/git": "^10.0.1",
|
|
24
|
-
"@semantic-release/npm": "^13.1.
|
|
24
|
+
"@semantic-release/npm": "^13.1.5",
|
|
25
25
|
"dotenv": "^17.3.1",
|
|
26
26
|
"eslint": "^9.11.1",
|
|
27
27
|
"eslint-plugin-import": "^2.30.0",
|
|
28
28
|
"eslint-plugin-n": "^17.24.0",
|
|
29
29
|
"eslint-plugin-promise": "^7.1.0",
|
|
30
|
-
"globals": "^17.
|
|
31
|
-
"jest": "^30.
|
|
32
|
-
"nodemon": "^3.1.
|
|
30
|
+
"globals": "^17.4.0",
|
|
31
|
+
"jest": "^30.3.0",
|
|
32
|
+
"nodemon": "^3.1.14",
|
|
33
33
|
"semantic-release": "^25.0.3"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
package/src/api.mjs
CHANGED
|
@@ -341,6 +341,73 @@ export function createApi(config) {
|
|
|
341
341
|
}
|
|
342
342
|
}
|
|
343
343
|
|
|
344
|
+
// ---- Orders endpoints ----
|
|
345
|
+
|
|
346
|
+
async function placeOrder({ pair, side, price, quantity, timeInForce, inverted, idempotencyKey }) {
|
|
347
|
+
try {
|
|
348
|
+
const key = idempotencyKey || randomUUID();
|
|
349
|
+
const body = { pair, side, price, quantity, timeInForce };
|
|
350
|
+
if (inverted !== undefined && inverted !== null) {
|
|
351
|
+
body.inverted = inverted;
|
|
352
|
+
}
|
|
353
|
+
const response = await api.post('/orders', body, {
|
|
354
|
+
headers: { 'x-idempotency-key': key },
|
|
355
|
+
});
|
|
356
|
+
return response.data;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
throw new Error(error.response?.data?.error || 'Failed to place order');
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function cancelOrder(orderId, { idempotencyKey } = {}) {
|
|
363
|
+
try {
|
|
364
|
+
const headers = {};
|
|
365
|
+
if (idempotencyKey) {
|
|
366
|
+
headers['x-idempotency-key'] = idempotencyKey;
|
|
367
|
+
}
|
|
368
|
+
const response = await api.delete(`/orders/${encodeURIComponent(orderId)}`, { headers });
|
|
369
|
+
return response.data;
|
|
370
|
+
} catch (error) {
|
|
371
|
+
throw new Error(error.response?.data?.error || 'Failed to cancel order');
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
async function getOrders({ pair, status, limit, offset } = {}) {
|
|
376
|
+
try {
|
|
377
|
+
const params = {};
|
|
378
|
+
if (pair) { params.pair = pair; }
|
|
379
|
+
if (status) { params.status = status; }
|
|
380
|
+
if (limit) { params.limit = limit; }
|
|
381
|
+
if (offset !== undefined && offset !== null) { params.offset = offset; }
|
|
382
|
+
const response = await api.get('/orders', { params });
|
|
383
|
+
return response.data;
|
|
384
|
+
} catch (error) {
|
|
385
|
+
throw new Error(error.response?.data?.error || 'Failed to fetch orders');
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async function getOrderBookApi(pair, { levels } = {}) {
|
|
390
|
+
try {
|
|
391
|
+
const params = {};
|
|
392
|
+
if (levels) { params.levels = levels; }
|
|
393
|
+
const response = await api.get(`/orders/book/${encodeURIComponent(pair)}`, { params });
|
|
394
|
+
return response.data;
|
|
395
|
+
} catch (error) {
|
|
396
|
+
throw new Error(error.response?.data?.error || 'Failed to fetch order book');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
async function getTrades(pair, { limit } = {}) {
|
|
401
|
+
try {
|
|
402
|
+
const params = {};
|
|
403
|
+
if (limit) { params.limit = limit; }
|
|
404
|
+
const response = await api.get(`/orders/trades/${encodeURIComponent(pair)}`, { params });
|
|
405
|
+
return response.data;
|
|
406
|
+
} catch (error) {
|
|
407
|
+
throw new Error(error.response?.data?.error || 'Failed to fetch trades');
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
344
411
|
async function deleteYardMessage(messageId) {
|
|
345
412
|
try {
|
|
346
413
|
const response = await api.delete(`/yard/messages/${encodeURIComponent(messageId)}`);
|
|
@@ -385,5 +452,11 @@ export function createApi(config) {
|
|
|
385
452
|
getTransactionHistory,
|
|
386
453
|
getUserOperations,
|
|
387
454
|
deleteYardMessage,
|
|
455
|
+
// Orders
|
|
456
|
+
placeOrder,
|
|
457
|
+
cancelOrder,
|
|
458
|
+
getOrders,
|
|
459
|
+
getOrderBookApi,
|
|
460
|
+
getTrades,
|
|
388
461
|
};
|
|
389
462
|
}
|
package/src/index.mjs
CHANGED
|
@@ -7,6 +7,9 @@ import { getCoins, getCoinByTicker } from './store/coinStore.mjs';
|
|
|
7
7
|
import { getChains, getChainByName } from './store/chainStore.mjs';
|
|
8
8
|
import { getWallets as getWalletsStore, getWalletByTicker } from './store/walletStore.mjs';
|
|
9
9
|
import { getUserShares, getUserShareByPoolId } from './store/userSharesStore.mjs';
|
|
10
|
+
import { getAllOrderBooks, getOrderBook, getOrderBookPairs, getUserOrders } from './store/orderbookStore.mjs';
|
|
11
|
+
import { getMarkets, getMarketByCoinKey, getMarketByCoins } from './store/marketStore.mjs';
|
|
12
|
+
import { getClobFees } from './store/exchangeConfigStore.mjs';
|
|
10
13
|
import { waitForStores } from './waitForStores.mjs';
|
|
11
14
|
import { estimateLiquidityFrontend, checkRunesLiquidityFrontend, calculateShareAmounts, estimateDepositShares } from './utils/liquidityUtils.mjs';
|
|
12
15
|
import { estimateSwap } from './utils/swapUtils.mjs';
|
|
@@ -23,9 +26,9 @@ export function createRunesXClient(options = {}) {
|
|
|
23
26
|
throw new Error('API_KEY is required');
|
|
24
27
|
}
|
|
25
28
|
socketHandler = setupSocket(config);
|
|
26
|
-
const { pools, coins, chains, wallets, userShares } = await waitForStores(socketHandler.socket);
|
|
29
|
+
const { pools, coins, chains, wallets, userShares, orderbooks } = await waitForStores(socketHandler.socket);
|
|
27
30
|
initialized = true;
|
|
28
|
-
return { pools, coins, chains, wallets, userShares };
|
|
31
|
+
return { pools, coins, chains, wallets, userShares, orderbooks };
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
function ensureInitialized() {
|
|
@@ -54,6 +57,12 @@ export function createRunesXClient(options = {}) {
|
|
|
54
57
|
getWalletByTicker,
|
|
55
58
|
getUserShares,
|
|
56
59
|
getUserShareByPoolId,
|
|
60
|
+
getAllOrderBooks,
|
|
61
|
+
getOrderBook,
|
|
62
|
+
getOrderBookPairs,
|
|
63
|
+
getUserOrders,
|
|
64
|
+
getMarkets,
|
|
65
|
+
getMarketByCoins,
|
|
57
66
|
|
|
58
67
|
// ---- Event callbacks ----
|
|
59
68
|
on: (event, callback) => {
|
|
@@ -133,13 +142,20 @@ export function createRunesXClient(options = {}) {
|
|
|
133
142
|
getVolumePool: api.getVolumePool,
|
|
134
143
|
getBucketsPools: api.getBucketsPools,
|
|
135
144
|
|
|
145
|
+
// ---- Orders API (auth required for place/cancel/getOrders, public for book/trades) ----
|
|
146
|
+
placeOrder: api.placeOrder,
|
|
147
|
+
cancelOrder: api.cancelOrder,
|
|
148
|
+
getOrders: api.getOrders,
|
|
149
|
+
getOrderBookApi: api.getOrderBookApi,
|
|
150
|
+
getTrades: api.getTrades,
|
|
151
|
+
|
|
136
152
|
// ---- Yard (chat) API ----
|
|
137
153
|
getYardMessages: api.getYardMessages,
|
|
138
154
|
deleteYardMessage: api.deleteYardMessage,
|
|
139
155
|
|
|
140
156
|
// ---- Client-side estimation utilities ----
|
|
141
157
|
estimateSwap: (inputCoin, outputCoin, amountIn, maxHops = 6, algorithm = 'dfs') =>
|
|
142
|
-
estimateSwap(inputCoin, outputCoin, amountIn, getPools(), getCoins(), maxHops, algorithm),
|
|
158
|
+
estimateSwap(inputCoin, outputCoin, amountIn, getPools(), getCoins(), maxHops, algorithm, getAllOrderBooks(), getUserOrders(), getClobFees(), getMarketByCoinKey()),
|
|
143
159
|
estimateLiquidityFrontend,
|
|
144
160
|
estimateDepositShares: ({ pool, amountA, amountB, slippagePercent } = {}) =>
|
|
145
161
|
estimateDepositShares({ pool, amountA, amountB, slippagePercent }),
|
package/src/socket.mjs
CHANGED
|
@@ -6,6 +6,9 @@ import { setInitialCoins, updateCoin, resetCoins } from './store/coinStore.mjs';
|
|
|
6
6
|
import { setInitialChains, updateChain, resetChains } from './store/chainStore.mjs';
|
|
7
7
|
import { setInitialWallets, updateWallet, resetWallets } from './store/walletStore.mjs';
|
|
8
8
|
import { setInitialUserShares, updateUserShare, resetUserShares } from './store/userSharesStore.mjs';
|
|
9
|
+
import { setInitialOrderBooks, updateOrderBook, resetOrderBooks, setUserOrders, updateUserOrder } from './store/orderbookStore.mjs';
|
|
10
|
+
import { setInitialMarkets, addOrUpdateMarket, resetMarkets } from './store/marketStore.mjs';
|
|
11
|
+
import { setExchangeConfig } from './store/exchangeConfigStore.mjs';
|
|
9
12
|
|
|
10
13
|
export function setupSocket(config) {
|
|
11
14
|
const socket = io(config.socketUrl, {
|
|
@@ -53,6 +56,8 @@ export function setupSocket(config) {
|
|
|
53
56
|
resetChains();
|
|
54
57
|
resetWallets();
|
|
55
58
|
resetUserShares();
|
|
59
|
+
resetOrderBooks();
|
|
60
|
+
resetMarkets();
|
|
56
61
|
}
|
|
57
62
|
emit('connect_error', err);
|
|
58
63
|
});
|
|
@@ -65,6 +70,7 @@ export function setupSocket(config) {
|
|
|
65
70
|
resetChains();
|
|
66
71
|
resetWallets();
|
|
67
72
|
resetUserShares();
|
|
73
|
+
resetMarkets();
|
|
68
74
|
emit('disconnect', reason);
|
|
69
75
|
});
|
|
70
76
|
|
|
@@ -86,6 +92,7 @@ export function setupSocket(config) {
|
|
|
86
92
|
resetChains();
|
|
87
93
|
resetWallets();
|
|
88
94
|
resetUserShares();
|
|
95
|
+
resetOrderBooks();
|
|
89
96
|
}
|
|
90
97
|
emit('reconnect_error', err);
|
|
91
98
|
});
|
|
@@ -97,6 +104,26 @@ export function setupSocket(config) {
|
|
|
97
104
|
|
|
98
105
|
// ---- Public room events ----
|
|
99
106
|
|
|
107
|
+
socket.on('exchange_config', (config) => {
|
|
108
|
+
setExchangeConfig(config);
|
|
109
|
+
emit('exchange_config', config);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
socket.on('markets_initial', (data) => {
|
|
113
|
+
setInitialMarkets(data);
|
|
114
|
+
emit('markets_initial', data);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
socket.on('market_created', (data) => {
|
|
118
|
+
addOrUpdateMarket(data);
|
|
119
|
+
emit('market_created', data);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
socket.on('market_updated', (data) => {
|
|
123
|
+
addOrUpdateMarket(data);
|
|
124
|
+
emit('market_updated', data);
|
|
125
|
+
});
|
|
126
|
+
|
|
100
127
|
socket.on('pools_updated', ({ pools, isInitial }) => {
|
|
101
128
|
if (isInitial) {
|
|
102
129
|
setInitialPools(pools);
|
|
@@ -140,6 +167,32 @@ export function setupSocket(config) {
|
|
|
140
167
|
emit('status_updated', data);
|
|
141
168
|
});
|
|
142
169
|
|
|
170
|
+
socket.on('orderbooks_initial', ({ books, isInitial }) => {
|
|
171
|
+
if (isInitial) {
|
|
172
|
+
setInitialOrderBooks(books);
|
|
173
|
+
}
|
|
174
|
+
emit('orderbooks_initial', { books, isInitial });
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
socket.on('orderbook_updated', ({ pair, bids, asks }) => {
|
|
178
|
+
updateOrderBook(pair, bids, asks);
|
|
179
|
+
emit('orderbook_updated', { pair, bids, asks });
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
socket.on('user_orders_initial', (orders) => {
|
|
183
|
+
setUserOrders(orders);
|
|
184
|
+
emit('user_orders_initial', orders);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
socket.on('order_updated', (data) => {
|
|
188
|
+
updateUserOrder(data);
|
|
189
|
+
emit('order_updated', data);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
socket.on('clob_trade', (trade) => {
|
|
193
|
+
emit('clob_trade', trade);
|
|
194
|
+
});
|
|
195
|
+
|
|
143
196
|
socket.on('volumeUpdate', (data) => {
|
|
144
197
|
emit('volumeUpdate', data);
|
|
145
198
|
});
|
package/src/store/coinStore.mjs
CHANGED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// src/store/exchangeConfigStore.mjs
|
|
2
|
+
// Exchange configuration received from the backend via socket.
|
|
3
|
+
// Keeps fee rates, depth limits, etc. in sync without hardcoding.
|
|
4
|
+
|
|
5
|
+
const exchangeConfigStore = {
|
|
6
|
+
clobFees: {
|
|
7
|
+
takerFeeRate: '0.002', // sensible default until backend config arrives
|
|
8
|
+
makerFeeRate: '0.001',
|
|
9
|
+
maxFillBatch: 50,
|
|
10
|
+
maxFillTotal: 500,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const setExchangeConfig = (config) => {
|
|
15
|
+
const { clobFees } = config;
|
|
16
|
+
if (clobFees) {
|
|
17
|
+
if (clobFees.takerFeeRate !== null && clobFees.takerFeeRate !== undefined) {
|
|
18
|
+
exchangeConfigStore.clobFees.takerFeeRate = clobFees.takerFeeRate;
|
|
19
|
+
}
|
|
20
|
+
if (clobFees.makerFeeRate !== null && clobFees.makerFeeRate !== undefined) {
|
|
21
|
+
exchangeConfigStore.clobFees.makerFeeRate = clobFees.makerFeeRate;
|
|
22
|
+
}
|
|
23
|
+
if (clobFees.maxFillBatch !== null && clobFees.maxFillBatch !== undefined) {
|
|
24
|
+
exchangeConfigStore.clobFees.maxFillBatch = clobFees.maxFillBatch;
|
|
25
|
+
}
|
|
26
|
+
if (clobFees.maxFillTotal !== null && clobFees.maxFillTotal !== undefined) {
|
|
27
|
+
exchangeConfigStore.clobFees.maxFillTotal = clobFees.maxFillTotal;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const getClobFees = () => exchangeConfigStore.clobFees;
|
|
33
|
+
|
|
34
|
+
const resetExchangeConfig = () => {
|
|
35
|
+
exchangeConfigStore.clobFees = {
|
|
36
|
+
takerFeeRate: '0.002',
|
|
37
|
+
makerFeeRate: '0.001',
|
|
38
|
+
maxFillBatch: 50,
|
|
39
|
+
maxFillTotal: 500,
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export {
|
|
44
|
+
exchangeConfigStore,
|
|
45
|
+
setExchangeConfig,
|
|
46
|
+
getClobFees,
|
|
47
|
+
resetExchangeConfig,
|
|
48
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// src/store/marketStore.mjs
|
|
2
|
+
// Stores admin-defined markets received from the backend via socket.
|
|
3
|
+
// Markets define the canonical pair direction (e.g., "RUNES-DOG" not "DOG-RUNES").
|
|
4
|
+
|
|
5
|
+
let markets = [];
|
|
6
|
+
let byCoinKey = {};
|
|
7
|
+
|
|
8
|
+
function buildCoinKey(coinIdA, coinIdB) {
|
|
9
|
+
return coinIdA < coinIdB ? `${coinIdA}|${coinIdB}` : `${coinIdB}|${coinIdA}`;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function rebuildLookup() {
|
|
13
|
+
const lookup = {};
|
|
14
|
+
for (const m of markets) {
|
|
15
|
+
const key = buildCoinKey(m.baseCoinId, m.quoteCoinId);
|
|
16
|
+
lookup[key] = m;
|
|
17
|
+
}
|
|
18
|
+
byCoinKey = lookup;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function setInitialMarkets(list) {
|
|
22
|
+
markets = list || [];
|
|
23
|
+
rebuildLookup();
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function addOrUpdateMarket(market) {
|
|
27
|
+
const idx = markets.findIndex((m) => m.id === market.id);
|
|
28
|
+
if (idx >= 0) {
|
|
29
|
+
markets[idx] = { ...markets[idx], ...market };
|
|
30
|
+
} else {
|
|
31
|
+
markets.push(market);
|
|
32
|
+
}
|
|
33
|
+
rebuildLookup();
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function getMarkets() {
|
|
37
|
+
return markets;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getMarketByCoinKey() {
|
|
41
|
+
return byCoinKey;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Look up a market by two coin IDs (order-independent).
|
|
46
|
+
* @param {string} coinIdA
|
|
47
|
+
* @param {string} coinIdB
|
|
48
|
+
* @returns {Object|undefined}
|
|
49
|
+
*/
|
|
50
|
+
export function getMarketByCoins(coinIdA, coinIdB) {
|
|
51
|
+
return byCoinKey[buildCoinKey(coinIdA, coinIdB)];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function resetMarkets() {
|
|
55
|
+
markets = [];
|
|
56
|
+
byCoinKey = {};
|
|
57
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
const orderbookStore = {
|
|
2
|
+
books: new Map(), // pair -> { bids: [[price, qty], ...], asks: [[price, qty], ...] }
|
|
3
|
+
isInitialReceived: false,
|
|
4
|
+
pendingUpdates: [],
|
|
5
|
+
userOrders: [], // User's open CLOB orders (across all pairs)
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
// Initialize all order books with initial data
|
|
9
|
+
const setInitialOrderBooks = (books) => {
|
|
10
|
+
orderbookStore.books.clear();
|
|
11
|
+
for (const [pair, depth] of Object.entries(books)) {
|
|
12
|
+
orderbookStore.books.set(pair, {
|
|
13
|
+
bids: depth.bids || [],
|
|
14
|
+
asks: depth.asks || [],
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
orderbookStore.isInitialReceived = true;
|
|
18
|
+
|
|
19
|
+
// Process buffered updates
|
|
20
|
+
if (orderbookStore.pendingUpdates.length > 0) {
|
|
21
|
+
orderbookStore.pendingUpdates.forEach(({ pair, bids, asks }) => {
|
|
22
|
+
updateOrderBook(pair, bids, asks);
|
|
23
|
+
});
|
|
24
|
+
orderbookStore.pendingUpdates = [];
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
// Update a single order book
|
|
29
|
+
const updateOrderBook = (pair, bids, asks) => {
|
|
30
|
+
if (!orderbookStore.isInitialReceived) {
|
|
31
|
+
orderbookStore.pendingUpdates.push({ pair, bids, asks });
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
orderbookStore.books.set(pair, {
|
|
35
|
+
bids: bids || [],
|
|
36
|
+
asks: asks || [],
|
|
37
|
+
});
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Get all order books as a plain object { [pair]: { bids, asks } }
|
|
41
|
+
const getAllOrderBooks = () => {
|
|
42
|
+
const result = {};
|
|
43
|
+
for (const [pair, depth] of orderbookStore.books) {
|
|
44
|
+
result[pair] = depth;
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
// Get a specific order book by pair
|
|
50
|
+
const getOrderBook = (pair) => {
|
|
51
|
+
return orderbookStore.books.get(pair) || { bids: [], asks: [] };
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Get all pairs with active order books
|
|
55
|
+
const getOrderBookPairs = () => {
|
|
56
|
+
return [...orderbookStore.books.keys()];
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Set user's open orders (received on connect)
|
|
60
|
+
const setUserOrders = (orders) => {
|
|
61
|
+
orderbookStore.userOrders = orders || [];
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
// Update a single user order (from order_updated event)
|
|
65
|
+
const updateUserOrder = (data) => {
|
|
66
|
+
if (data.refresh) {return;} // caller should refetch
|
|
67
|
+
const order = data.order;
|
|
68
|
+
if (!order) {return;}
|
|
69
|
+
const idx = orderbookStore.userOrders.findIndex((o) => o.id === order.id);
|
|
70
|
+
if (idx >= 0) {
|
|
71
|
+
orderbookStore.userOrders[idx] = { ...orderbookStore.userOrders[idx], ...order };
|
|
72
|
+
} else {
|
|
73
|
+
orderbookStore.userOrders.unshift(order);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
// Get user's open orders
|
|
78
|
+
const getUserOrders = () => orderbookStore.userOrders;
|
|
79
|
+
|
|
80
|
+
// Reset store on disconnect or error
|
|
81
|
+
const resetOrderBooks = () => {
|
|
82
|
+
orderbookStore.books.clear();
|
|
83
|
+
orderbookStore.isInitialReceived = false;
|
|
84
|
+
orderbookStore.pendingUpdates = [];
|
|
85
|
+
orderbookStore.userOrders = [];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
export {
|
|
89
|
+
orderbookStore,
|
|
90
|
+
setInitialOrderBooks,
|
|
91
|
+
updateOrderBook,
|
|
92
|
+
getAllOrderBooks,
|
|
93
|
+
getOrderBook,
|
|
94
|
+
getOrderBookPairs,
|
|
95
|
+
setUserOrders,
|
|
96
|
+
updateUserOrder,
|
|
97
|
+
getUserOrders,
|
|
98
|
+
resetOrderBooks,
|
|
99
|
+
};
|
package/src/store/poolStore.mjs
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/utils/liquidityUtils.mjs
|
|
2
|
-
import { BigNumber } from '
|
|
3
|
-
|
|
2
|
+
import { SafeBigNumber as BigNumber } from './safeBigNumber.mjs';
|
|
4
3
|
import { getRunesPriceUSD, getTokenPriceInRunes } from './swapUtils.mjs';
|
|
5
4
|
|
|
6
5
|
export function normalizeTokenPairFrontend(coinA, coinB, pools) {
|
|
@@ -111,7 +110,7 @@ export function getPoolRatioFrontend(pool) {
|
|
|
111
110
|
return reserveADecimal.div(reserveBDecimal);
|
|
112
111
|
}
|
|
113
112
|
|
|
114
|
-
export function estimateLiquidityFrontend({ coinA, coinB, amountA, amountB, pools
|
|
113
|
+
export function estimateLiquidityFrontend({ coinA, coinB, amountA, amountB, pools }) {
|
|
115
114
|
if ((amountA === null && amountB === null) || (amountA !== null && amountB !== null)) {
|
|
116
115
|
throw new Error('Provide either amountA or amountB, but not both or neither');
|
|
117
116
|
}
|
package/src/utils/priceUtils.mjs
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
// src/utils/priceUtils.mjs
|
|
2
|
-
import { BigNumber } from 'bignumber.js';
|
|
3
2
|
|
|
4
3
|
import { getPools } from '../store/poolStore.mjs';
|
|
5
4
|
import { getCoinByTicker } from '../store/coinStore.mjs';
|
|
6
5
|
|
|
6
|
+
import { SafeBigNumber as BigNumber } from './safeBigNumber.mjs';
|
|
7
|
+
|
|
7
8
|
export function createPriceUtils() {
|
|
8
9
|
const getRunesPriceUSD = () => {
|
|
9
10
|
const pools = getPools();
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// src/utils/safeBigNumber.mjs
|
|
2
|
+
// Isolated BigNumber constructor aligned with the backend's SafeBigNumber
|
|
3
|
+
// (runesx-api/src/utils/safeBigNumber.mjs). Uses the same DECIMAL_PLACES=40
|
|
4
|
+
// and EXPONENTIAL_AT=[-100, 100] configuration to ensure estimation arithmetic
|
|
5
|
+
// matches backend financial calculations.
|
|
6
|
+
import BigNumber from 'bignumber.js';
|
|
7
|
+
|
|
8
|
+
export const SafeBigNumber = BigNumber.clone({
|
|
9
|
+
DECIMAL_PLACES: 40,
|
|
10
|
+
EXPONENTIAL_AT: [-100, 100],
|
|
11
|
+
});
|