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/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 MINIMAL IMPLEMENTATIONS (for standalone operation)
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
- // Minimal save
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
- // Basic RPC endpoint
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
- let result = null;
539
- let error = null;
540
-
541
- switch (method) {
542
- case 'jaelis_getHealth':
543
- case 'eth_chainId':
544
- result = { status: 'healthy', chainId: this.blockchain?.chainId };
545
- break;
546
- case 'jaelis_blockNumber':
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
- res.json({ status: 'online', service: 'JAELIS Node', chainId: this.blockchain?.chainId });
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)