@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.
- package/README.md +47 -0
- package/package.json +58 -0
- package/src/api.mjs +50 -0
- package/src/config.mjs +8 -0
- package/src/index.mjs +80 -0
- package/src/socket.mjs +247 -0
- package/src/store/coinStore.mjs +113 -0
- package/src/store/poolStore.mjs +136 -0
- package/src/store/userSharesStore.mjs +90 -0
- package/src/store/walletStore.mjs +96 -0
- package/src/utils/liquidityUtils.mjs +213 -0
- package/src/utils/swapUtils.mjs +447 -0
- package/src/waitForStores.mjs +167 -0
- package/src/workers/swapWorker.mjs +14 -0
package/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# RunesX API Client
|
|
2
|
+
|
|
3
|
+
A Node.js client for interacting with the RunesX platform API and WebSocket, enabling developers to build bots or scripts for real-time trading, liquidity management, and data monitoring.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
- **WebSocket Integration**: Connect to the RunesX Socket.IO server to listen for real-time events like `pools_updated`, `coins_updated`, `wallets_updated`, `user_shares_updated`, and chat messages (`yard_message`).
|
|
7
|
+
- **API Access**: Perform HTTP requests for wallet data, swaps, and liquidity operations.
|
|
8
|
+
- **Store Management**: Manage coin, pool, wallet, and user share data with built-in buffering for WebSocket updates.
|
|
9
|
+
- **Swap Estimation**: Estimate swap outcomes using DFS or BFS pathfinding algorithms.
|
|
10
|
+
- **Liquidity Management**: Estimate, deposit, and withdraw liquidity for trading pairs.
|
|
11
|
+
- **Monitoring**: Periodically monitor pool, wallet, and user share states.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
```bash
|
|
15
|
+
npm install @runesx/api-client
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Usage
|
|
19
|
+
|
|
20
|
+
1. Initialize the Client
|
|
21
|
+
Create and initialize the client with your API key and endpoint URLs. This connects to the WebSocket and fetches initial data for pools, coins, wallets, and user shares.
|
|
22
|
+
|
|
23
|
+
```javascript
|
|
24
|
+
import { createRunesXClient } from '@runesx/api-client';
|
|
25
|
+
|
|
26
|
+
const client = createRunesXClient({
|
|
27
|
+
apiKey: 'your-api-key',
|
|
28
|
+
apiUrl: 'https://www.runesx.xyz/api',
|
|
29
|
+
socketUrl: 'https://www.runesx.xyz/api',
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
async function main() {
|
|
33
|
+
try {
|
|
34
|
+
const { pools, coins, wallets, userShares } = await client.initialize();
|
|
35
|
+
console.log('Initialized with:', {
|
|
36
|
+
poolCount: pools.length,
|
|
37
|
+
coinCount: coins.length,
|
|
38
|
+
walletCount: wallets.length,
|
|
39
|
+
userShareCount: userShares.length,
|
|
40
|
+
});
|
|
41
|
+
} catch (error) {
|
|
42
|
+
console.error('Initialization failed:', error.message);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
main();
|
|
47
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@runesx/api-client",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "A Node.js client for interacting with the RunesX platform API and WebSocket",
|
|
5
|
+
"main": "src/index.mjs",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node src/index.mjs",
|
|
9
|
+
"dev": "nodemon src/index.mjs",
|
|
10
|
+
"lint": "eslint src",
|
|
11
|
+
"build": "echo 'No build step required'",
|
|
12
|
+
"prepublishOnly": "npm run lint",
|
|
13
|
+
"test": "jest"
|
|
14
|
+
},
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"axios": "^1.7.7",
|
|
17
|
+
"bignumber.js": "^9.1.2",
|
|
18
|
+
"socket.io-client": "^4.7.5"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@eslint/js": "^9.11.1",
|
|
22
|
+
"@semantic-release/changelog": "^6.0.3",
|
|
23
|
+
"@semantic-release/git": "^10.0.1",
|
|
24
|
+
"@semantic-release/npm": "^11.0.3",
|
|
25
|
+
"dotenv": "^16.4.5",
|
|
26
|
+
"eslint": "^9.11.1",
|
|
27
|
+
"eslint-plugin-import": "^2.30.0",
|
|
28
|
+
"eslint-plugin-n": "^17.0.0",
|
|
29
|
+
"eslint-plugin-promise": "^7.1.0",
|
|
30
|
+
"globals": "^15.9.0",
|
|
31
|
+
"jest": "^30.0.4",
|
|
32
|
+
"nodemon": "^3.1.4",
|
|
33
|
+
"semantic-release": "^22.0.12"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"src/*.mjs",
|
|
37
|
+
"src/store/*.mjs",
|
|
38
|
+
"src/utils/*.mjs",
|
|
39
|
+
"src/workers/*.mjs"
|
|
40
|
+
],
|
|
41
|
+
"keywords": [
|
|
42
|
+
"runesx",
|
|
43
|
+
"api",
|
|
44
|
+
"websocket",
|
|
45
|
+
"cryptocurrency",
|
|
46
|
+
"defi"
|
|
47
|
+
],
|
|
48
|
+
"author": "RunesX",
|
|
49
|
+
"license": "MIT",
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/runebase/runesx-api-key-example.git"
|
|
53
|
+
},
|
|
54
|
+
"publishConfig": {
|
|
55
|
+
"access": "public",
|
|
56
|
+
"registry": "https://registry.npmjs.org/"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/src/api.mjs
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// src/api.mjs
|
|
2
|
+
import axios from 'axios';
|
|
3
|
+
|
|
4
|
+
export function createApi(config) {
|
|
5
|
+
const api = axios.create({
|
|
6
|
+
baseURL: config.apiUrl,
|
|
7
|
+
headers: {
|
|
8
|
+
Authorization: `Bearer ${config.apiKey}`,
|
|
9
|
+
'Content-Type': 'application/json',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
async function getWallets() {
|
|
14
|
+
try {
|
|
15
|
+
const response = await api.get('/wallets');
|
|
16
|
+
return response.data;
|
|
17
|
+
} catch (error) {
|
|
18
|
+
throw new Error(error.response?.data?.error || 'Failed to fetch wallets');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async function postSwap({ amountIn, path, minAmountOut }) {
|
|
23
|
+
try {
|
|
24
|
+
const response = await api.post('/swap', { amountIn, path, minAmountOut });
|
|
25
|
+
return response.data;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
throw new Error(error.response?.data?.error || 'Failed to execute swap');
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function depositLiquidity({ coinA, coinB, amountA, amountB }) {
|
|
32
|
+
try {
|
|
33
|
+
const response = await api.post('/liquidity/deposit', { coinA, coinB, amountA, amountB });
|
|
34
|
+
return response.data.data;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
throw new Error(error.response?.data?.error || 'Failed to deposit liquidity');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function withdrawLiquidity({ coinA, coinB, shares }) {
|
|
41
|
+
try {
|
|
42
|
+
const response = await api.post('/liquidity/withdraw', { coinA, coinB, shares });
|
|
43
|
+
return response.data.data;
|
|
44
|
+
} catch (error) {
|
|
45
|
+
throw new Error(error.response?.data?.error || 'Failed to withdraw liquidity');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return { getWallets, postSwap, depositLiquidity, withdrawLiquidity };
|
|
50
|
+
}
|
package/src/config.mjs
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// src/config.mjs
|
|
2
|
+
export function createConfig(options = {}) {
|
|
3
|
+
return {
|
|
4
|
+
apiUrl: options.apiUrl || process.env.API_URL || 'http://localhost:3010',
|
|
5
|
+
socketUrl: options.socketUrl || process.env.SOCKET_URL || 'http://localhost:3010',
|
|
6
|
+
apiKey: options.apiKey || process.env.API_KEY || '',
|
|
7
|
+
};
|
|
8
|
+
}
|
package/src/index.mjs
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/index.mjs
|
|
2
|
+
import { setupSocket } from './socket.mjs';
|
|
3
|
+
import { createApi } from './api.mjs';
|
|
4
|
+
import { createConfig } from './config.mjs';
|
|
5
|
+
import { getPools, getPool } from './store/poolStore.mjs';
|
|
6
|
+
import { getCoins, getCoinByTicker } from './store/coinStore.mjs';
|
|
7
|
+
import { getWallets as getWalletsStore, getWalletByTicker } from './store/walletStore.mjs';
|
|
8
|
+
import { getUserShares, getUserShareByPoolId } from './store/userSharesStore.mjs';
|
|
9
|
+
import { waitForStores } from './waitForStores.mjs';
|
|
10
|
+
import { estimateLiquidityFrontend, checkRunesLiquidityFrontend, calculateShareAmounts } from './utils/liquidityUtils.mjs';
|
|
11
|
+
import { estimateSwap } from './utils/swapUtils.mjs';
|
|
12
|
+
|
|
13
|
+
export function createRunesXClient(options = {}) {
|
|
14
|
+
const config = createConfig(options);
|
|
15
|
+
let socket = null;
|
|
16
|
+
let initialized = false;
|
|
17
|
+
const api = createApi(config);
|
|
18
|
+
|
|
19
|
+
async function initialize() {
|
|
20
|
+
if (!config.apiKey) {
|
|
21
|
+
throw new Error('API_KEY is required');
|
|
22
|
+
}
|
|
23
|
+
socket = setupSocket(config).socket;
|
|
24
|
+
const { pools, coins, wallets, userShares } = await waitForStores(socket);
|
|
25
|
+
initialized = true;
|
|
26
|
+
return { pools, coins, wallets, userShares };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function ensureInitialized() {
|
|
30
|
+
if (!initialized) {
|
|
31
|
+
throw new Error('Client not initialized. Call initialize() first.');
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return {
|
|
36
|
+
initialize,
|
|
37
|
+
getSocket: () => {
|
|
38
|
+
ensureInitialized();
|
|
39
|
+
return socket;
|
|
40
|
+
},
|
|
41
|
+
getPools,
|
|
42
|
+
getPool,
|
|
43
|
+
getCoins,
|
|
44
|
+
getCoinByTicker,
|
|
45
|
+
getWallets: getWalletsStore,
|
|
46
|
+
getWalletByTicker,
|
|
47
|
+
getUserShares,
|
|
48
|
+
getUserShareByPoolId,
|
|
49
|
+
postSwap: api.postSwap,
|
|
50
|
+
depositLiquidity: api.depositLiquidity,
|
|
51
|
+
withdrawLiquidity: api.withdrawLiquidity,
|
|
52
|
+
getWalletsApi: api.getWallets,
|
|
53
|
+
estimateSwap: (inputCoin, outputCoin, amountIn, maxHops = 6, algorithm = 'dfs') =>
|
|
54
|
+
estimateSwap(inputCoin, outputCoin, amountIn, getPools(), getCoins(), maxHops, algorithm),
|
|
55
|
+
estimateLiquidityFrontend,
|
|
56
|
+
checkRunesLiquidityFrontend: (coinA, coinB) =>
|
|
57
|
+
checkRunesLiquidityFrontend(coinA, coinB, getPools(), getCoins()),
|
|
58
|
+
calculateShareAmounts: () => calculateShareAmounts({ userShares: getUserShares(), pools: getPools() }),
|
|
59
|
+
monitorPool: (poolId, interval = 10000) => {
|
|
60
|
+
setInterval(() => {
|
|
61
|
+
const pool = getPool(poolId);
|
|
62
|
+
if (pool) {
|
|
63
|
+
console.log(`Monitoring pool ${poolId} (${pool.coinA.ticker}/${pool.coinB.ticker}):`, {
|
|
64
|
+
reserveA: pool.reserveA.toString(),
|
|
65
|
+
reserveB: pool.reserveB.toString(),
|
|
66
|
+
totalShares: pool.totalShares.toString(),
|
|
67
|
+
activeLiquidityProviders: pool.activeLiquidityProviders,
|
|
68
|
+
});
|
|
69
|
+
} else {
|
|
70
|
+
console.log(`Pool ${poolId} not found or not yet initialized`);
|
|
71
|
+
}
|
|
72
|
+
}, interval);
|
|
73
|
+
},
|
|
74
|
+
disconnect: () => {
|
|
75
|
+
if (socket) {
|
|
76
|
+
socket.disconnect();
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/src/socket.mjs
ADDED
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
// src/socket.mjs
|
|
2
|
+
import { io } from 'socket.io-client';
|
|
3
|
+
|
|
4
|
+
import { setInitialPools, updatePool, resetPools } from './store/poolStore.mjs';
|
|
5
|
+
import { setInitialCoins, updateCoin, resetCoins } from './store/coinStore.mjs';
|
|
6
|
+
import { setInitialWallets, updateWallet, resetWallets } from './store/walletStore.mjs';
|
|
7
|
+
import { setInitialUserShares, updateUserShare, resetUserShares } from './store/userSharesStore.mjs';
|
|
8
|
+
|
|
9
|
+
export function setupSocket(config) {
|
|
10
|
+
const socket = io(config.socketUrl, {
|
|
11
|
+
auth: { authorization: `Bearer ${config.apiKey}` },
|
|
12
|
+
transports: ['websocket'],
|
|
13
|
+
reconnection: true,
|
|
14
|
+
reconnectionAttempts: Infinity,
|
|
15
|
+
reconnectionDelay: 1000,
|
|
16
|
+
reconnectionDelayMax: 5000,
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
const errorCount = { count: 0 }; // Track connection errors
|
|
20
|
+
|
|
21
|
+
socket.on('connect', () => {
|
|
22
|
+
console.log('Connected to Socket.IO server');
|
|
23
|
+
socket.emit('join_public');
|
|
24
|
+
console.log('Joined public room');
|
|
25
|
+
socket.emit('join_private');
|
|
26
|
+
console.log('Joined private room');
|
|
27
|
+
errorCount.count = 0;
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
socket.on('connect_error', (err) => {
|
|
31
|
+
console.log('Socket connect error:', err.message);
|
|
32
|
+
errorCount.count += 1;
|
|
33
|
+
if (errorCount.count >= 3) {
|
|
34
|
+
resetPools();
|
|
35
|
+
resetCoins();
|
|
36
|
+
resetWallets();
|
|
37
|
+
resetUserShares();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
socket.on('disconnect', (reason) => {
|
|
42
|
+
console.log('Disconnected from Socket.IO server:', reason);
|
|
43
|
+
resetPools();
|
|
44
|
+
resetCoins();
|
|
45
|
+
resetWallets();
|
|
46
|
+
resetUserShares();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
socket.on('reconnect_attempt', (attempt) => {
|
|
50
|
+
console.log(`Reconnect attempt #${attempt}`);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
socket.on('reconnect', () => {
|
|
54
|
+
console.log('Reconnected to Socket.IO server');
|
|
55
|
+
socket.emit('join_public');
|
|
56
|
+
console.log('Rejoined public room');
|
|
57
|
+
socket.emit('join_private');
|
|
58
|
+
console.log('Rejoined private room');
|
|
59
|
+
resetPools();
|
|
60
|
+
resetCoins();
|
|
61
|
+
resetWallets();
|
|
62
|
+
resetUserShares();
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
socket.on('reconnect_error', (err) => {
|
|
66
|
+
console.log('Reconnect error:', err.message);
|
|
67
|
+
errorCount.count += 1;
|
|
68
|
+
if (errorCount.count >= 3) {
|
|
69
|
+
resetPools();
|
|
70
|
+
resetCoins();
|
|
71
|
+
resetWallets();
|
|
72
|
+
resetUserShares();
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
socket.on('error', (err) => {
|
|
77
|
+
console.log('Socket error:', err.message);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
socket.on('pools_updated', ({ pools, isInitial }) => {
|
|
81
|
+
console.log('Received pools_updated:', { pools, isInitial });
|
|
82
|
+
if (isInitial) {
|
|
83
|
+
setInitialPools(pools);
|
|
84
|
+
} else {
|
|
85
|
+
pools.forEach(pool => updatePool(pool));
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
socket.on('coins_updated', ({ coins, isInitial }) => {
|
|
90
|
+
console.log('Received coins_updated:', { coins, isInitial });
|
|
91
|
+
if (isInitial) {
|
|
92
|
+
setInitialCoins(coins);
|
|
93
|
+
} else {
|
|
94
|
+
coins.forEach(coin => updateCoin(coin));
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
socket.on('wallets_updated', ({ wallets, isInitial }) => {
|
|
99
|
+
console.log('Received wallets_updated:', { wallets, isInitial });
|
|
100
|
+
if (isInitial) {
|
|
101
|
+
setInitialWallets(wallets);
|
|
102
|
+
} else {
|
|
103
|
+
wallets.forEach(wallet => updateWallet(wallet));
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
socket.on('user_shares_updated', ({ userShares, isInitial }) => {
|
|
108
|
+
console.log('Received user_shares_updated:', { userShares, isInitial });
|
|
109
|
+
if (isInitial) {
|
|
110
|
+
setInitialUserShares(userShares);
|
|
111
|
+
} else {
|
|
112
|
+
userShares.forEach(share => updateUserShare(share));
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
socket.on('volumeUpdate', ({ type, poolId, timestamp, volume }) => {
|
|
117
|
+
console.log('Volume update:', { type, poolId, timestamp, volume });
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
socket.on('operationUpdate', (operation) => {
|
|
121
|
+
console.log('Operation update:', operation);
|
|
122
|
+
// Assuming operation includes liquidity deposit/withdraw results
|
|
123
|
+
if (operation.type === 'liquidity_deposit' || operation.type === 'liquidity_withdraw') {
|
|
124
|
+
console.log(`Liquidity operation ${operation.type} completed:`, {
|
|
125
|
+
uid: operation.uid,
|
|
126
|
+
coinA: operation.coinA,
|
|
127
|
+
coinB: operation.coinB,
|
|
128
|
+
amountA: operation.amountA,
|
|
129
|
+
amountB: operation.amountB,
|
|
130
|
+
shares: operation.shares,
|
|
131
|
+
status: operation.status,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
socket.on('status_updated', (data) => {
|
|
136
|
+
console.log('Status update:', data);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
socket.on('deposit_address_generated', ({ requestId, chainId, address, memo }) => {
|
|
140
|
+
console.log('Deposit address generated:', { requestId, chainId, address, memo });
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
socket.on('deposit_processed', (data) => {
|
|
144
|
+
const { amount, coin, chain, confirmations, status, credited } = data;
|
|
145
|
+
const message =
|
|
146
|
+
status === 'confirmed' && credited
|
|
147
|
+
? `Deposit of ${amount} ${coin.ticker} confirmed!`
|
|
148
|
+
: `Deposit of ${amount} ${coin.ticker} (confirming) [${confirmations}/${chain.requiredConfirmations}]`;
|
|
149
|
+
console.log('Deposit processed:', { message, data });
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
socket.on('withdrawal_processed', (data) => {
|
|
153
|
+
const { amount, coin, chain, confirmations, status, credited, createdAt } = data;
|
|
154
|
+
let message;
|
|
155
|
+
if (!createdAt) {
|
|
156
|
+
message = `Withdrawal of ${amount} ${coin.ticker} is stalled due to network congestion.`;
|
|
157
|
+
} else if (status === 'confirmed' && credited) {
|
|
158
|
+
message = `Withdrawal of ${amount} ${coin.ticker} confirmed!`;
|
|
159
|
+
} else {
|
|
160
|
+
message = `Withdrawal of ${amount} ${coin.ticker} (confirming) [${confirmations}/${chain.requiredConfirmations}]`;
|
|
161
|
+
}
|
|
162
|
+
console.log('Withdrawal processed:', { message, data });
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
socket.on('withdrawal_initiated', (data) => {
|
|
166
|
+
console.log('Withdrawal initiated:', data);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
socket.on('withdrawal_pin_generated', ({ pinImage, ticker, amount, pendingWithdrawalId, expiresAt, dp, fee, memoRequired }) => {
|
|
170
|
+
console.log('Withdrawal pin generated:', {
|
|
171
|
+
ticker,
|
|
172
|
+
amount,
|
|
173
|
+
pinImage,
|
|
174
|
+
pendingWithdrawalId,
|
|
175
|
+
expiresAt,
|
|
176
|
+
dp,
|
|
177
|
+
fee,
|
|
178
|
+
memoRequired,
|
|
179
|
+
});
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
socket.on('withdrawal_queued', ({ pendingWithdrawalId, ticker }) => {
|
|
183
|
+
console.log('Withdrawal queued:', { pendingWithdrawalId, ticker });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
socket.on('withdrawal_canceled', ({ ticker, amount }) => {
|
|
187
|
+
console.log('Withdrawal canceled:', { ticker, amount });
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
// socket.on('recent_yard_messages', (initialMessages) => {
|
|
191
|
+
// console.log('Recent yard messages:', initialMessages);
|
|
192
|
+
// });
|
|
193
|
+
|
|
194
|
+
socket.on('yard_message', (message) => {
|
|
195
|
+
console.log('New chat message:', {
|
|
196
|
+
username: message.username,
|
|
197
|
+
text: message.text,
|
|
198
|
+
role: message.role,
|
|
199
|
+
timestamp: new Date(message.timestamp).toLocaleString(),
|
|
200
|
+
});
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
socket.on('message_deleted', ({ messageId }) => {
|
|
204
|
+
console.log('Message deleted:', { messageId });
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
socket.on('banned', ({ reason, bannedUntil }) => {
|
|
208
|
+
console.log('Banned from yard:', {
|
|
209
|
+
reason,
|
|
210
|
+
bannedUntil: new Date(bannedUntil).toLocaleString(),
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
socket.on('withdrawal_updated', ({ pendingWithdrawalId, expiresAt, stage }) => {
|
|
215
|
+
console.log('Withdrawal updated:', { pendingWithdrawalId, expiresAt, stage });
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
socket.on('withdrawal_expired', ({ pendingWithdrawalId, ticker, amount }) => {
|
|
219
|
+
console.log('Withdrawal expired:', { pendingWithdrawalId, ticker, amount });
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
socket.on('pong', () => {
|
|
223
|
+
console.log('Received pong from server');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const heartbeatInterval = setInterval(() => {
|
|
227
|
+
if (socket.connected) {
|
|
228
|
+
socket.emit('ping');
|
|
229
|
+
}
|
|
230
|
+
}, 30000);
|
|
231
|
+
|
|
232
|
+
socket.on('close', () => {
|
|
233
|
+
clearInterval(heartbeatInterval);
|
|
234
|
+
resetPools();
|
|
235
|
+
resetCoins();
|
|
236
|
+
resetWallets();
|
|
237
|
+
resetUserShares();
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
socket.on('connect', () => {
|
|
241
|
+
socket.emit('yard_message', {
|
|
242
|
+
text: 'Hello from RunesX API Key Example Bot!!',
|
|
243
|
+
});
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
return { socket };
|
|
247
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { BigNumber } from 'bignumber.js';
|
|
2
|
+
|
|
3
|
+
const coinStore = {
|
|
4
|
+
coins: new Map(), // Store coin data by coin ID
|
|
5
|
+
isInitialReceived: false, // Track if initial coin data is received
|
|
6
|
+
pendingUpdates: [], // Buffer for updates before initial data
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// Initialize coins with initial data
|
|
10
|
+
const setInitialCoins = (coins) => {
|
|
11
|
+
coinStore.coins.clear();
|
|
12
|
+
coins.forEach(coin => {
|
|
13
|
+
coinStore.coins.set(coin.id, {
|
|
14
|
+
id: coin.id,
|
|
15
|
+
ticker: coin.ticker,
|
|
16
|
+
dp: coin.dp,
|
|
17
|
+
projectName: coin.projectName,
|
|
18
|
+
status: coin.status,
|
|
19
|
+
runesComplianceRequirement: coin.runesComplianceRequirement || false,
|
|
20
|
+
updatedAt: coin.updatedAt,
|
|
21
|
+
CoinChains: coin.CoinChains || [],
|
|
22
|
+
});
|
|
23
|
+
});
|
|
24
|
+
coinStore.isInitialReceived = true;
|
|
25
|
+
console.log(`Initialized with ${coins.length} coins`);
|
|
26
|
+
|
|
27
|
+
// Process buffered updates
|
|
28
|
+
if (coinStore.pendingUpdates.length > 0) {
|
|
29
|
+
console.log(`Processing ${coinStore.pendingUpdates.length} buffered coin updates`);
|
|
30
|
+
coinStore.pendingUpdates.forEach(({ coins }) => {
|
|
31
|
+
coins.forEach(coin => updateCoin(coin));
|
|
32
|
+
});
|
|
33
|
+
coinStore.pendingUpdates = [];
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Update a single coin
|
|
38
|
+
const updateCoin = (coin) => {
|
|
39
|
+
if (!coinStore.isInitialReceived) {
|
|
40
|
+
console.log('Buffering coin update, initial data not yet received:', coin);
|
|
41
|
+
coinStore.pendingUpdates.push({ coins: [coin] });
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const existingCoin = coinStore.coins.get(coin.id);
|
|
46
|
+
const incomingUpdatedAt = new Date(coin.updatedAt).getTime();
|
|
47
|
+
|
|
48
|
+
if (existingCoin) {
|
|
49
|
+
const existingUpdatedAt = new Date(existingCoin.updatedAt).getTime();
|
|
50
|
+
if (incomingUpdatedAt <= existingUpdatedAt) {
|
|
51
|
+
console.log(`Skipping stale update for coin ${coin.id}`);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log(`Updating coin ${coin.id} (${coin.ticker}):`, {
|
|
56
|
+
ticker: coin.ticker,
|
|
57
|
+
dp: coin.dp,
|
|
58
|
+
projectName: coin.projectName,
|
|
59
|
+
status: coin.status,
|
|
60
|
+
runesComplianceRequirement: coin.runesComplianceRequirement,
|
|
61
|
+
CoinChains: coin.CoinChains,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
Object.assign(existingCoin, {
|
|
65
|
+
ticker: coin.ticker,
|
|
66
|
+
dp: coin.dp,
|
|
67
|
+
projectName: coin.projectName,
|
|
68
|
+
status: coin.status,
|
|
69
|
+
runesComplianceRequirement: coin.runesComplianceRequirement || false,
|
|
70
|
+
updatedAt: coin.updatedAt,
|
|
71
|
+
CoinChains: coin.CoinChains || existingCoin.CoinChains,
|
|
72
|
+
});
|
|
73
|
+
} else if (coin.ticker && coin.status && new BigNumber(coin.dp || 0).gte(0)) {
|
|
74
|
+
console.log(`Adding new coin ${coin.id}:`, coin);
|
|
75
|
+
coinStore.coins.set(coin.id, {
|
|
76
|
+
id: coin.id,
|
|
77
|
+
ticker: coin.ticker,
|
|
78
|
+
dp: coin.dp,
|
|
79
|
+
projectName: coin.projectName,
|
|
80
|
+
status: coin.status,
|
|
81
|
+
runesComplianceRequirement: coin.runesComplianceRequirement || false,
|
|
82
|
+
updatedAt: coin.updatedAt,
|
|
83
|
+
CoinChains: coin.CoinChains || [],
|
|
84
|
+
});
|
|
85
|
+
} else {
|
|
86
|
+
console.warn(`Ignoring update for unknown coin ${coin.id} with incomplete data`);
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
// Get all coins
|
|
91
|
+
const getCoins = () => {
|
|
92
|
+
return Array.from(coinStore.coins.values());
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Get a specific coin by ID
|
|
96
|
+
const getCoin = (coinId) => {
|
|
97
|
+
return coinStore.coins.get(coinId);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Get a specific coin by ticker
|
|
101
|
+
const getCoinByTicker = (ticker) => {
|
|
102
|
+
return Array.from(coinStore.coins.values()).find(coin => coin.ticker === ticker);
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// Reset store on disconnect or error
|
|
106
|
+
const resetCoins = () => {
|
|
107
|
+
coinStore.coins.clear();
|
|
108
|
+
coinStore.isInitialReceived = false;
|
|
109
|
+
coinStore.pendingUpdates = [];
|
|
110
|
+
console.log('Reset coin state due to disconnect or error');
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
export { coinStore, setInitialCoins, updateCoin, getCoins, getCoin, getCoinByTicker, resetCoins };
|