jaelis-node 1.1.0 → 1.3.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/bin/jaelis-node.js +202 -1
- package/lib/index.js +958 -23
- package/lib/vm/index.js +397 -0
- package/package.json +12 -3
package/lib/index.js
CHANGED
|
@@ -11,8 +11,310 @@
|
|
|
11
11
|
|
|
12
12
|
const path = require('path');
|
|
13
13
|
const fs = require('fs');
|
|
14
|
+
const crypto = require('crypto');
|
|
14
15
|
const EventEmitter = require('events');
|
|
15
16
|
|
|
17
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
18
|
+
// MULTI-CHAIN ADDRESS UTILITIES
|
|
19
|
+
// Supports: EVM (0x...), Solana (base58), Bitcoin (P2PKH/bech32), etc.
|
|
20
|
+
// JAELIS accepts ANY chain's address format - TRUE multi-chain!
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
22
|
+
|
|
23
|
+
const ADDRESS_FORMATS = {
|
|
24
|
+
// EVM chains (Ethereum, Polygon, Arbitrum, etc.)
|
|
25
|
+
evm: {
|
|
26
|
+
pattern: /^0x[a-fA-F0-9]{40}$/,
|
|
27
|
+
name: 'EVM (Ethereum, Polygon, etc.)',
|
|
28
|
+
example: '0x742d35Cc6634C0532925a3b844Bc9e7595f...'
|
|
29
|
+
},
|
|
30
|
+
// Solana (base58)
|
|
31
|
+
solana: {
|
|
32
|
+
pattern: /^[1-9A-HJ-NP-Za-km-z]{32,44}$/,
|
|
33
|
+
name: 'Solana',
|
|
34
|
+
example: '7xKXtg2CW87d97TXJSDpbD5jBkheTqA83TZ...'
|
|
35
|
+
},
|
|
36
|
+
// Bitcoin P2PKH (starts with 1 or 3)
|
|
37
|
+
bitcoin_legacy: {
|
|
38
|
+
pattern: /^[13][a-km-zA-HJ-NP-Z1-9]{25,34}$/,
|
|
39
|
+
name: 'Bitcoin (Legacy)',
|
|
40
|
+
example: '1BvBMSEYstWetqTFn5Au4m4GFg7xJaNVN2'
|
|
41
|
+
},
|
|
42
|
+
// Bitcoin bech32 (starts with bc1)
|
|
43
|
+
bitcoin_bech32: {
|
|
44
|
+
pattern: /^bc1[a-z0-9]{39,59}$/,
|
|
45
|
+
name: 'Bitcoin (SegWit)',
|
|
46
|
+
example: 'bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq'
|
|
47
|
+
},
|
|
48
|
+
// TON (base64url)
|
|
49
|
+
ton: {
|
|
50
|
+
pattern: /^[A-Za-z0-9_-]{48}$/,
|
|
51
|
+
name: 'TON',
|
|
52
|
+
example: 'EQCD39VS5jcptHL8vMjEXrzGaRcCVYto7HUn4bpAOg8xqB2N'
|
|
53
|
+
},
|
|
54
|
+
// Aptos/Sui (hex with 0x, 64 chars)
|
|
55
|
+
move: {
|
|
56
|
+
pattern: /^0x[a-fA-F0-9]{64}$/,
|
|
57
|
+
name: 'Move (Aptos/Sui)',
|
|
58
|
+
example: '0x1234567890abcdef...'
|
|
59
|
+
},
|
|
60
|
+
// JAELIS native (same as EVM for now, but could be extended)
|
|
61
|
+
jaelis: {
|
|
62
|
+
pattern: /^0x[a-fA-F0-9]{40}$/,
|
|
63
|
+
name: 'JAELIS Native',
|
|
64
|
+
example: '0x742d35Cc6634C0532925a3b844Bc9e7595f...'
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Detect the chain type from an address
|
|
70
|
+
*/
|
|
71
|
+
function detectAddressType(address) {
|
|
72
|
+
if (!address || typeof address !== 'string') {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Check each format
|
|
77
|
+
for (const [type, config] of Object.entries(ADDRESS_FORMATS)) {
|
|
78
|
+
if (config.pattern.test(address)) {
|
|
79
|
+
return { type, ...config };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Validate any chain address
|
|
88
|
+
*/
|
|
89
|
+
function validateAddress(address) {
|
|
90
|
+
const detected = detectAddressType(address);
|
|
91
|
+
if (!detected) {
|
|
92
|
+
return {
|
|
93
|
+
valid: false,
|
|
94
|
+
error: 'Unknown address format',
|
|
95
|
+
supportedFormats: Object.keys(ADDRESS_FORMATS)
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
return {
|
|
99
|
+
valid: true,
|
|
100
|
+
type: detected.type,
|
|
101
|
+
name: detected.name
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Convert any address to canonical form for internal storage
|
|
107
|
+
* EVM addresses are lowercased, others kept as-is
|
|
108
|
+
*/
|
|
109
|
+
function toCanonicalAddress(address) {
|
|
110
|
+
const detected = detectAddressType(address);
|
|
111
|
+
if (!detected) {
|
|
112
|
+
throw new Error('Invalid address format');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// EVM addresses are case-insensitive, normalize to lowercase
|
|
116
|
+
if (detected.type === 'evm' || detected.type === 'jaelis') {
|
|
117
|
+
return address.toLowerCase();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Other formats are case-sensitive
|
|
121
|
+
return address;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
125
|
+
// NODE WALLET CONFIGURATION (Like Geth --suggested-fee-recipient)
|
|
126
|
+
// Stores the node operator's wallet for receiving rewards
|
|
127
|
+
// NO STAKING REQUIRED - JAELIS doesn't hold your funds!
|
|
128
|
+
// ═══════════════════════════════════════════════════════════════════════════
|
|
129
|
+
|
|
130
|
+
class NodeWalletConfig {
|
|
131
|
+
constructor(dataDir) {
|
|
132
|
+
this.dataDir = dataDir;
|
|
133
|
+
this.configPath = path.join(dataDir, 'node-wallet.json');
|
|
134
|
+
this.config = null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Load wallet configuration from disk
|
|
139
|
+
*/
|
|
140
|
+
load() {
|
|
141
|
+
try {
|
|
142
|
+
if (fs.existsSync(this.configPath)) {
|
|
143
|
+
const data = fs.readFileSync(this.configPath, 'utf8');
|
|
144
|
+
this.config = JSON.parse(data);
|
|
145
|
+
return this.config;
|
|
146
|
+
}
|
|
147
|
+
} catch (e) {
|
|
148
|
+
console.warn('[WALLET] Failed to load wallet config:', e.message);
|
|
149
|
+
}
|
|
150
|
+
return null;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Save wallet configuration to disk
|
|
155
|
+
*/
|
|
156
|
+
save() {
|
|
157
|
+
try {
|
|
158
|
+
// Ensure directory exists
|
|
159
|
+
if (!fs.existsSync(this.dataDir)) {
|
|
160
|
+
fs.mkdirSync(this.dataDir, { recursive: true });
|
|
161
|
+
}
|
|
162
|
+
fs.writeFileSync(this.configPath, JSON.stringify(this.config, null, 2));
|
|
163
|
+
return true;
|
|
164
|
+
} catch (e) {
|
|
165
|
+
console.error('[WALLET] Failed to save wallet config:', e.message);
|
|
166
|
+
return false;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Set the reward recipient wallet (ANY chain address!)
|
|
172
|
+
* Like Geth's --suggested-fee-recipient
|
|
173
|
+
*/
|
|
174
|
+
setRewardRecipient(address) {
|
|
175
|
+
const validation = validateAddress(address);
|
|
176
|
+
if (!validation.valid) {
|
|
177
|
+
throw new Error(`Invalid address: ${validation.error}`);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (!this.config) {
|
|
181
|
+
this.config = { createdAt: Date.now() };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
this.config.rewardRecipient = {
|
|
185
|
+
address: toCanonicalAddress(address),
|
|
186
|
+
originalFormat: address,
|
|
187
|
+
type: validation.type,
|
|
188
|
+
typeName: validation.name,
|
|
189
|
+
setAt: Date.now()
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
this.save();
|
|
193
|
+
console.log(`[WALLET] Reward recipient set: ${address} (${validation.name})`);
|
|
194
|
+
return this.config.rewardRecipient;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get the reward recipient address
|
|
199
|
+
*/
|
|
200
|
+
getRewardRecipient() {
|
|
201
|
+
if (!this.config) this.load();
|
|
202
|
+
return this.config?.rewardRecipient || null;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Add an additional wallet (for multi-wallet nodes)
|
|
207
|
+
*/
|
|
208
|
+
addWallet(name, address, options = {}) {
|
|
209
|
+
const validation = validateAddress(address);
|
|
210
|
+
if (!validation.valid) {
|
|
211
|
+
throw new Error(`Invalid address: ${validation.error}`);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (!this.config) {
|
|
215
|
+
this.config = { createdAt: Date.now() };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
if (!this.config.wallets) {
|
|
219
|
+
this.config.wallets = [];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check for duplicates
|
|
223
|
+
const existing = this.config.wallets.find(w =>
|
|
224
|
+
toCanonicalAddress(w.address) === toCanonicalAddress(address)
|
|
225
|
+
);
|
|
226
|
+
if (existing) {
|
|
227
|
+
throw new Error(`Wallet already exists: ${existing.name}`);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
const wallet = {
|
|
231
|
+
name,
|
|
232
|
+
address: toCanonicalAddress(address),
|
|
233
|
+
originalFormat: address,
|
|
234
|
+
type: validation.type,
|
|
235
|
+
typeName: validation.name,
|
|
236
|
+
purpose: options.purpose || 'general',
|
|
237
|
+
addedAt: Date.now()
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
this.config.wallets.push(wallet);
|
|
241
|
+
this.save();
|
|
242
|
+
console.log(`[WALLET] Added wallet: ${name} (${validation.name})`);
|
|
243
|
+
return wallet;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Remove a wallet by name or address
|
|
248
|
+
*/
|
|
249
|
+
removeWallet(nameOrAddress) {
|
|
250
|
+
if (!this.config?.wallets) {
|
|
251
|
+
throw new Error('No wallets configured');
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const canonical = nameOrAddress.startsWith('0x') || nameOrAddress.startsWith('bc1')
|
|
255
|
+
? toCanonicalAddress(nameOrAddress)
|
|
256
|
+
: null;
|
|
257
|
+
|
|
258
|
+
const index = this.config.wallets.findIndex(w =>
|
|
259
|
+
w.name === nameOrAddress ||
|
|
260
|
+
(canonical && toCanonicalAddress(w.address) === canonical)
|
|
261
|
+
);
|
|
262
|
+
|
|
263
|
+
if (index === -1) {
|
|
264
|
+
throw new Error(`Wallet not found: ${nameOrAddress}`);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const removed = this.config.wallets.splice(index, 1)[0];
|
|
268
|
+
this.save();
|
|
269
|
+
console.log(`[WALLET] Removed wallet: ${removed.name}`);
|
|
270
|
+
return removed;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* List all configured wallets
|
|
275
|
+
*/
|
|
276
|
+
listWallets() {
|
|
277
|
+
if (!this.config) this.load();
|
|
278
|
+
return {
|
|
279
|
+
rewardRecipient: this.config?.rewardRecipient || null,
|
|
280
|
+
wallets: this.config?.wallets || []
|
|
281
|
+
};
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Get node identity (generates one if not exists)
|
|
286
|
+
* Like Solana's identity keypair
|
|
287
|
+
*/
|
|
288
|
+
getNodeIdentity() {
|
|
289
|
+
if (!this.config) this.load();
|
|
290
|
+
if (!this.config) {
|
|
291
|
+
this.config = { createdAt: Date.now() };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
if (!this.config.nodeIdentity) {
|
|
295
|
+
// Generate a node identity (not a wallet, just an ID)
|
|
296
|
+
const nodeId = crypto.randomBytes(32).toString('hex');
|
|
297
|
+
this.config.nodeIdentity = {
|
|
298
|
+
id: nodeId,
|
|
299
|
+
publicKey: '0x' + crypto.createHash('sha256').update(nodeId).digest('hex').slice(0, 40),
|
|
300
|
+
createdAt: Date.now()
|
|
301
|
+
};
|
|
302
|
+
this.save();
|
|
303
|
+
console.log(`[WALLET] Generated node identity: ${this.config.nodeIdentity.publicKey}`);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
return this.config.nodeIdentity;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Get full configuration
|
|
311
|
+
*/
|
|
312
|
+
getConfig() {
|
|
313
|
+
if (!this.config) this.load();
|
|
314
|
+
return this.config;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
16
318
|
// JAELIS Network Endpoints - users connect here automatically
|
|
17
319
|
const JAELIS_NETWORKS = {
|
|
18
320
|
testnet: {
|
|
@@ -73,6 +375,65 @@ class JaelisNode extends EventEmitter {
|
|
|
73
375
|
this.isRunning = false;
|
|
74
376
|
this.startTime = null;
|
|
75
377
|
this.peerCount = 0;
|
|
378
|
+
|
|
379
|
+
// Initialize wallet configuration (like Geth's keystore)
|
|
380
|
+
this.walletConfig = new NodeWalletConfig(this.options.dataDir);
|
|
381
|
+
this.walletConfig.load();
|
|
382
|
+
|
|
383
|
+
// Set reward recipient if provided via options (like --suggested-fee-recipient)
|
|
384
|
+
if (options.rewardRecipient) {
|
|
385
|
+
this.walletConfig.setRewardRecipient(options.rewardRecipient);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Get the wallet configuration manager
|
|
391
|
+
*/
|
|
392
|
+
getWalletConfig() {
|
|
393
|
+
return this.walletConfig;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
/**
|
|
397
|
+
* Set the reward recipient address (ANY chain format!)
|
|
398
|
+
* Like Geth's --suggested-fee-recipient
|
|
399
|
+
*/
|
|
400
|
+
setRewardRecipient(address) {
|
|
401
|
+
return this.walletConfig.setRewardRecipient(address);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Get the reward recipient address
|
|
406
|
+
*/
|
|
407
|
+
getRewardRecipient() {
|
|
408
|
+
return this.walletConfig.getRewardRecipient();
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Add a wallet to this node
|
|
413
|
+
*/
|
|
414
|
+
addWallet(name, address, options = {}) {
|
|
415
|
+
return this.walletConfig.addWallet(name, address, options);
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Remove a wallet from this node
|
|
420
|
+
*/
|
|
421
|
+
removeWallet(nameOrAddress) {
|
|
422
|
+
return this.walletConfig.removeWallet(nameOrAddress);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* List all configured wallets
|
|
427
|
+
*/
|
|
428
|
+
listWallets() {
|
|
429
|
+
return this.walletConfig.listWallets();
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Get node identity
|
|
434
|
+
*/
|
|
435
|
+
getNodeIdentity() {
|
|
436
|
+
return this.walletConfig.getNodeIdentity();
|
|
76
437
|
}
|
|
77
438
|
|
|
78
439
|
/**
|
|
@@ -110,6 +471,12 @@ class JaelisNode extends EventEmitter {
|
|
|
110
471
|
this.isRunning = true;
|
|
111
472
|
this.startTime = Date.now();
|
|
112
473
|
|
|
474
|
+
// Register with the JAELIS network for tracking
|
|
475
|
+
await this._registerWithNetwork();
|
|
476
|
+
|
|
477
|
+
// Start heartbeat to keep registration alive
|
|
478
|
+
this._startHeartbeat();
|
|
479
|
+
|
|
113
480
|
this.emit('started', {
|
|
114
481
|
network: this.options.network,
|
|
115
482
|
chainId: this.options.chainId,
|
|
@@ -135,6 +502,9 @@ class JaelisNode extends EventEmitter {
|
|
|
135
502
|
|
|
136
503
|
console.log('[JAELIS] Stopping node...');
|
|
137
504
|
|
|
505
|
+
// Stop heartbeat first
|
|
506
|
+
this._stopHeartbeat();
|
|
507
|
+
|
|
138
508
|
// Stop RPC server
|
|
139
509
|
if (this.rpcServer) {
|
|
140
510
|
await this._stopRpcServer();
|
|
@@ -275,6 +645,141 @@ class JaelisNode extends EventEmitter {
|
|
|
275
645
|
}
|
|
276
646
|
}
|
|
277
647
|
|
|
648
|
+
/**
|
|
649
|
+
* Register this node with the JAELIS network for tracking
|
|
650
|
+
* Mario can see all nodes that connect! Lifetime tracking!
|
|
651
|
+
*/
|
|
652
|
+
async _registerWithNetwork() {
|
|
653
|
+
try {
|
|
654
|
+
const https = require('https');
|
|
655
|
+
const http = require('http');
|
|
656
|
+
|
|
657
|
+
const identity = this.walletConfig.getNodeIdentity();
|
|
658
|
+
const rewardRecipient = this.walletConfig.getRewardRecipient();
|
|
659
|
+
|
|
660
|
+
const nodeInfo = {
|
|
661
|
+
nodeId: identity.id,
|
|
662
|
+
publicKey: identity.publicKey,
|
|
663
|
+
network: this.options.network,
|
|
664
|
+
chainId: this.options.chainId,
|
|
665
|
+
version: '1.3.0',
|
|
666
|
+
rewardAddress: rewardRecipient?.address || null,
|
|
667
|
+
rewardChainType: rewardRecipient?.type || null,
|
|
668
|
+
capabilities: ['full-node', 'rpc'],
|
|
669
|
+
p2pPort: this.options.p2pPort,
|
|
670
|
+
rpcPort: this.options.rpcPort
|
|
671
|
+
};
|
|
672
|
+
|
|
673
|
+
// Store for heartbeat
|
|
674
|
+
this._nodeInfo = nodeInfo;
|
|
675
|
+
|
|
676
|
+
const data = JSON.stringify({
|
|
677
|
+
jsonrpc: '2.0',
|
|
678
|
+
method: 'jaelis_node_register',
|
|
679
|
+
params: [nodeInfo],
|
|
680
|
+
id: 1
|
|
681
|
+
});
|
|
682
|
+
|
|
683
|
+
const url = new URL(this.options.remoteRpc);
|
|
684
|
+
const protocol = url.protocol === 'https:' ? https : http;
|
|
685
|
+
|
|
686
|
+
const options = {
|
|
687
|
+
hostname: url.hostname,
|
|
688
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
689
|
+
path: url.pathname || '/',
|
|
690
|
+
method: 'POST',
|
|
691
|
+
headers: {
|
|
692
|
+
'Content-Type': 'application/json',
|
|
693
|
+
'Content-Length': Buffer.byteLength(data)
|
|
694
|
+
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
await new Promise((resolve) => {
|
|
698
|
+
const req = protocol.request(options, (res) => {
|
|
699
|
+
let body = '';
|
|
700
|
+
res.on('data', chunk => body += chunk);
|
|
701
|
+
res.on('end', () => {
|
|
702
|
+
try {
|
|
703
|
+
const result = JSON.parse(body);
|
|
704
|
+
if (result.result?.registered) {
|
|
705
|
+
console.log(`[JAELIS] Node registered with network (ID: ${identity.publicKey.slice(0, 12)}...)`);
|
|
706
|
+
}
|
|
707
|
+
} catch (e) {
|
|
708
|
+
// Silent - registration is best-effort
|
|
709
|
+
}
|
|
710
|
+
resolve();
|
|
711
|
+
});
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
req.on('error', () => resolve()); // Silent fail - will retry on heartbeat
|
|
715
|
+
req.write(data);
|
|
716
|
+
req.end();
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
} catch (error) {
|
|
720
|
+
// Registration is best-effort, don't fail node startup
|
|
721
|
+
console.log('[JAELIS] Network registration pending (will retry)');
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/**
|
|
726
|
+
* Start heartbeat to keep node registration alive
|
|
727
|
+
* Sends heartbeat every 60 seconds
|
|
728
|
+
*/
|
|
729
|
+
_startHeartbeat() {
|
|
730
|
+
const HEARTBEAT_INTERVAL = 60000; // 60 seconds
|
|
731
|
+
|
|
732
|
+
this._heartbeatInterval = setInterval(async () => {
|
|
733
|
+
if (!this.isRunning || !this._nodeInfo) return;
|
|
734
|
+
|
|
735
|
+
try {
|
|
736
|
+
const https = require('https');
|
|
737
|
+
const http = require('http');
|
|
738
|
+
|
|
739
|
+
const data = JSON.stringify({
|
|
740
|
+
jsonrpc: '2.0',
|
|
741
|
+
method: 'jaelis_node_heartbeat',
|
|
742
|
+
params: [this._nodeInfo.nodeId],
|
|
743
|
+
id: 1
|
|
744
|
+
});
|
|
745
|
+
|
|
746
|
+
const url = new URL(this.options.remoteRpc);
|
|
747
|
+
const protocol = url.protocol === 'https:' ? https : http;
|
|
748
|
+
|
|
749
|
+
const options = {
|
|
750
|
+
hostname: url.hostname,
|
|
751
|
+
port: url.port || (url.protocol === 'https:' ? 443 : 80),
|
|
752
|
+
path: url.pathname || '/',
|
|
753
|
+
method: 'POST',
|
|
754
|
+
headers: {
|
|
755
|
+
'Content-Type': 'application/json',
|
|
756
|
+
'Content-Length': Buffer.byteLength(data)
|
|
757
|
+
}
|
|
758
|
+
};
|
|
759
|
+
|
|
760
|
+
const req = protocol.request(options, () => {});
|
|
761
|
+
req.on('error', () => {}); // Silent
|
|
762
|
+
req.write(data);
|
|
763
|
+
req.end();
|
|
764
|
+
|
|
765
|
+
} catch (e) {
|
|
766
|
+
// Silent - heartbeat is best-effort
|
|
767
|
+
}
|
|
768
|
+
}, HEARTBEAT_INTERVAL);
|
|
769
|
+
|
|
770
|
+
console.log('[JAELIS] Heartbeat started (60s interval)');
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* Stop heartbeat
|
|
775
|
+
*/
|
|
776
|
+
_stopHeartbeat() {
|
|
777
|
+
if (this._heartbeatInterval) {
|
|
778
|
+
clearInterval(this._heartbeatInterval);
|
|
779
|
+
this._heartbeatInterval = null;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
|
|
278
783
|
/**
|
|
279
784
|
* Get node status
|
|
280
785
|
*/
|
|
@@ -362,14 +867,26 @@ class BootstrapNode extends EventEmitter {
|
|
|
362
867
|
}
|
|
363
868
|
|
|
364
869
|
// ============================================================
|
|
365
|
-
// EMBEDDED
|
|
870
|
+
// EMBEDDED IMPLEMENTATIONS WITH UNIFIED VM
|
|
366
871
|
// ============================================================
|
|
367
872
|
|
|
873
|
+
// Import VM Executor (UnifiedJaelisVM integration)
|
|
874
|
+
let VMExecutor;
|
|
875
|
+
try {
|
|
876
|
+
VMExecutor = require('./vm').VMExecutor;
|
|
877
|
+
} catch (e) {
|
|
878
|
+
console.log('[JAELIS] VM module not loaded - running in light mode');
|
|
879
|
+
}
|
|
880
|
+
|
|
368
881
|
class EmbeddedBlockchain {
|
|
369
882
|
constructor(options = {}) {
|
|
370
883
|
this.chainId = options.chainId || 4545;
|
|
371
884
|
this.chain = [];
|
|
372
885
|
this.state = new Map();
|
|
886
|
+
this.pendingTransactions = [];
|
|
887
|
+
|
|
888
|
+
// Initialize VM Executor (UnifiedJaelisVM - NO BRIDGES!)
|
|
889
|
+
this.vmExecutor = null;
|
|
373
890
|
}
|
|
374
891
|
|
|
375
892
|
async initialize() {
|
|
@@ -378,9 +895,19 @@ class EmbeddedBlockchain {
|
|
|
378
895
|
this.chain.push({
|
|
379
896
|
number: 0,
|
|
380
897
|
hash: '0x' + '0'.repeat(64),
|
|
898
|
+
parentHash: '0x' + '0'.repeat(64),
|
|
381
899
|
timestamp: Date.now(),
|
|
382
|
-
transactions: []
|
|
900
|
+
transactions: [],
|
|
901
|
+
stateRoot: '0x' + '0'.repeat(64)
|
|
902
|
+
});
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// Initialize the Unified VM
|
|
906
|
+
if (VMExecutor) {
|
|
907
|
+
this.vmExecutor = new VMExecutor(this, {
|
|
908
|
+
debug: false
|
|
383
909
|
});
|
|
910
|
+
console.log('[BLOCKCHAIN] Unified VM initialized - TRUE cross-chain interop!');
|
|
384
911
|
}
|
|
385
912
|
}
|
|
386
913
|
|
|
@@ -388,8 +915,119 @@ class EmbeddedBlockchain {
|
|
|
388
915
|
return this.chain.length - 1;
|
|
389
916
|
}
|
|
390
917
|
|
|
918
|
+
getLatestBlock() {
|
|
919
|
+
return this.chain[this.chain.length - 1];
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
getBlock(numberOrHash) {
|
|
923
|
+
if (typeof numberOrHash === 'number') {
|
|
924
|
+
return this.chain[numberOrHash];
|
|
925
|
+
}
|
|
926
|
+
return this.chain.find(b => b.hash === numberOrHash);
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
/**
|
|
930
|
+
* Deploy a smart contract (ANY language!)
|
|
931
|
+
*/
|
|
932
|
+
async deployContract(source, language, options = {}) {
|
|
933
|
+
if (!this.vmExecutor) {
|
|
934
|
+
throw new Error('VM not available - initialize blockchain first');
|
|
935
|
+
}
|
|
936
|
+
return this.vmExecutor.deployContract(source, language, options);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
/**
|
|
940
|
+
* Execute a contract call
|
|
941
|
+
*/
|
|
942
|
+
async executeContract(to, functionName, args = [], options = {}) {
|
|
943
|
+
if (!this.vmExecutor) {
|
|
944
|
+
throw new Error('VM not available');
|
|
945
|
+
}
|
|
946
|
+
return this.vmExecutor.executeCall(to, functionName, args, options);
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
/**
|
|
950
|
+
* Cross-contract call (NO BRIDGES!)
|
|
951
|
+
*/
|
|
952
|
+
async crossContractCall(from, to, functionName, args = []) {
|
|
953
|
+
if (!this.vmExecutor) {
|
|
954
|
+
throw new Error('VM not available');
|
|
955
|
+
}
|
|
956
|
+
return this.vmExecutor.crossContractCall(from, to, functionName, args);
|
|
957
|
+
}
|
|
958
|
+
|
|
959
|
+
/**
|
|
960
|
+
* Process a transaction
|
|
961
|
+
*/
|
|
962
|
+
async processTransaction(tx) {
|
|
963
|
+
if (this.vmExecutor) {
|
|
964
|
+
return this.vmExecutor.processTransaction(tx);
|
|
965
|
+
}
|
|
966
|
+
// Fallback: just store in pending
|
|
967
|
+
this.pendingTransactions.push(tx);
|
|
968
|
+
return { status: 'pending' };
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
/**
|
|
972
|
+
* Create a new block with pending transactions
|
|
973
|
+
*/
|
|
974
|
+
async createBlock(validator) {
|
|
975
|
+
const crypto = require('crypto');
|
|
976
|
+
const parentBlock = this.getLatestBlock();
|
|
977
|
+
|
|
978
|
+
const block = {
|
|
979
|
+
number: parentBlock.number + 1,
|
|
980
|
+
hash: null,
|
|
981
|
+
parentHash: parentBlock.hash,
|
|
982
|
+
timestamp: Date.now(),
|
|
983
|
+
validator: validator || '0x0',
|
|
984
|
+
transactions: [...this.pendingTransactions],
|
|
985
|
+
stateRoot: this._computeStateRoot(),
|
|
986
|
+
gasUsed: 0 // ZERO FEES!
|
|
987
|
+
};
|
|
988
|
+
|
|
989
|
+
// Compute block hash
|
|
990
|
+
block.hash = '0x' + crypto.createHash('sha256')
|
|
991
|
+
.update(JSON.stringify({
|
|
992
|
+
number: block.number,
|
|
993
|
+
parentHash: block.parentHash,
|
|
994
|
+
timestamp: block.timestamp,
|
|
995
|
+
stateRoot: block.stateRoot
|
|
996
|
+
}))
|
|
997
|
+
.digest('hex');
|
|
998
|
+
|
|
999
|
+
// Add to chain
|
|
1000
|
+
this.chain.push(block);
|
|
1001
|
+
this.pendingTransactions = [];
|
|
1002
|
+
|
|
1003
|
+
console.log(`[BLOCKCHAIN] Block #${block.number} created with ${block.transactions.length} txs`);
|
|
1004
|
+
|
|
1005
|
+
return block;
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
_computeStateRoot() {
|
|
1009
|
+
const crypto = require('crypto');
|
|
1010
|
+
const stateData = JSON.stringify(Array.from(this.state.entries()));
|
|
1011
|
+
return '0x' + crypto.createHash('sha256').update(stateData).digest('hex');
|
|
1012
|
+
}
|
|
1013
|
+
|
|
1014
|
+
/**
|
|
1015
|
+
* Get VM stats
|
|
1016
|
+
*/
|
|
1017
|
+
getVMStats() {
|
|
1018
|
+
return this.vmExecutor?.getStats?.() || { vmAvailable: false };
|
|
1019
|
+
}
|
|
1020
|
+
|
|
1021
|
+
/**
|
|
1022
|
+
* Get all deployed contracts
|
|
1023
|
+
*/
|
|
1024
|
+
getContracts() {
|
|
1025
|
+
return this.vmExecutor?.getAllContracts?.() || [];
|
|
1026
|
+
}
|
|
1027
|
+
|
|
391
1028
|
async save() {
|
|
392
|
-
//
|
|
1029
|
+
// Save blockchain state to disk
|
|
1030
|
+
// TODO: Implement persistent storage
|
|
393
1031
|
}
|
|
394
1032
|
}
|
|
395
1033
|
|
|
@@ -529,34 +1167,44 @@ class EmbeddedRpcServer {
|
|
|
529
1167
|
|
|
530
1168
|
this.app = express();
|
|
531
1169
|
this.app.use(cors());
|
|
532
|
-
this.app.use(express.json());
|
|
1170
|
+
this.app.use(express.json({ limit: '50mb' })); // Allow large contract deployments
|
|
533
1171
|
|
|
534
|
-
//
|
|
535
|
-
this.app.post('/', (req, res) => {
|
|
1172
|
+
// Main RPC endpoint
|
|
1173
|
+
this.app.post('/', async (req, res) => {
|
|
536
1174
|
const { method, params, id } = req.body;
|
|
537
1175
|
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
case 'eth_blockNumber':
|
|
548
|
-
result = '0x' + (this.blockchain?.getHeight?.() || 0).toString(16);
|
|
549
|
-
break;
|
|
550
|
-
default:
|
|
551
|
-
error = { code: -32601, message: 'Method not found' };
|
|
1176
|
+
try {
|
|
1177
|
+
const result = await this._handleMethod(method, params || []);
|
|
1178
|
+
res.json({ jsonrpc: '2.0', id, result });
|
|
1179
|
+
} catch (error) {
|
|
1180
|
+
res.json({
|
|
1181
|
+
jsonrpc: '2.0',
|
|
1182
|
+
id,
|
|
1183
|
+
error: { code: -32000, message: error.message }
|
|
1184
|
+
});
|
|
552
1185
|
}
|
|
553
|
-
|
|
554
|
-
res.json({ jsonrpc: '2.0', id, result, error });
|
|
555
1186
|
});
|
|
556
1187
|
|
|
557
1188
|
// Health check
|
|
558
1189
|
this.app.get('/', (req, res) => {
|
|
559
|
-
|
|
1190
|
+
const vmStats = this.blockchain?.getVMStats?.() || {};
|
|
1191
|
+
res.json({
|
|
1192
|
+
status: 'online',
|
|
1193
|
+
service: 'JAELIS Node',
|
|
1194
|
+
chainId: this.blockchain?.chainId,
|
|
1195
|
+
blockHeight: this.blockchain?.getHeight?.() || 0,
|
|
1196
|
+
vm: {
|
|
1197
|
+
available: vmStats.vmAvailable || false,
|
|
1198
|
+
contractsDeployed: vmStats.contractsDeployed || 0,
|
|
1199
|
+
crossContractCalls: vmStats.crossContractCalls || 0
|
|
1200
|
+
},
|
|
1201
|
+
features: [
|
|
1202
|
+
'ZERO_FEES',
|
|
1203
|
+
'UNIFIED_VM',
|
|
1204
|
+
'NO_BRIDGES',
|
|
1205
|
+
'CROSS_CHAIN_INTEROP'
|
|
1206
|
+
]
|
|
1207
|
+
});
|
|
560
1208
|
});
|
|
561
1209
|
|
|
562
1210
|
return new Promise((resolve) => {
|
|
@@ -566,6 +1214,284 @@ class EmbeddedRpcServer {
|
|
|
566
1214
|
});
|
|
567
1215
|
}
|
|
568
1216
|
|
|
1217
|
+
async _handleMethod(method, params) {
|
|
1218
|
+
switch (method) {
|
|
1219
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1220
|
+
// STANDARD ETHEREUM-COMPATIBLE METHODS
|
|
1221
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1222
|
+
case 'eth_chainId':
|
|
1223
|
+
return '0x' + (this.blockchain?.chainId || 4545).toString(16);
|
|
1224
|
+
|
|
1225
|
+
case 'eth_blockNumber':
|
|
1226
|
+
case 'jaelis_blockNumber':
|
|
1227
|
+
return '0x' + (this.blockchain?.getHeight?.() || 0).toString(16);
|
|
1228
|
+
|
|
1229
|
+
case 'eth_getBlockByNumber':
|
|
1230
|
+
const blockNum = parseInt(params[0], 16);
|
|
1231
|
+
return this.blockchain?.getBlock?.(blockNum) || null;
|
|
1232
|
+
|
|
1233
|
+
case 'eth_sendTransaction':
|
|
1234
|
+
case 'eth_sendRawTransaction':
|
|
1235
|
+
return this._sendTransaction(params[0]);
|
|
1236
|
+
|
|
1237
|
+
case 'eth_call':
|
|
1238
|
+
return this._call(params[0], params[1]);
|
|
1239
|
+
|
|
1240
|
+
case 'eth_getTransactionReceipt':
|
|
1241
|
+
return this.blockchain?.vmExecutor?.getReceipt?.(params[0]) || null;
|
|
1242
|
+
|
|
1243
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1244
|
+
// JAELIS-SPECIFIC METHODS (Unified VM)
|
|
1245
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1246
|
+
case 'jaelis_getHealth':
|
|
1247
|
+
return {
|
|
1248
|
+
status: 'healthy',
|
|
1249
|
+
chainId: this.blockchain?.chainId,
|
|
1250
|
+
blockHeight: this.blockchain?.getHeight?.() || 0,
|
|
1251
|
+
vmActive: !!this.blockchain?.vmExecutor
|
|
1252
|
+
};
|
|
1253
|
+
|
|
1254
|
+
case 'jaelis_deployContract':
|
|
1255
|
+
// Deploy contract in ANY language!
|
|
1256
|
+
// params: [source, language, options]
|
|
1257
|
+
return this._deployContract(params[0], params[1], params[2] || {});
|
|
1258
|
+
|
|
1259
|
+
case 'jaelis_executeContract':
|
|
1260
|
+
// Execute contract call
|
|
1261
|
+
// params: [to, functionName, args, options]
|
|
1262
|
+
return this._executeContract(params[0], params[1], params[2] || [], params[3] || {});
|
|
1263
|
+
|
|
1264
|
+
case 'jaelis_crossContractCall':
|
|
1265
|
+
// Cross-contract call (NO BRIDGES!)
|
|
1266
|
+
// params: [fromContract, toContract, functionName, args]
|
|
1267
|
+
return this._crossContractCall(params[0], params[1], params[2], params[3] || []);
|
|
1268
|
+
|
|
1269
|
+
case 'jaelis_getContracts':
|
|
1270
|
+
return this.blockchain?.getContracts?.() || [];
|
|
1271
|
+
|
|
1272
|
+
case 'jaelis_getVMStats':
|
|
1273
|
+
return this.blockchain?.getVMStats?.() || { vmAvailable: false };
|
|
1274
|
+
|
|
1275
|
+
case 'jaelis_getSupportedLanguages':
|
|
1276
|
+
return ['solidity', 'rust', 'move', 'func', 'cairo', 'vyper'];
|
|
1277
|
+
|
|
1278
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1279
|
+
// CROSS-CHAIN SETTLEMENT METHODS (Universal State Layer!)
|
|
1280
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1281
|
+
|
|
1282
|
+
case 'jaelis_crossChain_getChains':
|
|
1283
|
+
// Get all registered chains
|
|
1284
|
+
return this._getCrossChainRegistry();
|
|
1285
|
+
|
|
1286
|
+
case 'jaelis_crossChain_getChainInfo':
|
|
1287
|
+
// Get info for specific chain ID
|
|
1288
|
+
return this._getCrossChainInfo(params[0]);
|
|
1289
|
+
|
|
1290
|
+
case 'jaelis_crossChain_readState':
|
|
1291
|
+
// Read settled state from external chain
|
|
1292
|
+
// params: [chainId, contractAddress, variableName]
|
|
1293
|
+
return this._readCrossChainState(params[0], params[1], params[2]);
|
|
1294
|
+
|
|
1295
|
+
case 'jaelis_crossChain_settleState':
|
|
1296
|
+
// Settle external chain state with proof
|
|
1297
|
+
// params: [chainId, contractAddress, variableName, value, proof]
|
|
1298
|
+
return this._settleCrossChainState(params[0], params[1], params[2], params[3], params[4]);
|
|
1299
|
+
|
|
1300
|
+
case 'jaelis_crossChain_getStateDiff':
|
|
1301
|
+
// Get state diff between two chains
|
|
1302
|
+
// params: [chainId1, chainId2, contractAddress, variableNames]
|
|
1303
|
+
return this._getCrossChainStateDiff(params[0], params[1], params[2], params[3]);
|
|
1304
|
+
|
|
1305
|
+
case 'jaelis_crossChain_computeSlot':
|
|
1306
|
+
// Compute namespaced storage slot
|
|
1307
|
+
// params: [chainId, contractAddress, variableName]
|
|
1308
|
+
return this._computeCrossChainSlot(params[0], params[1], params[2]);
|
|
1309
|
+
|
|
1310
|
+
case 'jaelis_crossChain_getLightClientStatus':
|
|
1311
|
+
// Get light client status for a chain
|
|
1312
|
+
return this._getLightClientStatus(params[0]);
|
|
1313
|
+
|
|
1314
|
+
case 'jaelis_crossChain_getSettlements':
|
|
1315
|
+
// Get all settlements for a contract
|
|
1316
|
+
// params: [chainId, contractAddress]
|
|
1317
|
+
return this._getSettlements(params[0], params[1]);
|
|
1318
|
+
|
|
1319
|
+
case 'jaelis_crossChain_getStats':
|
|
1320
|
+
// Get cross-chain stats
|
|
1321
|
+
return this._getCrossChainStats();
|
|
1322
|
+
|
|
1323
|
+
default:
|
|
1324
|
+
throw new Error(`Method not found: ${method}`);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
async _sendTransaction(tx) {
|
|
1329
|
+
if (!this.blockchain) throw new Error('Blockchain not available');
|
|
1330
|
+
const result = await this.blockchain.processTransaction(tx);
|
|
1331
|
+
return result.transactionHash || '0x0';
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
async _call(callObject) {
|
|
1335
|
+
if (!this.blockchain?.vmExecutor) throw new Error('VM not available');
|
|
1336
|
+
const result = await this.blockchain.vmExecutor.executeCall(
|
|
1337
|
+
callObject.to,
|
|
1338
|
+
callObject.data?.functionName || 'call',
|
|
1339
|
+
callObject.data?.args || [],
|
|
1340
|
+
{ from: callObject.from }
|
|
1341
|
+
);
|
|
1342
|
+
return result.returnValue;
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
async _deployContract(source, language, options) {
|
|
1346
|
+
if (!this.blockchain) throw new Error('Blockchain not available');
|
|
1347
|
+
console.log(`[RPC] Deploying ${language} contract...`);
|
|
1348
|
+
const result = await this.blockchain.deployContract(source, language, options);
|
|
1349
|
+
return {
|
|
1350
|
+
address: result.address,
|
|
1351
|
+
language,
|
|
1352
|
+
gasUsed: 0 // ZERO FEES!
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
async _executeContract(to, functionName, args, options) {
|
|
1357
|
+
if (!this.blockchain) throw new Error('Blockchain not available');
|
|
1358
|
+
return this.blockchain.executeContract(to, functionName, args, options);
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
async _crossContractCall(from, to, functionName, args) {
|
|
1362
|
+
if (!this.blockchain) throw new Error('Blockchain not available');
|
|
1363
|
+
console.log(`[RPC] Cross-call: ${from.substring(0, 10)}... → ${to.substring(0, 10)}...::${functionName}`);
|
|
1364
|
+
return this.blockchain.crossContractCall(from, to, functionName, args);
|
|
1365
|
+
}
|
|
1366
|
+
|
|
1367
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1368
|
+
// CROSS-CHAIN SETTLEMENT IMPLEMENTATIONS
|
|
1369
|
+
// ═══════════════════════════════════════════════════════════════
|
|
1370
|
+
|
|
1371
|
+
_getCrossChainRegistry() {
|
|
1372
|
+
// Return the VM's cross-chain registry if available
|
|
1373
|
+
if (this.blockchain?.vmExecutor?.vm?.getAllChains) {
|
|
1374
|
+
return this.blockchain.vmExecutor.vm.getAllChains();
|
|
1375
|
+
}
|
|
1376
|
+
// Fallback: return hardcoded registry
|
|
1377
|
+
return {
|
|
1378
|
+
JAELIS_MAINNET: { id: 4547, name: 'JAELIS Mainnet', type: 'native', status: 'upcoming' },
|
|
1379
|
+
JAELIS_TESTNET: { id: 4545, name: 'JAELIS Testnet', type: 'native', status: 'live' },
|
|
1380
|
+
ETH_MAINNET: { id: 1, name: 'Ethereum Mainnet', type: 'evm' },
|
|
1381
|
+
ETH_SEPOLIA: { id: 11155111, name: 'Ethereum Sepolia', type: 'evm' },
|
|
1382
|
+
POLYGON: { id: 137, name: 'Polygon PoS', type: 'evm' },
|
|
1383
|
+
ARBITRUM_ONE: { id: 42161, name: 'Arbitrum One', type: 'evm' },
|
|
1384
|
+
OPTIMISM: { id: 10, name: 'OP Mainnet', type: 'evm' },
|
|
1385
|
+
BASE: { id: 8453, name: 'Base', type: 'evm' },
|
|
1386
|
+
AVALANCHE_C: { id: 43114, name: 'Avalanche C-Chain', type: 'evm' },
|
|
1387
|
+
BSC: { id: 56, name: 'BNB Smart Chain', type: 'evm' },
|
|
1388
|
+
SOLANA_MAINNET: { id: 101, name: 'Solana Mainnet', type: 'svm', cluster: 'mainnet-beta' },
|
|
1389
|
+
SOLANA_DEVNET: { id: 102, name: 'Solana Devnet', type: 'svm', cluster: 'devnet' },
|
|
1390
|
+
APTOS_MAINNET: { id: 1, name: 'Aptos Mainnet', type: 'move', network: 'aptos' },
|
|
1391
|
+
SUI_MAINNET: { id: 1, name: 'Sui Mainnet', type: 'move', network: 'sui' },
|
|
1392
|
+
TON_MAINNET: { id: -1, name: 'TON Mainnet', type: 'tvm', workchain: -1 }
|
|
1393
|
+
};
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
_getCrossChainInfo(chainId) {
|
|
1397
|
+
const chains = this._getCrossChainRegistry();
|
|
1398
|
+
const chain = Object.values(chains).find(c => c.id === chainId);
|
|
1399
|
+
return chain || { error: 'Chain not found', chainId };
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
async _readCrossChainState(chainId, contractAddress, variableName) {
|
|
1403
|
+
if (this.blockchain?.vmExecutor?.vm?.readCrossChainState) {
|
|
1404
|
+
const value = this.blockchain.vmExecutor.vm.readCrossChainState(chainId, contractAddress, variableName);
|
|
1405
|
+
return {
|
|
1406
|
+
chainId,
|
|
1407
|
+
contractAddress,
|
|
1408
|
+
variableName,
|
|
1409
|
+
value: value ? '0x' + value.toString('hex') : '0x0',
|
|
1410
|
+
settled: value ? true : false
|
|
1411
|
+
};
|
|
1412
|
+
}
|
|
1413
|
+
return { error: 'Cross-chain state not available', chainId, contractAddress, variableName };
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
async _settleCrossChainState(chainId, contractAddress, variableName, value, proof) {
|
|
1417
|
+
if (this.blockchain?.vmExecutor?.vm?.settleExternalState) {
|
|
1418
|
+
const valueBuffer = Buffer.from(value.replace('0x', ''), 'hex');
|
|
1419
|
+
const result = await this.blockchain.vmExecutor.vm.settleExternalState(
|
|
1420
|
+
chainId, contractAddress, variableName, valueBuffer, proof
|
|
1421
|
+
);
|
|
1422
|
+
console.log(`[RPC] Cross-chain settlement: Chain ${chainId} → ${variableName}`);
|
|
1423
|
+
return {
|
|
1424
|
+
success: true,
|
|
1425
|
+
chainId,
|
|
1426
|
+
contractAddress,
|
|
1427
|
+
variableName,
|
|
1428
|
+
slot: result.slot ? '0x' + result.slot.toString('hex') : null,
|
|
1429
|
+
settlement: result.settlement
|
|
1430
|
+
};
|
|
1431
|
+
}
|
|
1432
|
+
return { error: 'Cross-chain settlement not available' };
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
_getCrossChainStateDiff(chainId1, chainId2, contractAddress, variableNames) {
|
|
1436
|
+
if (this.blockchain?.vmExecutor?.vm?.getCrossChainStateDiff) {
|
|
1437
|
+
return this.blockchain.vmExecutor.vm.getCrossChainStateDiff(
|
|
1438
|
+
chainId1, chainId2, contractAddress, variableNames
|
|
1439
|
+
);
|
|
1440
|
+
}
|
|
1441
|
+
return { error: 'State diff not available', chainId1, chainId2 };
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
_computeCrossChainSlot(chainId, contractAddress, variableName) {
|
|
1445
|
+
if (this.blockchain?.vmExecutor?.vm?.computeCrossChainSlot) {
|
|
1446
|
+
const slot = this.blockchain.vmExecutor.vm.computeCrossChainSlot(chainId, contractAddress, variableName);
|
|
1447
|
+
return '0x' + slot.toString('hex');
|
|
1448
|
+
}
|
|
1449
|
+
// Fallback: compute locally
|
|
1450
|
+
const crypto = require('crypto');
|
|
1451
|
+
const data = Buffer.concat([
|
|
1452
|
+
Buffer.alloc(4), // chainId placeholder
|
|
1453
|
+
Buffer.from(contractAddress.replace('0x', ''), 'hex'),
|
|
1454
|
+
Buffer.from(variableName, 'utf8')
|
|
1455
|
+
]);
|
|
1456
|
+
data.writeUInt32LE(chainId, 0);
|
|
1457
|
+
return '0x' + crypto.createHash('sha256').update(data).digest('hex');
|
|
1458
|
+
}
|
|
1459
|
+
|
|
1460
|
+
_getLightClientStatus(chainId) {
|
|
1461
|
+
if (this.blockchain?.vmExecutor?.vm?.crossChainManager?.stateStore?.lightClients) {
|
|
1462
|
+
const client = this.blockchain.vmExecutor.vm.crossChainManager.stateStore.lightClients.get(chainId);
|
|
1463
|
+
if (client) {
|
|
1464
|
+
return {
|
|
1465
|
+
chainId,
|
|
1466
|
+
synced: client.synced || false,
|
|
1467
|
+
latestBlock: client.latestBlock || 0,
|
|
1468
|
+
stateRoot: client.latestStateRoot ? '0x' + client.latestStateRoot.toString('hex') : null
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
}
|
|
1472
|
+
return { chainId, synced: false, error: 'Light client not available' };
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
_getSettlements(chainId, contractAddress) {
|
|
1476
|
+
if (this.blockchain?.vmExecutor?.vm?.crossChainManager?.stateStore?.settlementLog) {
|
|
1477
|
+
const log = this.blockchain.vmExecutor.vm.crossChainManager.stateStore.settlementLog;
|
|
1478
|
+
return log.filter(s => s.chainId === chainId && s.contractAddress === contractAddress);
|
|
1479
|
+
}
|
|
1480
|
+
return [];
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
_getCrossChainStats() {
|
|
1484
|
+
if (this.blockchain?.vmExecutor?.vm?.crossChainManager?.getStats) {
|
|
1485
|
+
return this.blockchain.vmExecutor.vm.crossChainManager.getStats();
|
|
1486
|
+
}
|
|
1487
|
+
return {
|
|
1488
|
+
totalSettlements: 0,
|
|
1489
|
+
chainsActive: 0,
|
|
1490
|
+
lightClientsRegistered: 0,
|
|
1491
|
+
stateSize: 0
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
|
|
569
1495
|
async stop() {
|
|
570
1496
|
if (this.server) {
|
|
571
1497
|
this.server.close();
|
|
@@ -599,6 +1525,15 @@ module.exports = {
|
|
|
599
1525
|
EmbeddedRpcServer,
|
|
600
1526
|
EmbeddedStorage,
|
|
601
1527
|
|
|
1528
|
+
// Wallet configuration (like Geth's keystore)
|
|
1529
|
+
NodeWalletConfig,
|
|
1530
|
+
|
|
1531
|
+
// Multi-chain address utilities
|
|
1532
|
+
ADDRESS_FORMATS,
|
|
1533
|
+
detectAddressType,
|
|
1534
|
+
validateAddress,
|
|
1535
|
+
toCanonicalAddress,
|
|
1536
|
+
|
|
602
1537
|
// Convenience factory
|
|
603
1538
|
createNode: (options) => new JaelisNode(options),
|
|
604
1539
|
createBootstrap: (options) => new BootstrapNode(options)
|