jaelis-node 1.5.0 → 1.8.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
@@ -485,6 +485,9 @@ class JaelisNode extends EventEmitter {
485
485
  await this._initRpcServer();
486
486
  }
487
487
 
488
+ // Start Settlement Server for cross-chain deployments
489
+ await this._initSettlementServer();
490
+
488
491
  this.isRunning = true;
489
492
  this.startTime = Date.now();
490
493
 
@@ -522,6 +525,11 @@ class JaelisNode extends EventEmitter {
522
525
  // Stop heartbeat first
523
526
  this._stopHeartbeat();
524
527
 
528
+ // Stop Settlement server
529
+ if (this.settlementServer) {
530
+ await this._stopSettlementServer();
531
+ }
532
+
525
533
  // Stop RPC server
526
534
  if (this.rpcServer) {
527
535
  await this._stopRpcServer();
@@ -623,7 +631,8 @@ class JaelisNode extends EventEmitter {
623
631
  console.log('[JAELIS] Initializing RPC server...');
624
632
 
625
633
  if (this.JaelisRpcServer) {
626
- this.rpcServer = new this.JaelisRpcServer(this.blockchain, this.options.rpcPort);
634
+ // Pass network reference for sync status reporting
635
+ this.rpcServer = new this.JaelisRpcServer(this.blockchain, this.options.rpcPort, this.network);
627
636
 
628
637
  if (this.rpcServer.start) {
629
638
  await this.rpcServer.start();
@@ -635,6 +644,52 @@ class JaelisNode extends EventEmitter {
635
644
  console.log(`[JAELIS] RPC server started on http://${this.options.rpcHost}:${this.options.rpcPort}`);
636
645
  }
637
646
 
647
+ /**
648
+ * Initialize Settlement Server for cross-chain deployments
649
+ *
650
+ * The Settlement Server handles propagating JAELIS contract deployments
651
+ * to external chains. NO GAS - NO FEES - NO SANDBOX - NO WALLS!
652
+ */
653
+ async _initSettlementServer() {
654
+ console.log('[JAELIS] Initializing Settlement Server...');
655
+
656
+ try {
657
+ // Load the settlement server module
658
+ const settlementPath = path.join(__dirname, 'settlement-server.js');
659
+
660
+ if (fs.existsSync(settlementPath)) {
661
+ const { SettlementServer } = require(settlementPath);
662
+
663
+ this.settlementServer = new SettlementServer({
664
+ port: this.options.settlementPort || 3847,
665
+ host: this.options.rpcHost || '0.0.0.0'
666
+ });
667
+
668
+ await this.settlementServer.start();
669
+
670
+ console.log(`[JAELIS] Settlement Server started on port ${this.options.settlementPort || 3847}`);
671
+ console.log('[JAELIS] Cross-chain deployments: ENABLED');
672
+ console.log('[JAELIS] NO GAS - NO FEES - NO SANDBOX - NO WALLS!');
673
+ } else {
674
+ console.log('[JAELIS] Settlement Server not found, cross-chain disabled');
675
+ }
676
+ } catch (error) {
677
+ // Don't fail node startup if settlement server fails
678
+ console.error('[JAELIS] Settlement Server error:', error.message);
679
+ console.log('[JAELIS] Continuing without cross-chain support...');
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Stop Settlement Server
685
+ */
686
+ async _stopSettlementServer() {
687
+ if (this.settlementServer && this.settlementServer.stop) {
688
+ await this.settlementServer.stop();
689
+ console.log('[JAELIS] Settlement Server stopped');
690
+ }
691
+ }
692
+
638
693
  /**
639
694
  * Stop RPC server
640
695
  */
@@ -707,7 +762,8 @@ class JaelisNode extends EventEmitter {
707
762
  method: 'POST',
708
763
  headers: {
709
764
  'Content-Type': 'application/json',
710
- 'Content-Length': Buffer.byteLength(data)
765
+ 'Content-Length': Buffer.byteLength(data),
766
+ 'User-Agent': 'JAELIS-Node/1.7.0' // Identifies us as a node - gets priority treatment
711
767
  }
712
768
  };
713
769
 
@@ -770,7 +826,8 @@ class JaelisNode extends EventEmitter {
770
826
  method: 'POST',
771
827
  headers: {
772
828
  'Content-Type': 'application/json',
773
- 'Content-Length': Buffer.byteLength(data)
829
+ 'Content-Length': Buffer.byteLength(data),
830
+ 'User-Agent': 'JAELIS-Node/1.7.0' // Identifies us as a node - gets priority treatment
774
831
  }
775
832
  };
776
833
 
@@ -1042,6 +1099,105 @@ class EmbeddedBlockchain {
1042
1099
  return this.vmExecutor?.getAllContracts?.() || [];
1043
1100
  }
1044
1101
 
1102
+ /**
1103
+ * Add a block received from sync (RPC fallback or P2P)
1104
+ * Validates the block before adding to chain
1105
+ */
1106
+ async addBlock(block) {
1107
+ if (!block || block.number === undefined) {
1108
+ throw new Error('Invalid block');
1109
+ }
1110
+
1111
+ // Normalize block number - could be hex string (0x1) or decimal
1112
+ let blockNumber = block.number;
1113
+ if (typeof blockNumber === 'string') {
1114
+ if (blockNumber.startsWith('0x')) {
1115
+ blockNumber = parseInt(blockNumber, 16);
1116
+ } else {
1117
+ blockNumber = parseInt(blockNumber, 10);
1118
+ }
1119
+ }
1120
+
1121
+ const expectedNumber = this.chain.length;
1122
+ if (blockNumber !== expectedNumber) {
1123
+ // Skip if we already have this block or it's out of order
1124
+ if (blockNumber < expectedNumber) {
1125
+ return { status: 'skipped', reason: 'already_have' };
1126
+ }
1127
+ throw new Error(`Block out of order: expected ${expectedNumber}, got ${blockNumber}`);
1128
+ }
1129
+
1130
+ // Store normalized block number for consistency
1131
+ block.number = blockNumber;
1132
+
1133
+ // Validate parent hash (skip for first sync - genesis hash may differ)
1134
+ // Like Ethereum "fast sync" - we trust the RPC source initially
1135
+ const parentBlock = this.getLatestBlock();
1136
+ if (blockNumber > 1 && block.parentHash && parentBlock.hash) {
1137
+ // Only validate parent hash for blocks after genesis
1138
+ // During initial sync, parent hashes are validated by the remote node
1139
+ if (block.parentHash !== parentBlock.hash) {
1140
+ // Check if this is initial sync (chain is mostly empty)
1141
+ if (this.chain.length > 10) {
1142
+ throw new Error('Invalid parent hash - possible chain reorg');
1143
+ }
1144
+ // During initial sync, log but continue
1145
+ console.log(`[SYNC] Accepting block ${blockNumber} (trusting remote during initial sync)`);
1146
+ }
1147
+ }
1148
+
1149
+ // Add block to chain
1150
+ this.chain.push(block);
1151
+
1152
+ // Process transactions in the block
1153
+ if (block.transactions && Array.isArray(block.transactions)) {
1154
+ for (const tx of block.transactions) {
1155
+ // Update account states from transactions
1156
+ if (tx.from && tx.to && tx.value) {
1157
+ const value = BigInt(tx.value || '0');
1158
+ const fromBalance = BigInt(this.state.get(tx.from)?.balance || '0');
1159
+ const toBalance = BigInt(this.state.get(tx.to)?.balance || '0');
1160
+
1161
+ // Debit sender, credit receiver
1162
+ this.state.set(tx.from, {
1163
+ ...this.state.get(tx.from),
1164
+ balance: (fromBalance - value).toString(),
1165
+ nonce: (parseInt(this.state.get(tx.from)?.nonce || '0') + 1).toString()
1166
+ });
1167
+ this.state.set(tx.to, {
1168
+ ...this.state.get(tx.to),
1169
+ balance: (toBalance + value).toString()
1170
+ });
1171
+ }
1172
+ }
1173
+ }
1174
+
1175
+ console.log(`[BLOCKCHAIN] Synced block #${block.number} (${block.transactions?.length || 0} txs)`);
1176
+
1177
+ return { status: 'added', blockNumber: block.number };
1178
+ }
1179
+
1180
+ /**
1181
+ * Get sync status (jaelis_syncing compatible)
1182
+ */
1183
+ getSyncStatus(remoteHeight) {
1184
+ const localHeight = this.getHeight();
1185
+ const isSyncing = localHeight < remoteHeight;
1186
+
1187
+ if (!isSyncing) {
1188
+ return false; // Fully synced
1189
+ }
1190
+
1191
+ return {
1192
+ startingBlock: '0x0',
1193
+ currentBlock: '0x' + localHeight.toString(16),
1194
+ highestBlock: '0x' + remoteHeight.toString(16),
1195
+ syncedBlocks: localHeight,
1196
+ remainingBlocks: remoteHeight - localHeight,
1197
+ percentComplete: ((localHeight / remoteHeight) * 100).toFixed(2) + '%'
1198
+ };
1199
+ }
1200
+
1045
1201
  async save() {
1046
1202
  // Save blockchain state to disk
1047
1203
  // TODO: Implement persistent storage
@@ -1084,9 +1240,10 @@ class EmbeddedNetwork {
1084
1240
  const { tcp } = await import('@libp2p/tcp');
1085
1241
  const { noise } = await import('@chainsafe/libp2p-noise');
1086
1242
  const { yamux } = await import('@chainsafe/libp2p-yamux');
1087
- const { bootstrap } = await import('@libp2p/bootstrap');
1088
1243
  const { identify } = await import('@libp2p/identify');
1089
1244
 
1245
+ // Create libp2p node - JAELIS user node
1246
+ // Like Ethereum: start listening, bootstrap nodes come later via DHT
1090
1247
  const config = {
1091
1248
  addresses: {
1092
1249
  listen: [`/ip4/${this.host}/tcp/${this.port}`]
@@ -1099,17 +1256,11 @@ class EmbeddedNetwork {
1099
1256
  }
1100
1257
  };
1101
1258
 
1102
- // Add bootstrap if we have nodes
1103
- if (this.bootstrapNodes.length > 0) {
1104
- const validBootstrap = this.bootstrapNodes.filter(n => n.includes('/p2p/'));
1105
- if (validBootstrap.length > 0) {
1106
- config.peerDiscovery = [bootstrap({ list: validBootstrap })];
1107
- }
1108
- }
1109
-
1110
1259
  this.libp2p = await createLibp2p(config);
1111
1260
  await this.libp2p.start();
1112
1261
 
1262
+ console.log(`[P2P] Node started with ID: ${this.libp2p.peerId.toString().slice(0, 16)}...`);
1263
+
1113
1264
  // Track peers
1114
1265
  this.libp2p.addEventListener('peer:connect', (evt) => {
1115
1266
  const peerId = evt.detail.toString();
@@ -1122,33 +1273,131 @@ class EmbeddedNetwork {
1122
1273
  this.peers.delete(peerId);
1123
1274
  console.log(`[P2P] Peer disconnected (${this.peers.size} remaining)`);
1124
1275
  });
1276
+
1277
+ // Try to dial bootstrap nodes directly (without peer ID)
1278
+ // Like Ethereum: we connect via TCP, then learn peer ID during handshake
1279
+ for (const addr of this.bootstrapNodes) {
1280
+ try {
1281
+ const { multiaddr } = await import('@multiformats/multiaddr');
1282
+ const ma = multiaddr(addr);
1283
+ await this.libp2p.dial(ma);
1284
+ console.log(`[P2P] Connected to bootstrap: ${addr}`);
1285
+ } catch (err) {
1286
+ // Bootstrap unavailable - this is OK, we'll use RPC fallback
1287
+ console.log(`[P2P] Bootstrap ${addr.slice(0, 30)}... unavailable`);
1288
+ }
1289
+ }
1125
1290
  }
1126
1291
 
1127
1292
  async _startRPCFallback() {
1128
- // Sync from remote RPC periodically
1129
- const syncFromRPC = async () => {
1130
- try {
1293
+ // Track sync state
1294
+ this.syncState = {
1295
+ localHeight: 0,
1296
+ remoteHeight: 0,
1297
+ syncing: false,
1298
+ lastSync: null,
1299
+ blocksDownloaded: 0
1300
+ };
1301
+
1302
+ // Helper to make RPC calls to remote node
1303
+ const rpcCall = async (method, params = []) => {
1304
+ return new Promise((resolve, reject) => {
1131
1305
  const https = require('https');
1132
1306
  const http = require('http');
1133
- const protocol = this.remoteRpc.startsWith('https') ? https : http;
1307
+ const url = new URL(this.remoteRpc);
1308
+ const protocol = url.protocol === 'https:' ? https : http;
1134
1309
 
1135
1310
  const data = JSON.stringify({
1136
1311
  jsonrpc: '2.0',
1137
- method: 'eth_blockNumber',
1138
- params: [],
1139
- id: 1
1312
+ method,
1313
+ params,
1314
+ id: Date.now()
1140
1315
  });
1141
1316
 
1142
- // Simple fetch for block number (keeps node in sync)
1143
- // Full sync implementation would fetch blocks
1317
+ const req = protocol.request({
1318
+ hostname: url.hostname,
1319
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
1320
+ path: url.pathname || '/',
1321
+ method: 'POST',
1322
+ headers: {
1323
+ 'Content-Type': 'application/json',
1324
+ 'Content-Length': Buffer.byteLength(data),
1325
+ 'User-Agent': 'JAELIS-Node/1.7.0' // Node sync traffic - exempt from rate limits
1326
+ }
1327
+ }, (res) => {
1328
+ let body = '';
1329
+ res.on('data', chunk => body += chunk);
1330
+ res.on('end', () => {
1331
+ try {
1332
+ const json = JSON.parse(body);
1333
+ if (json.error) reject(new Error(json.error.message));
1334
+ else resolve(json.result);
1335
+ } catch (e) { reject(e); }
1336
+ });
1337
+ });
1338
+
1339
+ req.on('error', reject);
1340
+ req.write(data);
1341
+ req.end();
1342
+ });
1343
+ };
1344
+
1345
+ // Sync blocks from remote RPC using JAELIS NATIVE methods
1346
+ const syncFromRPC = async () => {
1347
+ if (this.syncState.syncing) return; // Already syncing
1348
+ this.syncState.syncing = true;
1349
+
1350
+ try {
1351
+ // 1. Get remote block height using NATIVE jaelis_blockNumber
1352
+ const remoteHeightHex = await rpcCall('jaelis_blockNumber');
1353
+ this.syncState.remoteHeight = parseInt(remoteHeightHex, 16);
1354
+
1355
+ // 2. Get local block height
1356
+ this.syncState.localHeight = this.blockchain?.getHeight?.() || 0;
1357
+
1358
+ // 3. Sync missing blocks (batch of 10 at a time)
1359
+ const blocksToSync = Math.min(10, this.syncState.remoteHeight - this.syncState.localHeight);
1360
+
1361
+ if (blocksToSync > 0) {
1362
+ console.log(`[SYNC] Syncing blocks ${this.syncState.localHeight + 1} to ${this.syncState.localHeight + blocksToSync} (remote: ${this.syncState.remoteHeight})`);
1363
+
1364
+ for (let i = 1; i <= blocksToSync; i++) {
1365
+ const blockNum = this.syncState.localHeight + i;
1366
+ const blockHex = '0x' + blockNum.toString(16);
1367
+
1368
+ // Fetch full block with transactions using NATIVE jaelis_getBlockByNumber
1369
+ // This returns JAELIS-native fields (lodeUsed, consensus: PoEC, etc.)
1370
+ const block = await rpcCall('jaelis_getBlockByNumber', [blockHex, true]);
1371
+
1372
+ if (block) {
1373
+ // Store block in local blockchain
1374
+ if (this.blockchain?.addBlock) {
1375
+ await this.blockchain.addBlock(block);
1376
+ }
1377
+ this.syncState.blocksDownloaded++;
1378
+ }
1379
+ }
1380
+
1381
+ this.syncState.localHeight += blocksToSync;
1382
+ console.log(`[SYNC] Downloaded ${blocksToSync} blocks. Local height: ${this.syncState.localHeight}`);
1383
+ }
1384
+
1385
+ this.syncState.lastSync = Date.now();
1386
+ this.emit('sync', this.syncState);
1387
+
1144
1388
  } catch (err) {
1145
- // Silent fail - will retry
1389
+ console.error('[SYNC] Error:', err.message);
1390
+ } finally {
1391
+ this.syncState.syncing = false;
1146
1392
  }
1147
1393
  };
1148
1394
 
1149
- // Sync every 5 seconds
1150
- this.syncInterval = setInterval(syncFromRPC, 5000);
1395
+ // Initial sync
1396
+ console.log('[SYNC] Starting RPC fallback sync from', this.remoteRpc);
1151
1397
  await syncFromRPC();
1398
+
1399
+ // Continue syncing every 3 seconds (JAELIS has 3-second blocks)
1400
+ this.syncInterval = setInterval(syncFromRPC, 3000);
1152
1401
  }
1153
1402
 
1154
1403
  async stop() {
@@ -1172,9 +1421,10 @@ class EmbeddedNetwork {
1172
1421
  }
1173
1422
 
1174
1423
  class EmbeddedRpcServer {
1175
- constructor(blockchain, port) {
1424
+ constructor(blockchain, port, network = null) {
1176
1425
  this.blockchain = blockchain;
1177
1426
  this.port = port;
1427
+ this.network = network; // Reference to EmbeddedNetwork for sync status
1178
1428
  this.app = null;
1179
1429
  }
1180
1430
 
@@ -1242,6 +1492,36 @@ class EmbeddedRpcServer {
1242
1492
  case 'jaelis_blockNumber':
1243
1493
  return '0x' + (this.blockchain?.getHeight?.() || 0).toString(16);
1244
1494
 
1495
+ case 'jaelis_syncing':
1496
+ // Return sync status - false if fully synced, object if syncing
1497
+ if (this.network?.syncState) {
1498
+ return this.blockchain?.getSyncStatus?.(this.network.syncState.remoteHeight) || false;
1499
+ }
1500
+ return false;
1501
+
1502
+ case 'jaelis_getBlockByNumber':
1503
+ // Native JAELIS method - returns JAELIS-native block fields
1504
+ const jaelisBlockNum = typeof params[0] === 'string' && params[0].startsWith('0x')
1505
+ ? parseInt(params[0], 16)
1506
+ : params[0];
1507
+ const jaelisBlock = this.blockchain?.getBlock?.(jaelisBlockNum);
1508
+ if (jaelisBlock) {
1509
+ // Add JAELIS-specific fields
1510
+ return {
1511
+ ...jaelisBlock,
1512
+ lodeUsed: '0x0', // ZERO FEES
1513
+ consensus: 'PoEC', // Proof of Entropy Contribution
1514
+ jaelisVersion: '1.0.0'
1515
+ };
1516
+ }
1517
+ return null;
1518
+
1519
+ case 'jaelis_getBlockByHash':
1520
+ // Find block by hash
1521
+ const targetHash = params[0];
1522
+ const foundBlock = this.blockchain?.chain?.find(b => b.hash === targetHash);
1523
+ return foundBlock || null;
1524
+
1245
1525
  // ═══════════════════════════════════════════════════════════════
1246
1526
  // LAYER 2: ETHEREUM COMPATIBILITY (PROXIES → jaelis_*)
1247
1527
  // ═══════════════════════════════════════════════════════════════
@@ -1265,9 +1545,35 @@ class EmbeddedRpcServer {
1265
1545
  case 'eth_getTransactionReceipt':
1266
1546
  return this.blockchain?.vmExecutor?.getReceipt?.(params[0]) || null;
1267
1547
 
1548
+ case 'eth_getCode':
1549
+ // CRITICAL: This is how block explorers detect contracts!
1550
+ // Returns bytecode at address, or '0x' for EOAs
1551
+ return this._getCode(params[0], params[1] || 'latest');
1552
+
1553
+ case 'eth_getBalance':
1554
+ return this._getBalance(params[0], params[1] || 'latest');
1555
+
1556
+ case 'eth_getTransactionCount':
1557
+ return this._getTransactionCount(params[0], params[1] || 'latest');
1558
+
1559
+ case 'eth_getStorageAt':
1560
+ return this._getStorageAt(params[0], params[1], params[2] || 'latest');
1561
+
1562
+ case 'eth_estimateGas':
1563
+ // JAELIS has ZERO GAS - always return minimal
1564
+ return '0x5208'; // 21000 in hex
1565
+
1566
+ case 'eth_gasPrice':
1567
+ // JAELIS has ZERO GAS
1568
+ return '0x0';
1569
+
1268
1570
  // ═══════════════════════════════════════════════════════════════
1269
1571
  // JAELIS-SPECIFIC METHODS (Unified VM)
1270
1572
  // ═══════════════════════════════════════════════════════════════
1573
+ case 'jaelis_getCode':
1574
+ // Native JAELIS method - returns contract bytecode
1575
+ return this._getCode(params[0], params[1] || 'latest');
1576
+
1271
1577
  case 'jaelis_getHealth':
1272
1578
  return {
1273
1579
  status: 'healthy',
@@ -1307,6 +1613,45 @@ class EmbeddedRpcServer {
1307
1613
  case 'jaelis_getSupportedLanguages':
1308
1614
  return ['solidity', 'rust', 'move', 'func', 'cairo', 'vyper'];
1309
1615
 
1616
+ // ═══════════════════════════════════════════════════════════════
1617
+ // CROSS-CHAIN DEPLOYMENT METHODS (NEW!)
1618
+ // Query contract deployments across chains
1619
+ // ═══════════════════════════════════════════════════════════════
1620
+
1621
+ case 'jaelis_crossChain_getDeployment':
1622
+ // Get deployment info for an address
1623
+ // params: [address]
1624
+ return this._getCrossChainDeployment(params[0]);
1625
+
1626
+ case 'jaelis_crossChain_getChainDeployments':
1627
+ // Get all deployments to a specific chain
1628
+ // params: [chainId]
1629
+ return this._getChainDeployments(params[0]);
1630
+
1631
+ case 'jaelis_crossChain_getExplorerUrl':
1632
+ // Get explorer URL for a deployment
1633
+ // params: [chainId, address]
1634
+ return this._getExplorerUrl(params[0], params[1]);
1635
+
1636
+ case 'jaelis_crossChain_getDeploymentStats':
1637
+ // Get deployment statistics
1638
+ return this._getDeploymentStats();
1639
+
1640
+ // ═══════════════════════════════════════════════════════════════
1641
+ // MULTI-CHAIN SIGNATURE METHODS (Per-chain wallet authorization)
1642
+ // ═══════════════════════════════════════════════════════════════
1643
+
1644
+ case 'jaelis_crossChain_getSigningRequests':
1645
+ // Get signing messages for each target chain
1646
+ // params: [bytecode, targetChains[]]
1647
+ // Returns array of { chainId, message, switchParams } for frontend
1648
+ return this._getMultiChainSigningRequests(params[0], params[1]);
1649
+
1650
+ case 'jaelis_crossChain_deployWithSignatures':
1651
+ // Deploy with per-chain wallet signatures
1652
+ // params: [{ from, bytecode, abi, targetChains, chainSignatures: {chainId: sig} }]
1653
+ return this._deployWithMultiChainSignatures(params[0]);
1654
+
1310
1655
  // ═══════════════════════════════════════════════════════════════
1311
1656
  // CROSS-CHAIN SETTLEMENT METHODS (Universal State Layer!)
1312
1657
  // ═══════════════════════════════════════════════════════════════
@@ -1352,6 +1697,43 @@ class EmbeddedRpcServer {
1352
1697
  // Get cross-chain stats
1353
1698
  return this._getCrossChainStats();
1354
1699
 
1700
+ // ═══════════════════════════════════════════════════════════════
1701
+ // MCP SHORTHAND ALIASES (crosschain_* → jaelis_crossChain_*)
1702
+ // Unified RPC - same methods, shorter names for AI agents
1703
+ // ═══════════════════════════════════════════════════════════════
1704
+ case 'crosschain_getChains':
1705
+ return this._handleMethod('jaelis_crossChain_getChains', params);
1706
+ case 'crosschain_getChainInfo':
1707
+ return this._handleMethod('jaelis_crossChain_getChainInfo', params);
1708
+ case 'crosschain_readState':
1709
+ return this._handleMethod('jaelis_crossChain_readState', params);
1710
+ case 'crosschain_settleState':
1711
+ return this._handleMethod('jaelis_crossChain_settleState', params);
1712
+ case 'crosschain_getStateDiff':
1713
+ return this._handleMethod('jaelis_crossChain_getStateDiff', params);
1714
+ case 'crosschain_computeSlot':
1715
+ return this._handleMethod('jaelis_crossChain_computeSlot', params);
1716
+ case 'crosschain_getLightClientStatus':
1717
+ return this._handleMethod('jaelis_crossChain_getLightClientStatus', params);
1718
+ case 'crosschain_getSettlements':
1719
+ return this._handleMethod('jaelis_crossChain_getSettlements', params);
1720
+ case 'crosschain_getStats':
1721
+ return this._handleMethod('jaelis_crossChain_getStats', params);
1722
+ case 'crosschain_getDeployment':
1723
+ return this._handleMethod('jaelis_crossChain_getDeployment', params);
1724
+ case 'crosschain_getExplorerUrl':
1725
+ return this._handleMethod('jaelis_crossChain_getExplorerUrl', params);
1726
+ case 'crosschain_getDeploymentStats':
1727
+ return this._handleMethod('jaelis_crossChain_getDeploymentStats', params);
1728
+ case 'crosschain_getSigningRequests':
1729
+ return this._handleMethod('jaelis_crossChain_getSigningRequests', params);
1730
+ case 'crosschain_deployWithSignatures':
1731
+ return this._handleMethod('jaelis_crossChain_deployWithSignatures', params);
1732
+ case 'crosschain_deployUnified':
1733
+ return this._handleMethod('jaelis_deployUnified', params);
1734
+ case 'crosschain_syncState':
1735
+ return this._handleMethod('jaelis_crossChain_settleState', params);
1736
+
1355
1737
  // ═══════════════════════════════════════════════════════════════
1356
1738
  // LAYER 2: SOLANA COMPATIBILITY (PROXIES → jaelis_*)
1357
1739
  // For Rust/SPL developers familiar with Solana RPC
@@ -1574,6 +1956,160 @@ class EmbeddedRpcServer {
1574
1956
  return result.transactionHash || '0x0';
1575
1957
  }
1576
1958
 
1959
+ /**
1960
+ * Get contract bytecode at address
1961
+ *
1962
+ * CRITICAL: This is how block explorers detect contracts!
1963
+ * - Returns bytecode hex string if contract exists
1964
+ * - Returns '0x' if address is an EOA or doesn't exist
1965
+ *
1966
+ * Block explorers use: eth_getCode(address) !== '0x' → it's a contract!
1967
+ */
1968
+ async _getCode(address, blockTag = 'latest') {
1969
+ if (!this.blockchain) return '0x';
1970
+
1971
+ try {
1972
+ // Normalize address
1973
+ const normalizedAddr = address.toLowerCase();
1974
+
1975
+ // Try StateManager first (persistent LevelDB storage)
1976
+ if (this.blockchain.stateManager) {
1977
+ const code = await this.blockchain.stateManager.getCode(normalizedAddr);
1978
+ if (code && code.length > 0) {
1979
+ // Return as hex string
1980
+ if (Buffer.isBuffer(code)) {
1981
+ return '0x' + code.toString('hex');
1982
+ }
1983
+ if (typeof code === 'string') {
1984
+ return code.startsWith('0x') ? code : '0x' + code;
1985
+ }
1986
+ return '0x' + Buffer.from(code).toString('hex');
1987
+ }
1988
+ }
1989
+
1990
+ // Try VM Executor
1991
+ if (this.blockchain.vmExecutor?.stateManager) {
1992
+ const code = await this.blockchain.vmExecutor.stateManager.getCode(normalizedAddr);
1993
+ if (code && code.length > 0) {
1994
+ if (Buffer.isBuffer(code)) {
1995
+ return '0x' + code.toString('hex');
1996
+ }
1997
+ return code.startsWith('0x') ? code : '0x' + code;
1998
+ }
1999
+ }
2000
+
2001
+ // Try UnifiedVM contracts
2002
+ if (this.blockchain.unifiedVM?.contractManager) {
2003
+ const contract = this.blockchain.unifiedVM.contractManager.contracts.get(normalizedAddr);
2004
+ if (contract?.bytecode) {
2005
+ if (Buffer.isBuffer(contract.bytecode)) {
2006
+ return '0x' + contract.bytecode.toString('hex');
2007
+ }
2008
+ return contract.bytecode.startsWith('0x') ? contract.bytecode : '0x' + contract.bytecode;
2009
+ }
2010
+ }
2011
+
2012
+ // Try contracts Map directly
2013
+ if (this.blockchain.contracts) {
2014
+ const contract = this.blockchain.contracts.get(normalizedAddr);
2015
+ if (contract?.bytecode) {
2016
+ if (Buffer.isBuffer(contract.bytecode)) {
2017
+ return '0x' + contract.bytecode.toString('hex');
2018
+ }
2019
+ return contract.bytecode.startsWith('0x') ? contract.bytecode : '0x' + contract.bytecode;
2020
+ }
2021
+ }
2022
+
2023
+ // No bytecode found - it's an EOA or doesn't exist
2024
+ return '0x';
2025
+ } catch (error) {
2026
+ console.error(`[RPC] _getCode error for ${address}:`, error.message);
2027
+ return '0x';
2028
+ }
2029
+ }
2030
+
2031
+ /**
2032
+ * Get account balance
2033
+ */
2034
+ async _getBalance(address, blockTag = 'latest') {
2035
+ if (!this.blockchain) return '0x0';
2036
+
2037
+ try {
2038
+ const normalizedAddr = address.toLowerCase();
2039
+
2040
+ // Try StateManager
2041
+ if (this.blockchain.stateManager) {
2042
+ const account = await this.blockchain.stateManager.getAccount(normalizedAddr);
2043
+ if (account?.balance !== undefined) {
2044
+ const balance = BigInt(account.balance || 0);
2045
+ return '0x' + balance.toString(16);
2046
+ }
2047
+ }
2048
+
2049
+ // Default balance (JAELIS has no native token, but return 0)
2050
+ return '0x0';
2051
+ } catch (error) {
2052
+ return '0x0';
2053
+ }
2054
+ }
2055
+
2056
+ /**
2057
+ * Get transaction count (nonce)
2058
+ */
2059
+ async _getTransactionCount(address, blockTag = 'latest') {
2060
+ if (!this.blockchain) return '0x0';
2061
+
2062
+ try {
2063
+ const normalizedAddr = address.toLowerCase();
2064
+
2065
+ // Try StateManager
2066
+ if (this.blockchain.stateManager) {
2067
+ const account = await this.blockchain.stateManager.getAccount(normalizedAddr);
2068
+ if (account?.nonce !== undefined) {
2069
+ const nonce = BigInt(account.nonce || 0);
2070
+ return '0x' + nonce.toString(16);
2071
+ }
2072
+ }
2073
+
2074
+ return '0x0';
2075
+ } catch (error) {
2076
+ return '0x0';
2077
+ }
2078
+ }
2079
+
2080
+ /**
2081
+ * Get storage value at slot
2082
+ */
2083
+ async _getStorageAt(address, slot, blockTag = 'latest') {
2084
+ if (!this.blockchain) return '0x' + '0'.repeat(64);
2085
+
2086
+ try {
2087
+ const normalizedAddr = address.toLowerCase();
2088
+
2089
+ // Try StateManager
2090
+ if (this.blockchain.stateManager) {
2091
+ const value = await this.blockchain.stateManager.getStorage(normalizedAddr, slot);
2092
+ if (value !== undefined) {
2093
+ const bigValue = BigInt(value || 0);
2094
+ return '0x' + bigValue.toString(16).padStart(64, '0');
2095
+ }
2096
+ }
2097
+
2098
+ // Try VM Executor StateManager
2099
+ if (this.blockchain.vmExecutor?.stateManager) {
2100
+ const value = await this.blockchain.vmExecutor.stateManager.getStorage(normalizedAddr, slot);
2101
+ if (value !== undefined) {
2102
+ const bigValue = BigInt(value || 0);
2103
+ return '0x' + bigValue.toString(16).padStart(64, '0');
2104
+ }
2105
+ }
2106
+
2107
+ return '0x' + '0'.repeat(64);
2108
+ } catch (error) {
2109
+ return '0x' + '0'.repeat(64);
2110
+ }
2111
+ }
2112
+
1577
2113
  async _call(callObject) {
1578
2114
  if (!this.blockchain?.vmExecutor) throw new Error('VM not available');
1579
2115
  const result = await this.blockchain.vmExecutor.executeCall(
@@ -1602,13 +2138,17 @@ class EmbeddedRpcServer {
1602
2138
  * Compiles MULTIPLE contracts from MULTIPLE languages into ONE bytecode bundle.
1603
2139
  * All contracts share memory and can call each other DIRECTLY - no bridges!
1604
2140
  *
1605
- * @param {Object} params - { from, sources: [{ code, language, name }...] }
2141
+ * NOW WITH CROSS-CHAIN PROPAGATION:
2142
+ * - Contracts appear on ETH, SOL, and other block explorers!
2143
+ * - Shadow contracts on external chains proxy to JAELIS
2144
+ *
2145
+ * @param {Object} params - { from, sources: [{ code, language, name }...], targetChains?: number[] }
1606
2146
  */
1607
2147
  async _deployUnified(params) {
1608
2148
  if (!this.blockchain) throw new Error('Blockchain not available');
1609
2149
  if (!this.blockchain.unifiedVM) throw new Error('Unified VM not available');
1610
2150
 
1611
- const { from, sources } = params;
2151
+ const { from, sources, targetChains } = params;
1612
2152
 
1613
2153
  if (!sources || !Array.isArray(sources) || sources.length === 0) {
1614
2154
  throw new Error('sources array is required for unified deployment');
@@ -1617,13 +2157,95 @@ class EmbeddedRpcServer {
1617
2157
  console.log(`[RPC] Deploying unified bundle with ${sources.length} contracts...`);
1618
2158
  console.log(`[RPC] Languages: ${[...new Set(sources.map(s => s.language))].join(', ')}`);
1619
2159
 
1620
- // Compile all sources together using the Unified Compiler
1621
- const compiled = await this.blockchain.unifiedVM.compileBundle(sources);
2160
+ // Default target chains if not specified - ALL 30+ chains we support!
2161
+ const defaultTargetChains = [
2162
+ // EVM Mainnets
2163
+ 1, // Ethereum Mainnet
2164
+ 137, // Polygon PoS
2165
+ 42161, // Arbitrum One
2166
+ 42170, // Arbitrum Nova
2167
+ 10, // OP Mainnet
2168
+ 8453, // Base
2169
+ 56, // BNB Smart Chain
2170
+ 43114, // Avalanche C-Chain
2171
+ 250, // Fantom Opera
2172
+ 100, // Gnosis
2173
+ 59144, // Linea
2174
+ 324, // zkSync Era
2175
+ 534352, // Scroll
2176
+ 5000, // Mantle
2177
+ 81457, // Blast
2178
+
2179
+ // Solana
2180
+ 101, // Solana Mainnet
2181
+
2182
+ // Move chains (using type prefix to differentiate)
2183
+ // Note: Aptos/Sui use same IDs but different networks
2184
+
2185
+ // TON
2186
+ -1, // TON Mainnet
2187
+
2188
+ // StarkNet (using string identifier due to large chain ID)
2189
+ 'starknet-main', // StarkNet Mainnet
2190
+
2191
+ // Cosmos
2192
+ 118, // Cosmos Hub
2193
+ 119, // Osmosis
2194
+
2195
+ // Bitcoin
2196
+ 0 // Bitcoin Mainnet
2197
+ ];
2198
+ const chains = targetChains || defaultTargetChains;
2199
+
2200
+ console.log(`[RPC] Target chains: ${chains.join(', ')}`);
2201
+
2202
+ // Use the new deployUnifiedWithPropagation for cross-chain deployment
2203
+ if (this.blockchain.unifiedVM.deployUnifiedWithPropagation) {
2204
+ const result = await this.blockchain.unifiedVM.deployUnifiedWithPropagation({
2205
+ from,
2206
+ sources,
2207
+ targetChains: chains
2208
+ });
1622
2209
 
1623
- // Deploy the unified bytecode
2210
+ // Build contracts map for response
2211
+ const contractsMap = {};
2212
+ for (const source of sources) {
2213
+ contractsMap[source.name] = {
2214
+ address: result.address,
2215
+ language: source.language,
2216
+ name: source.name
2217
+ };
2218
+ }
2219
+
2220
+ console.log(`[RPC] Unified bundle deployed at ${result.address}`);
2221
+ console.log(`[RPC] Chain deployments: ${result.deployments?.length || 0}`);
2222
+
2223
+ // Log explorer URLs
2224
+ if (result.explorerUrls) {
2225
+ console.log(`[RPC] Explorer URLs:`);
2226
+ for (const [chainId, url] of Object.entries(result.explorerUrls)) {
2227
+ console.log(`[RPC] Chain ${chainId}: ${url}`);
2228
+ }
2229
+ }
2230
+
2231
+ return {
2232
+ address: result.address,
2233
+ bytecode: result.bytecode,
2234
+ abi: result.abi || [],
2235
+ contracts: contractsMap,
2236
+ deployments: result.deployments,
2237
+ explorerUrls: result.explorerUrls,
2238
+ metadata: result.metadata,
2239
+ gasUsed: 0 // ZERO FEES!
2240
+ };
2241
+ }
2242
+
2243
+ // Fallback: original local-only deployment
2244
+ console.log(`[RPC] Fallback: local-only deployment (no cross-chain propagation)`);
2245
+
2246
+ const compiled = await this.blockchain.unifiedVM.compileBundle(sources);
1624
2247
  const address = '0x' + require('crypto').randomBytes(20).toString('hex');
1625
2248
 
1626
- // Store the unified contract
1627
2249
  if (this.blockchain.contracts) {
1628
2250
  this.blockchain.contracts.set(address, {
1629
2251
  bytecode: compiled.bytecode,
@@ -1635,15 +2257,10 @@ class EmbeddedRpcServer {
1635
2257
  });
1636
2258
  }
1637
2259
 
1638
- console.log(`[RPC] Unified bundle deployed at ${address}`);
1639
- console.log(`[RPC] Bytecode: ${compiled.bytecode?.length || 0} bytes`);
1640
- console.log(`[RPC] Cross-references: ${compiled.crossReferences?.size || 0}`);
1641
-
1642
- // Build contracts map for response
1643
2260
  const contractsMap = {};
1644
2261
  for (const source of sources) {
1645
2262
  contractsMap[source.name] = {
1646
- address: address, // All share the same bundle address
2263
+ address: address,
1647
2264
  language: source.language,
1648
2265
  name: source.name
1649
2266
  };
@@ -1798,6 +2415,131 @@ class EmbeddedRpcServer {
1798
2415
  };
1799
2416
  }
1800
2417
 
2418
+ // ═══════════════════════════════════════════════════════════════
2419
+ // CROSS-CHAIN DEPLOYMENT QUERY IMPLEMENTATIONS
2420
+ // ═══════════════════════════════════════════════════════════════
2421
+
2422
+ _getCrossChainDeployment(address) {
2423
+ if (this.blockchain?.unifiedVM?.deployManager?.getDeployment) {
2424
+ const deployment = this.blockchain.unifiedVM.deployManager.getDeployment(address);
2425
+ if (deployment) {
2426
+ return deployment;
2427
+ }
2428
+ }
2429
+ return { error: 'Deployment not found', address };
2430
+ }
2431
+
2432
+ _getChainDeployments(chainId) {
2433
+ if (this.blockchain?.unifiedVM?.deployManager?.getChainDeployments) {
2434
+ return this.blockchain.unifiedVM.deployManager.getChainDeployments(chainId);
2435
+ }
2436
+ return [];
2437
+ }
2438
+
2439
+ _getExplorerUrl(chainId, address) {
2440
+ if (this.blockchain?.unifiedVM?.deployManager?.getExplorerUrl) {
2441
+ const url = this.blockchain.unifiedVM.deployManager.getExplorerUrl(chainId, address);
2442
+ return { chainId, address, url };
2443
+ }
2444
+ return { chainId, address, url: null, error: 'Deploy manager not available' };
2445
+ }
2446
+
2447
+ _getDeploymentStats() {
2448
+ if (this.blockchain?.unifiedVM?.deployManager?.getStats) {
2449
+ return this.blockchain.unifiedVM.deployManager.getStats();
2450
+ }
2451
+ return {
2452
+ totalDeployments: 0,
2453
+ pendingCount: 0,
2454
+ failedCount: 0,
2455
+ chainStats: {}
2456
+ };
2457
+ }
2458
+
2459
+ // ═══════════════════════════════════════════════════════════════
2460
+ // MULTI-CHAIN SIGNATURE IMPLEMENTATIONS
2461
+ // For external chains to accept deployments, user signs per-chain
2462
+ // ═══════════════════════════════════════════════════════════════
2463
+
2464
+ /**
2465
+ * Get signing requests for each target chain
2466
+ *
2467
+ * Frontend flow:
2468
+ * 1. Get these messages
2469
+ * 2. For each chain, switch wallet network and sign
2470
+ * 3. Collect all signatures
2471
+ * 4. Call deployWithSignatures
2472
+ */
2473
+ _getMultiChainSigningRequests(bytecode, targetChains) {
2474
+ if (this.blockchain?.unifiedVM?.deployManager?.getMultiChainSigningRequests) {
2475
+ return this.blockchain.unifiedVM.deployManager.getMultiChainSigningRequests(bytecode, targetChains);
2476
+ }
2477
+
2478
+ // Fallback implementation
2479
+ const crypto = require('crypto');
2480
+ const bytecodeHash = crypto.createHash('sha256')
2481
+ .update(bytecode || '')
2482
+ .digest('hex')
2483
+ .substring(0, 16);
2484
+
2485
+ const timestamp = Date.now();
2486
+ const requests = [];
2487
+
2488
+ for (const chainId of (targetChains || [])) {
2489
+ const message = `JAELIS Cross-Chain Deploy Authorization
2490
+
2491
+ Chain ID: ${chainId}
2492
+ Bytecode Hash: ${bytecodeHash}
2493
+ Timestamp: ${timestamp}
2494
+
2495
+ I authorize JAELIS to deploy this contract.
2496
+ ZERO FEES - No gas cost to me.`;
2497
+
2498
+ requests.push({
2499
+ chainId,
2500
+ message,
2501
+ messageHash: '0x' + crypto.createHash('sha256')
2502
+ .update(`\x19Ethereum Signed Message:\n${message.length}${message}`)
2503
+ .digest('hex'),
2504
+ switchParams: { chainId: '0x' + chainId.toString(16) }
2505
+ });
2506
+ }
2507
+
2508
+ return { requests, bytecodeHash, timestamp, totalChains: targetChains?.length || 0 };
2509
+ }
2510
+
2511
+ /**
2512
+ * Deploy with per-chain wallet signatures
2513
+ *
2514
+ * Each chain that needs to recognize the deployment
2515
+ * requires its own signature from the deployer's wallet.
2516
+ */
2517
+ async _deployWithMultiChainSignatures(params) {
2518
+ const { from, bytecode, abi, targetChains, chainSignatures, metadata } = params;
2519
+
2520
+ console.log(`[RPC] Multi-chain signature deployment from ${from}`);
2521
+ console.log(`[RPC] Target chains: ${targetChains?.length || 0}`);
2522
+ console.log(`[RPC] Signatures provided: ${Object.keys(chainSignatures || {}).length}`);
2523
+
2524
+ if (this.blockchain?.unifiedVM?.deployManager?.deployWithMultiChainSignatures) {
2525
+ return await this.blockchain.unifiedVM.deployManager.deployWithMultiChainSignatures({
2526
+ from,
2527
+ bytecode,
2528
+ abi,
2529
+ targetChains,
2530
+ chainSignatures,
2531
+ metadata
2532
+ });
2533
+ }
2534
+
2535
+ // Fallback: regular deployment without chain signatures
2536
+ console.log(`[RPC] Fallback: Deploy manager not available, using standard flow`);
2537
+ return {
2538
+ error: 'Multi-chain signatures not supported',
2539
+ message: 'Deploy manager not initialized'
2540
+ };
2541
+ }
2542
+
1801
2543
  async stop() {
1802
2544
  if (this.server) {
1803
2545
  this.server.close();