jaelis-node 1.8.0 → 1.9.0
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 +54 -1
- package/bin/jaelis-node.js +16 -3
- package/lib/index.js +219 -44
- package/package.json +4 -3
package/README.md
CHANGED
|
@@ -169,7 +169,24 @@ Options:
|
|
|
169
169
|
|
|
170
170
|
## Block Sync & Node Status
|
|
171
171
|
|
|
172
|
-
Your node automatically syncs with the JAELIS network:
|
|
172
|
+
Your node automatically syncs with the JAELIS network using **WebSocket push notifications** (like Ethereum's `newHeads` subscription) for real-time updates:
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
[SYNC] Starting sync from https://rpc.jaelis.io
|
|
176
|
+
[SYNC] Syncing 25 blocks (1 → 25)
|
|
177
|
+
[SYNC] Local height: 25/139
|
|
178
|
+
[SYNC] Syncing 25 blocks (26 → 50)
|
|
179
|
+
...
|
|
180
|
+
[SYNC] Local height: 139/139
|
|
181
|
+
[SYNC] WebSocket connected! Subscribing to newHeads...
|
|
182
|
+
[SYNC] ✓ Subscribed to newHeads (id: 0x1)
|
|
183
|
+
[SYNC] Listening for new blocks via WebSocket...
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Sync Features:**
|
|
187
|
+
- **Smart batching** - 25 blocks during initial sync, reduces as you get closer
|
|
188
|
+
- **WebSocket subscriptions** - Real-time block notifications (no polling!)
|
|
189
|
+
- **Automatic fallback** - Falls back to HTTP polling if WebSocket unavailable
|
|
173
190
|
|
|
174
191
|
```bash
|
|
175
192
|
# Check sync status
|
|
@@ -215,6 +232,42 @@ curl -X POST https://rpc.jaelis.io \
|
|
|
215
232
|
}
|
|
216
233
|
```
|
|
217
234
|
|
|
235
|
+
## WebSocket Subscriptions
|
|
236
|
+
|
|
237
|
+
Connect to the WebSocket endpoint for real-time updates:
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
const WebSocket = require('ws');
|
|
241
|
+
const ws = new WebSocket('wss://rpc.jaelis.io/ws');
|
|
242
|
+
|
|
243
|
+
ws.on('open', () => {
|
|
244
|
+
// Subscribe to new block headers
|
|
245
|
+
ws.send(JSON.stringify({
|
|
246
|
+
jsonrpc: '2.0',
|
|
247
|
+
method: 'jaelis_subscribe',
|
|
248
|
+
params: ['newHeads'],
|
|
249
|
+
id: 1
|
|
250
|
+
}));
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
ws.on('message', (data) => {
|
|
254
|
+
const msg = JSON.parse(data);
|
|
255
|
+
if (msg.method === 'jaelis_subscription') {
|
|
256
|
+
console.log('New block:', msg.params.result.number);
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
**Available Subscriptions:**
|
|
262
|
+
| Subscription | Description |
|
|
263
|
+
|--------------|-------------|
|
|
264
|
+
| `newHeads` | New block headers as they're produced (real-time sync) |
|
|
265
|
+
| `newPendingTransactions` | Transaction hashes entering mempool |
|
|
266
|
+
| `logs` | Contract event logs (with filter options) |
|
|
267
|
+
| `syncing` | Sync status changes |
|
|
268
|
+
|
|
269
|
+
> **Note:** JAELIS uses Proof of Efficient Consensus (PoEC), not mining. Blocks are produced by validators, not mined. The `newHeads` subscription keeps your node synced in real-time.
|
|
270
|
+
|
|
218
271
|
## RPC Methods
|
|
219
272
|
|
|
220
273
|
Your node exposes standard JSON-RPC plus JAELIS-specific methods:
|
package/bin/jaelis-node.js
CHANGED
|
@@ -36,16 +36,29 @@ const BANNER = `
|
|
|
36
36
|
`;
|
|
37
37
|
|
|
38
38
|
// Network configurations
|
|
39
|
+
// Bootstrap nodes use libp2p multiaddr format: /dns4/host/tcp/port/p2p/PEER_ID
|
|
40
|
+
//
|
|
41
|
+
// IMPORTANT: libp2p requires peer ID for direct dialing!
|
|
42
|
+
// Format: /dns4/rpc.jaelis.io/tcp/30304/p2p/12D3KooW...
|
|
43
|
+
//
|
|
44
|
+
// How it works (like Ethereum):
|
|
45
|
+
// 1. Main node generates persistent peer ID on first run
|
|
46
|
+
// 2. Peer ID is saved to disk (jaelis-data/peer-id.json)
|
|
47
|
+
// 3. Main node logs bootstrap multiaddr on startup
|
|
48
|
+
// 4. Update bootstrapNodes here with that multiaddr
|
|
49
|
+
//
|
|
50
|
+
// If no bootstrap nodes configured, RPC fallback sync is used automatically
|
|
39
51
|
const NETWORKS = {
|
|
40
52
|
testnet: {
|
|
41
53
|
chainId: 4545,
|
|
42
54
|
name: 'JAELIS Testnet',
|
|
43
55
|
symbol: 'tJAELIS',
|
|
44
56
|
rpcUrl: 'https://rpc.jaelis.io',
|
|
45
|
-
//
|
|
46
|
-
//
|
|
57
|
+
// Testnet P2P port: 30304 (mainnet: 30303)
|
|
58
|
+
// Bootstrap nodes will be added once main node generates peer ID
|
|
59
|
+
// Until then, nodes sync via RPC fallback (works perfectly)
|
|
47
60
|
bootstrapNodes: [
|
|
48
|
-
'/dns4/rpc.jaelis.io/tcp/
|
|
61
|
+
// Will be: '/dns4/rpc.jaelis.io/tcp/30304/p2p/PEER_ID'
|
|
49
62
|
]
|
|
50
63
|
},
|
|
51
64
|
mainnet: {
|
package/lib/index.js
CHANGED
|
@@ -333,6 +333,12 @@ class NodeWalletConfig {
|
|
|
333
333
|
}
|
|
334
334
|
|
|
335
335
|
// JAELIS Network Endpoints - users connect here automatically
|
|
336
|
+
// Bootstrap nodes use libp2p multiaddr format: /dns4/host/tcp/port/p2p/PEER_ID
|
|
337
|
+
// External nodes connect via DNS which resolves to the main node's public IP
|
|
338
|
+
//
|
|
339
|
+
// IMPORTANT: Peer ID is required for libp2p dialing!
|
|
340
|
+
// The peer ID is generated on first run and persisted to disk.
|
|
341
|
+
// Main node logs bootstrap info on startup - update this when it changes.
|
|
336
342
|
const JAELIS_NETWORKS = {
|
|
337
343
|
testnet: {
|
|
338
344
|
name: 'JAELIS Testnet',
|
|
@@ -341,8 +347,12 @@ const JAELIS_NETWORKS = {
|
|
|
341
347
|
rpcUrl: 'https://rpc.jaelis.io',
|
|
342
348
|
wsUrl: 'wss://rpc.jaelis.io/ws',
|
|
343
349
|
explorerUrl: 'https://explorer.jaelis.io',
|
|
350
|
+
// Testnet uses port 30304 (mainnet uses 30303)
|
|
351
|
+
// Peer ID will be added after main node first run
|
|
352
|
+
// For now, P2P will fallback to RPC sync until peer ID is configured
|
|
344
353
|
bootstrapNodes: [
|
|
345
|
-
|
|
354
|
+
// Format: /dns4/rpc.jaelis.io/tcp/30304/p2p/PEER_ID
|
|
355
|
+
// The main node outputs this on startup - update here when available
|
|
346
356
|
]
|
|
347
357
|
},
|
|
348
358
|
mainnet: {
|
|
@@ -1204,8 +1214,9 @@ class EmbeddedBlockchain {
|
|
|
1204
1214
|
}
|
|
1205
1215
|
}
|
|
1206
1216
|
|
|
1207
|
-
class EmbeddedNetwork {
|
|
1217
|
+
class EmbeddedNetwork extends EventEmitter {
|
|
1208
1218
|
constructor(options = {}) {
|
|
1219
|
+
super();
|
|
1209
1220
|
this.port = options.port || 30303;
|
|
1210
1221
|
this.host = options.host || '0.0.0.0';
|
|
1211
1222
|
this.maxPeers = options.maxPeers || 50;
|
|
@@ -1221,16 +1232,24 @@ class EmbeddedNetwork {
|
|
|
1221
1232
|
}
|
|
1222
1233
|
|
|
1223
1234
|
async start() {
|
|
1224
|
-
//
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1235
|
+
// ALWAYS start RPC sync first - this is reliable through Cloudflare
|
|
1236
|
+
// P2P is optional/additional for peer discovery
|
|
1237
|
+
console.log(`[NETWORK] Starting RPC sync from ${this.remoteRpc}`);
|
|
1238
|
+
await this._startRPCFallback();
|
|
1239
|
+
this.isRPCFallback = true;
|
|
1240
|
+
|
|
1241
|
+
// Try P2P alongside (optional - for peer discovery when bootstrap nodes available)
|
|
1242
|
+
if (this.bootstrapNodes && this.bootstrapNodes.length > 0) {
|
|
1243
|
+
try {
|
|
1244
|
+
await this._startLibp2p();
|
|
1245
|
+
this.isP2PConnected = true;
|
|
1246
|
+
console.log(`[NETWORK] P2P network also started on port ${this.port}`);
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
console.log(`[NETWORK] P2P unavailable (${err.message}), continuing with RPC only`);
|
|
1249
|
+
}
|
|
1250
|
+
} else {
|
|
1251
|
+
console.log(`[NETWORK] No bootstrap nodes configured - using RPC sync only`);
|
|
1252
|
+
console.log(`[NETWORK] This is fine! RPC sync is reliable and works through firewalls.`);
|
|
1234
1253
|
}
|
|
1235
1254
|
}
|
|
1236
1255
|
|
|
@@ -1296,7 +1315,8 @@ class EmbeddedNetwork {
|
|
|
1296
1315
|
remoteHeight: 0,
|
|
1297
1316
|
syncing: false,
|
|
1298
1317
|
lastSync: null,
|
|
1299
|
-
blocksDownloaded: 0
|
|
1318
|
+
blocksDownloaded: 0,
|
|
1319
|
+
mode: 'polling' // 'websocket' or 'polling'
|
|
1300
1320
|
};
|
|
1301
1321
|
|
|
1302
1322
|
// Helper to make RPC calls to remote node
|
|
@@ -1322,7 +1342,7 @@ class EmbeddedNetwork {
|
|
|
1322
1342
|
headers: {
|
|
1323
1343
|
'Content-Type': 'application/json',
|
|
1324
1344
|
'Content-Length': Buffer.byteLength(data),
|
|
1325
|
-
'User-Agent': 'JAELIS-Node/1.
|
|
1345
|
+
'User-Agent': 'JAELIS-Node/1.8.0' // Node sync traffic
|
|
1326
1346
|
}
|
|
1327
1347
|
}, (res) => {
|
|
1328
1348
|
let body = '';
|
|
@@ -1342,44 +1362,52 @@ class EmbeddedNetwork {
|
|
|
1342
1362
|
});
|
|
1343
1363
|
};
|
|
1344
1364
|
|
|
1345
|
-
//
|
|
1346
|
-
const
|
|
1347
|
-
|
|
1365
|
+
// Fetch and add a specific block
|
|
1366
|
+
const fetchBlock = async (blockNum) => {
|
|
1367
|
+
const blockHex = '0x' + blockNum.toString(16);
|
|
1368
|
+
const block = await rpcCall('jaelis_getBlockByNumber', [blockHex, true]);
|
|
1369
|
+
if (block && this.blockchain?.addBlock) {
|
|
1370
|
+
await this.blockchain.addBlock(block);
|
|
1371
|
+
this.syncState.blocksDownloaded++;
|
|
1372
|
+
return true;
|
|
1373
|
+
}
|
|
1374
|
+
return false;
|
|
1375
|
+
};
|
|
1376
|
+
|
|
1377
|
+
// Sync missing blocks (used for both initial sync and catching up)
|
|
1378
|
+
const syncMissingBlocks = async () => {
|
|
1379
|
+
if (this.syncState.syncing) return;
|
|
1348
1380
|
this.syncState.syncing = true;
|
|
1349
1381
|
|
|
1350
1382
|
try {
|
|
1351
|
-
// 1. Get remote block height using NATIVE jaelis_blockNumber
|
|
1352
1383
|
const remoteHeightHex = await rpcCall('jaelis_blockNumber');
|
|
1353
1384
|
this.syncState.remoteHeight = parseInt(remoteHeightHex, 16);
|
|
1354
|
-
|
|
1355
|
-
// 2. Get local block height
|
|
1356
1385
|
this.syncState.localHeight = this.blockchain?.getHeight?.() || 0;
|
|
1357
1386
|
|
|
1358
|
-
//
|
|
1359
|
-
|
|
1387
|
+
// Loop until fully synced
|
|
1388
|
+
while (this.syncState.localHeight < this.syncState.remoteHeight) {
|
|
1389
|
+
const blocksToSync = this.syncState.remoteHeight - this.syncState.localHeight;
|
|
1360
1390
|
|
|
1361
|
-
|
|
1362
|
-
|
|
1391
|
+
// Batch size: larger during initial sync, smaller when near tip
|
|
1392
|
+
const batchSize = blocksToSync > 50 ? 25 : (blocksToSync > 10 ? 10 : blocksToSync);
|
|
1363
1393
|
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1394
|
+
if (blocksToSync > 1) {
|
|
1395
|
+
console.log(`[SYNC] Syncing ${batchSize} blocks (${this.syncState.localHeight + 1} → ${this.syncState.localHeight + batchSize})`);
|
|
1396
|
+
}
|
|
1367
1397
|
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1398
|
+
for (let i = 1; i <= batchSize; i++) {
|
|
1399
|
+
await fetchBlock(this.syncState.localHeight + i);
|
|
1400
|
+
}
|
|
1371
1401
|
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
}
|
|
1377
|
-
this.syncState.blocksDownloaded++;
|
|
1378
|
-
}
|
|
1402
|
+
this.syncState.localHeight += batchSize;
|
|
1403
|
+
|
|
1404
|
+
if (batchSize > 1) {
|
|
1405
|
+
console.log(`[SYNC] Local height: ${this.syncState.localHeight}/${this.syncState.remoteHeight}`);
|
|
1379
1406
|
}
|
|
1380
1407
|
|
|
1381
|
-
|
|
1382
|
-
|
|
1408
|
+
// Re-check remote height in case new blocks arrived during sync
|
|
1409
|
+
const newRemoteHex = await rpcCall('jaelis_blockNumber');
|
|
1410
|
+
this.syncState.remoteHeight = parseInt(newRemoteHex, 16);
|
|
1383
1411
|
}
|
|
1384
1412
|
|
|
1385
1413
|
this.syncState.lastSync = Date.now();
|
|
@@ -1392,20 +1420,167 @@ class EmbeddedNetwork {
|
|
|
1392
1420
|
}
|
|
1393
1421
|
};
|
|
1394
1422
|
|
|
1395
|
-
//
|
|
1396
|
-
|
|
1397
|
-
|
|
1423
|
+
// ============================================================
|
|
1424
|
+
// WEBSOCKET SUBSCRIPTION (PREFERRED - like Ethereum newHeads)
|
|
1425
|
+
// ============================================================
|
|
1426
|
+
const tryWebSocketSync = async () => {
|
|
1427
|
+
try {
|
|
1428
|
+
const WebSocket = require('ws');
|
|
1429
|
+
const wsUrl = this.remoteRpc.replace('https://', 'wss://').replace('http://', 'ws://') + '/ws';
|
|
1430
|
+
|
|
1431
|
+
console.log(`[SYNC] Trying WebSocket subscription at ${wsUrl}`);
|
|
1432
|
+
|
|
1433
|
+
return new Promise((resolve, reject) => {
|
|
1434
|
+
const ws = new WebSocket(wsUrl, {
|
|
1435
|
+
headers: { 'User-Agent': 'JAELIS-Node/1.8.0' }
|
|
1436
|
+
});
|
|
1437
|
+
|
|
1438
|
+
const timeout = setTimeout(() => {
|
|
1439
|
+
ws.close();
|
|
1440
|
+
reject(new Error('WebSocket connection timeout'));
|
|
1441
|
+
}, 10000);
|
|
1442
|
+
|
|
1443
|
+
ws.on('open', () => {
|
|
1444
|
+
clearTimeout(timeout);
|
|
1445
|
+
console.log('[SYNC] WebSocket connected! Subscribing to newHeads...');
|
|
1446
|
+
|
|
1447
|
+
// Subscribe to new block headers
|
|
1448
|
+
ws.send(JSON.stringify({
|
|
1449
|
+
jsonrpc: '2.0',
|
|
1450
|
+
method: 'jaelis_subscribe',
|
|
1451
|
+
params: ['newHeads'],
|
|
1452
|
+
id: 1
|
|
1453
|
+
}));
|
|
1454
|
+
|
|
1455
|
+
this.syncState.mode = 'websocket';
|
|
1456
|
+
this.wsConnection = ws;
|
|
1457
|
+
resolve(true);
|
|
1458
|
+
});
|
|
1459
|
+
|
|
1460
|
+
ws.on('message', async (data) => {
|
|
1461
|
+
try {
|
|
1462
|
+
const msg = JSON.parse(data.toString());
|
|
1463
|
+
|
|
1464
|
+
// Subscription confirmation
|
|
1465
|
+
if (msg.id === 1 && msg.result) {
|
|
1466
|
+
console.log(`[SYNC] ✓ Subscribed to newHeads (id: ${msg.result})`);
|
|
1467
|
+
console.log('[SYNC] Listening for new blocks via WebSocket...');
|
|
1468
|
+
return;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
// New block notification
|
|
1472
|
+
if (msg.method === 'jaelis_subscription' && msg.params?.result) {
|
|
1473
|
+
const header = msg.params.result;
|
|
1474
|
+
const blockNum = typeof header.number === 'string'
|
|
1475
|
+
? parseInt(header.number, 16)
|
|
1476
|
+
: header.number;
|
|
1477
|
+
|
|
1478
|
+
// Fetch and sync the new block
|
|
1479
|
+
if (blockNum > this.syncState.localHeight) {
|
|
1480
|
+
// If we're behind, catch up first
|
|
1481
|
+
await syncMissingBlocks();
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
this.syncState.remoteHeight = blockNum;
|
|
1485
|
+
this.emit('newBlock', header);
|
|
1486
|
+
}
|
|
1487
|
+
} catch (e) {
|
|
1488
|
+
// Ignore parse errors
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
|
|
1492
|
+
ws.on('error', (err) => {
|
|
1493
|
+
clearTimeout(timeout);
|
|
1494
|
+
reject(err);
|
|
1495
|
+
});
|
|
1496
|
+
|
|
1497
|
+
ws.on('close', () => {
|
|
1498
|
+
console.log('[SYNC] WebSocket disconnected, falling back to polling...');
|
|
1499
|
+
this.syncState.mode = 'polling';
|
|
1500
|
+
this.wsConnection = null;
|
|
1501
|
+
// Fall back to polling
|
|
1502
|
+
startPolling();
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1505
|
+
} catch (err) {
|
|
1506
|
+
// WebSocket module not available or connection failed
|
|
1507
|
+
return false;
|
|
1508
|
+
}
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
// ============================================================
|
|
1512
|
+
// HTTP POLLING FALLBACK (like traditional Geth sync)
|
|
1513
|
+
// ============================================================
|
|
1514
|
+
const SYNCED_INTERVAL = 6000; // Poll every 6s when synced (2x block time)
|
|
1515
|
+
const CATCHUP_INTERVAL = 500; // Fast poll during catchup
|
|
1516
|
+
|
|
1517
|
+
const startPolling = () => {
|
|
1518
|
+
if (this.syncInterval) return; // Already polling
|
|
1519
|
+
|
|
1520
|
+
console.log('[SYNC] Using HTTP polling mode');
|
|
1521
|
+
this.syncState.mode = 'polling';
|
|
1522
|
+
|
|
1523
|
+
let pollInterval = CATCHUP_INTERVAL;
|
|
1524
|
+
let lastHeight = 0;
|
|
1525
|
+
|
|
1526
|
+
const poll = async () => {
|
|
1527
|
+
await syncMissingBlocks();
|
|
1528
|
+
|
|
1529
|
+
// Adjust polling speed based on sync status
|
|
1530
|
+
const isSynced = this.syncState.localHeight >= this.syncState.remoteHeight;
|
|
1531
|
+
const heightChanged = this.syncState.remoteHeight !== lastHeight;
|
|
1532
|
+
|
|
1533
|
+
if (isSynced && !heightChanged && pollInterval !== SYNCED_INTERVAL) {
|
|
1534
|
+
// Synced and no new blocks - slow down
|
|
1535
|
+
pollInterval = SYNCED_INTERVAL;
|
|
1536
|
+
clearInterval(this.syncInterval);
|
|
1537
|
+
this.syncInterval = setInterval(poll, pollInterval);
|
|
1538
|
+
console.log(`[SYNC] ✓ Synced at block ${this.syncState.localHeight}. Polling every ${pollInterval/1000}s`);
|
|
1539
|
+
} else if (!isSynced && pollInterval !== CATCHUP_INTERVAL) {
|
|
1540
|
+
// Falling behind - speed up
|
|
1541
|
+
pollInterval = CATCHUP_INTERVAL;
|
|
1542
|
+
clearInterval(this.syncInterval);
|
|
1543
|
+
this.syncInterval = setInterval(poll, pollInterval);
|
|
1544
|
+
}
|
|
1545
|
+
|
|
1546
|
+
lastHeight = this.syncState.remoteHeight;
|
|
1547
|
+
};
|
|
1548
|
+
|
|
1549
|
+
// Start polling
|
|
1550
|
+
this.syncInterval = setInterval(poll, pollInterval);
|
|
1551
|
+
};
|
|
1552
|
+
|
|
1553
|
+
// ============================================================
|
|
1554
|
+
// INITIAL SYNC
|
|
1555
|
+
// ============================================================
|
|
1556
|
+
console.log('[SYNC] Starting sync from', this.remoteRpc);
|
|
1557
|
+
|
|
1558
|
+
// Do initial block sync first
|
|
1559
|
+
await syncMissingBlocks();
|
|
1398
1560
|
|
|
1399
|
-
//
|
|
1400
|
-
|
|
1561
|
+
// Try WebSocket first (like Ethereum pub/sub), fall back to polling
|
|
1562
|
+
const wsSuccess = await tryWebSocketSync().catch(() => false);
|
|
1563
|
+
|
|
1564
|
+
if (!wsSuccess) {
|
|
1565
|
+
console.log('[SYNC] WebSocket unavailable, using HTTP polling');
|
|
1566
|
+
startPolling();
|
|
1567
|
+
}
|
|
1401
1568
|
}
|
|
1402
1569
|
|
|
1403
1570
|
async stop() {
|
|
1571
|
+
// Close WebSocket connection if active
|
|
1572
|
+
if (this.wsConnection) {
|
|
1573
|
+
this.wsConnection.close();
|
|
1574
|
+
this.wsConnection = null;
|
|
1575
|
+
}
|
|
1576
|
+
// Stop libp2p if active
|
|
1404
1577
|
if (this.libp2p) {
|
|
1405
1578
|
await this.libp2p.stop();
|
|
1406
1579
|
}
|
|
1580
|
+
// Stop polling interval if active
|
|
1407
1581
|
if (this.syncInterval) {
|
|
1408
1582
|
clearInterval(this.syncInterval);
|
|
1583
|
+
this.syncInterval = null;
|
|
1409
1584
|
}
|
|
1410
1585
|
}
|
|
1411
1586
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jaelis-node",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Official JAELIS Blockchain Node - Universal VM (6 languages), LevelDB state persistence, native jaelis_* RPC, multi-ecosystem compatibility (eth/solana/move/ton/btc/wasm/starknet), Cross-Chain Settlement (30+ chains!), AI-native MCP integration",
|
|
3
|
+
"version": "1.9.0",
|
|
4
|
+
"description": "Official JAELIS Blockchain Node - WebSocket real-time sync, Universal VM (6 languages), LevelDB state persistence, native jaelis_* RPC, multi-ecosystem compatibility (eth/solana/move/ton/btc/wasm/starknet), Cross-Chain Settlement (30+ chains!), AI-native MCP integration",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"jaelis-node": "./bin/jaelis-node.js"
|
|
@@ -72,7 +72,8 @@
|
|
|
72
72
|
"express": "^4.18.2",
|
|
73
73
|
"level": "^8.0.0",
|
|
74
74
|
"libp2p": "^1.2.0",
|
|
75
|
-
"ora": "^5.4.1"
|
|
75
|
+
"ora": "^5.4.1",
|
|
76
|
+
"ws": "^8.18.3"
|
|
76
77
|
},
|
|
77
78
|
"files": [
|
|
78
79
|
"bin/",
|