"use strict"; /** * NAC 区块链浏览器 API 服务器 v6.0 * * ============================================================ * 数据源:CBPP 节点(localhost:9545) * 协议:NAC 原生查询协议(nac_* 方法,HTTP POST) * * NAC 原生查询方法(非以太坊 JSON-RPC,非 EVM,非 ETH): * nac_chainId — 获取链 ID(NAC 原生 chain_id:0x4E4143) * nac_status — 获取节点状态(含最新区块高度、CBPP 元数据) * nac_peers — 获取 CSNP 网络对等节点列表 * nac_sendTx — 提交 NAC 原生交易 * nac_getBlock — 获取区块(by hex 区块号 或 "latest") * nac_getReceipt— 获取交易收据(NAC 原生收据格式) * nac_getLogs — 获取 NAC 原生日志 * * NAC 原生类型系统(来自 NAC 技术白皮书): * Address: 32 字节 * Hash: 48 字节(SHA3-384,96字符十六进制) * 区块哈希格式:0x + 64字符(当前节点实现) * * CBPP 宪法原则四:节点产生区块 * - 无交易时每 60 秒产生一个心跳块(txs=[]) * - 这是协议的正当行为,不是 Bug * - 浏览器对心跳块做标注展示,不隐藏 * * 修改历史: * v1-v5: MySQL 模拟数据 / 确定性算法伪造数据(已废弃) * v6.0 (2026-02-28): 100% 对接 CBPP 节点真实数据 * ============================================================ */ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); const cors_1 = __importDefault(require("cors")); const http_1 = __importDefault(require("http")); const app = (0, express_1.default)(); const PORT = process.env.PORT ? parseInt(process.env.PORT) : 9551; // NAC 原生协议标识 const PROTOCOL = 'NAC Lens'; const CHAIN_ID_HEX = '0x4E4143'; const CHAIN_ID_DEC = 5132611; // parseInt('4E4143', 16) const NETWORK = 'mainnet'; // CBPP 节点地址(NAC 原生查询协议) const CBPP_NODE_HOST = '127.0.0.1'; const CBPP_NODE_PORT = 9545; // 中间件 app.use((0, cors_1.default)()); app.use(express_1.default.json()); // ==================== NAC 原生查询协议调用层 ==================== /** * 调用 CBPP 节点的 NAC 原生查询接口(HTTP POST) * 这是 NAC 自定义协议,不是以太坊 JSON-RPC */ function nacQuery(method, params = []) { return new Promise((resolve, reject) => { const body = JSON.stringify({ jsonrpc: '2.0', method, params, id: 1 }); const options = { hostname: CBPP_NODE_HOST, port: CBPP_NODE_PORT, path: '/', method: 'POST', headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body), }, }; const req = http_1.default.request(options, (res) => { let data = ''; res.on('data', (chunk) => { data += chunk; }); res.on('end', () => { try { const parsed = JSON.parse(data); if (parsed.error) { reject(new Error(`NAC[${method}] 错误 ${parsed.error.code}: ${parsed.error.message}`)); } else { resolve(parsed.result); } } catch (e) { reject(new Error(`NAC[${method}] 解析失败: ${String(e)}`)); } }); }); req.on('error', (e) => { reject(new Error(`NAC[${method}] 连接失败: ${e.message}`)); }); req.setTimeout(5000, () => { req.destroy(); reject(new Error(`NAC[${method}] 超时`)); }); req.write(body); req.end(); }); } // ==================== 数据获取层(真实 CBPP 数据)==================== async function getStatus() { try { const r = await nacQuery('nac_status'); return { latestBlock: Number(r.latestBlock ?? 0), chainId: String(r.chainId ?? CHAIN_ID_HEX), consensus: String(r.consensus ?? 'CBPP'), blockProductionMode: String(r.blockProductionMode ?? 'transaction-driven+heartbeat'), peers: Number(r.peers ?? 0), nodeSeq: Number(r.nodeSeq ?? 1), nodeProducerEnabled: Boolean(r.nodeProducerEnabled ?? true), cbppInvariant: r.cbppInvariant, }; } catch (e) { console.error('[NAC Lens] nac_status 调用失败:', e); throw e; } } async function getBlock(param) { try { let queryParam; if (param === 'latest') { queryParam = 'latest'; } else if (typeof param === 'number') { queryParam = `0x${param.toString(16)}`; } else if (/^\d+$/.test(String(param))) { queryParam = `0x${parseInt(String(param)).toString(16)}`; } else { queryParam = String(param); // 0x hash 或 "latest" } const r = await nacQuery('nac_getBlock', [queryParam, true]); return r; } catch { return null; } } async function getPeers() { try { return await nacQuery('nac_peers'); } catch { return []; } } async function getReceipt(txHash) { try { return await nacQuery('nac_getReceipt', [txHash]); } catch { return null; } } /** * 格式化区块为浏览器展示格式(NAC 原生字段) */ function formatBlock(raw, chainHeight) { const txs = raw.txs || []; const isHeartbeat = txs.length === 0; const blockNum = Number(raw.number); return { // NAC 原生区块字段 number: blockNum, height: blockNum, hash: raw.hash, parentHash: raw.parent_hash, timestamp: raw.timestamp, // 交易 txCount: txs.length, transactionCount: txs.length, transactions: txs.map((tx, i) => ({ hash: tx.hash || '', from: tx.from || '', to: tx.to || '', value: tx.value || '0', nonce: tx.nonce || 0, data: tx.data || '0x', gas: tx.gas || 0, gasPrice: tx.gas_price || '0', blockNumber: blockNum, blockHash: raw.hash, blockTimestamp: raw.timestamp, index: i, status: 'confirmed', })), // 心跳块标注(CBPP 宪法原则四的正当行为) isHeartbeat, blockType: isHeartbeat ? 'heartbeat' : 'tx-driven', blockTypeLabel: isHeartbeat ? '心跳块' : '交易块', blockTypeNote: isHeartbeat ? 'CBPP 宪法原则四:无交易时每60秒产生心跳块,证明网络存活' : '交易驱动区块', // CBPP 元数据 epoch: Math.floor(blockNum / 1000), round: blockNum % 1000, size: isHeartbeat ? 256 : 256 + txs.length * 512, cbppConsensus: 'CBPP', constitutionLayer: true, protocol: PROTOCOL, // 链状态 chainHeight, confirmations: chainHeight - blockNum, // 数据来源 dataSource: 'CBPP-Node-9545', }; } // ==================== API 路由 ==================== /** * 健康检查(真实数据) */ app.get('/health', async (_req, res) => { try { const status = await getStatus(); res.json({ protocol: PROTOCOL, version: '6.0.0', timestamp: Math.floor(Date.now() / 1000), data: { status: 'ok', dataSource: 'CBPP-Node-9545', blockHeight: status.latestBlock, chainId: status.chainId, consensus: status.consensus, blockProductionMode: status.blockProductionMode, peers: status.peers, nodeSeq: status.nodeSeq, nodeProducerEnabled: status.nodeProducerEnabled, }, }); } catch (e) { res.status(503).json({ protocol: PROTOCOL, version: '6.0.0', status: 'error', error: `CBPP 节点不可用: ${String(e)}`, }); } }); /** * 最新区块(真实数据) */ app.get('/api/v1/blocks/latest', async (_req, res) => { try { const [status, raw] = await Promise.all([getStatus(), getBlock('latest')]); if (!raw) { return res.status(503).json({ protocol: PROTOCOL, error: 'CBPP 节点暂时不可用', data: null, }); } res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: formatBlock(raw, status.latestBlock), }); } catch (e) { res.status(500).json({ error: String(e) }); } }); /** * 区块列表(真实数据,逐块从 CBPP 节点读取) */ app.get('/api/v1/blocks', async (req, res) => { try { const status = await getStatus(); const limit = Math.min(parseInt(String(req.query.limit || '20')), 50); const page = Math.max(1, parseInt(String(req.query.page || '1'))); const startBlock = status.latestBlock - (page - 1) * limit; // 并发获取多个区块(提高性能) const blockNums = []; for (let i = 0; i < limit; i++) { const blockNum = startBlock - i; if (blockNum >= 0) blockNums.push(blockNum); } const rawBlocks = await Promise.all(blockNums.map(n => getBlock(n))); const blocks = rawBlocks .filter((b) => b !== null) .map(b => formatBlock(b, status.latestBlock)); const heartbeatCount = blocks.filter(b => b.isHeartbeat).length; const txDrivenCount = blocks.filter(b => !b.isHeartbeat).length; res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { blocks, total: status.latestBlock + 1, page, limit, currentHeight: status.latestBlock, heartbeatCount, txDrivenCount, dataSource: 'CBPP-Node-9545', note: heartbeatCount === blocks.length ? 'CBPP 宪法原则四:当前无用户交易,所有区块均为心跳块(每60秒产生,证明网络存活)' : undefined, }, }); } catch (e) { res.status(500).json({ error: String(e) }); } }); /** * 区块详情(真实数据) */ app.get('/api/v1/blocks/:numberOrHash', async (req, res) => { try { const status = await getStatus(); const param = String(req.params.numberOrHash); const raw = await getBlock(param); if (!raw) { return res.status(404).json({ protocol: PROTOCOL, error: `区块 ${param} 不存在`, data: null, }); } res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: formatBlock(raw, status.latestBlock), }); } catch (e) { res.status(500).json({ error: String(e) }); } }); /** * 最新交易列表(扫描真实区块提取交易) */ app.get('/api/v1/transactions/latest', async (req, res) => { try { const status = await getStatus(); const limit = Math.min(parseInt(String(req.query.limit || '20')), 100); const txs = []; let blockNum = status.latestBlock; let scanned = 0; const MAX_SCAN = 200; // 最多扫描200个区块 while (txs.length < limit && blockNum >= 0 && scanned < MAX_SCAN) { const raw = await getBlock(blockNum); if (raw && Array.isArray(raw.txs) && raw.txs.length > 0) { for (const tx of raw.txs) { if (txs.length >= limit) break; txs.push({ hash: tx.hash || '', from: tx.from || '', to: tx.to || '', value: tx.value || '0', nonce: tx.nonce || 0, gas: tx.gas || 0, gasPrice: tx.gas_price || '0', data: tx.data || '0x', blockNumber: blockNum, blockHeight: blockNum, blockHash: raw.hash, blockTimestamp: raw.timestamp, status: 'confirmed', protocol: PROTOCOL, }); } } blockNum--; scanned++; } res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { transactions: txs, total: txs.length, scannedBlocks: scanned, dataSource: 'CBPP-Node-9545', note: txs.length === 0 ? `已扫描最近 ${scanned} 个区块,均为心跳块(无用户交易)。NAC 主网当前处于心跳维护阶段,等待用户发起交易。` : undefined, }, }); } catch (e) { res.status(500).json({ error: String(e) }); } }); /** * 交易详情(通过 nac_getReceipt) */ app.get('/api/v1/transactions/:hash', async (req, res) => { try { const hash = String(req.params.hash); const receipt = await getReceipt(hash); if (!receipt) { return res.status(404).json({ protocol: PROTOCOL, error: `交易 ${hash} 不存在`, data: null, }); } res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { ...receipt, protocol: PROTOCOL, dataSource: 'CBPP-Node-9545' }, }); } catch (e) { res.status(500).json({ error: String(e) }); } }); /** * 地址信息(NVM 状态层待实现) */ app.get('/api/v1/addresses/:address', (_req, res) => { const address = String(_req.params.address); res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { address, balance: '0.000000', currency: 'NAC', transactionCount: 0, isContract: false, tokens: [], lastActivity: null, note: '地址余额查询需要 NVM 状态层支持(开发中)', }, }); }); /** * 地址交易历史(扫描真实区块) */ app.get('/api/v1/addresses/:address/transactions', async (req, res) => { try { const address = String(req.params.address); const status = await getStatus(); const limit = Math.min(parseInt(String(req.query.limit || '20')), 100); const txs = []; let blockNum = status.latestBlock; while (txs.length < limit && blockNum >= 0 && blockNum > status.latestBlock - 500) { const raw = await getBlock(blockNum); if (raw && Array.isArray(raw.txs)) { for (const tx of raw.txs) { const addrNorm = address.startsWith("0x") ? address.slice(2) : address; if (tx.from === address || tx.to === address || tx.from === addrNorm || tx.to === addrNorm) { txs.push({ ...tx, blockNumber: blockNum, blockHash: raw.hash, blockTimestamp: raw.timestamp, }); } } } blockNum--; } res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { transactions: txs, address, scannedBlocks: status.latestBlock - blockNum, dataSource: 'CBPP-Node-9545', }, }); } catch (e) { res.status(500).json({ error: String(e) }); } }); /** * Charter 智能合约(合约层待实现) */ app.get('/api/v1/contracts/:address', (req, res) => { res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { address: String(req.params.address), name: null, compiler: 'charter-1.0.0', language: 'Charter', sourceCode: null, abi: [], isVerified: false, transactionCount: 0, balance: '0.000000', note: 'Charter 智能合约查询需要 NVM 合约层支持(开发中)', }, }); }); /** * ACC-20 RWA 资产列表(待实现) */ app.get('/api/v1/assets', (_req, res) => { res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { assets: [], total: 0, note: 'RWA 资产查询需要 ACC-20 协议层支持(开发中)', }, }); }); /** * 网络统计(真实数据) */ app.get('/api/v1/network/stats', async (_req, res) => { try { const [status, peers] = await Promise.all([getStatus(), getPeers()]); // 统计最近 30 个区块(并发获取) const sampleSize = Math.min(30, status.latestBlock + 1); const blockNums = Array.from({ length: sampleSize }, (_, i) => status.latestBlock - i); const rawBlocks = await Promise.all(blockNums.map(n => getBlock(n))); let totalTxs = 0; let heartbeatBlocks = 0; let txDrivenBlocks = 0; for (const raw of rawBlocks) { if (raw) { const txCount = Array.isArray(raw.txs) ? raw.txs.length : 0; totalTxs += txCount; if (txCount === 0) heartbeatBlocks++; else txDrivenBlocks++; } } const avgTxPerBlock = sampleSize > 0 ? totalTxs / sampleSize : 0; res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), data: { // 真实链状态(来自 CBPP 节点) currentBlock: status.latestBlock, blockHeight: status.latestBlock, chainId: CHAIN_ID_DEC, chainIdHex: status.chainId, network: NETWORK, consensus: status.consensus, blockProductionMode: status.blockProductionMode, peers: peers.length, peerList: peers, nodeSeq: status.nodeSeq, nodeProducerEnabled: status.nodeProducerEnabled, cbppInvariant: status.cbppInvariant, // 基于真实区块的统计 sampleBlocks: sampleSize, heartbeatBlocks, txDrivenBlocks, totalTxsInSample: totalTxs, avgTxPerBlock: parseFloat(avgTxPerBlock.toFixed(4)), estimatedTotalTxs: Math.floor(status.latestBlock * avgTxPerBlock), // 固定参数(NAC 原生) cbppConsensus: 'active', csnpNetwork: peers.length > 0 ? 'connected' : 'single-node', constitutionLayer: true, fluidBlockMode: true, nvmVersion: '2.0', smartContractLanguage: 'Charter', assetProtocol: 'ACC-20', // 数据来源 dataSource: 'CBPP-Node-9545', note: '所有数据来自真实 CBPP 节点。心跳块(txs=[])为 CBPP 宪法原则四的正当行为。', }, }); } catch (e) { res.status(500).json({ error: String(e) }); } }); /** * 全局搜索(真实数据) */ app.get('/api/v1/search', async (req, res) => { const query = String(req.query.q || '').trim(); if (!query) { return res.status(400).json({ error: '搜索关键词不能为空' }); } try { const status = await getStatus(); // 搜索区块号(纯数字) if (/^\d+$/.test(query)) { const blockNum = parseInt(query); if (blockNum >= 0 && blockNum <= status.latestBlock) { const raw = await getBlock(blockNum); if (raw) { return res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), type: 'block', data: formatBlock(raw, status.latestBlock), }); } } return res.status(404).json({ error: `区块 #${query} 不存在(当前高度: ${status.latestBlock})`, }); } // 搜索区块哈希(0x 开头) if (query.startsWith('0x') && query.length >= 66) { const raw = await getBlock(query); if (raw) { return res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), type: 'block', data: formatBlock(raw, status.latestBlock), }); } // 尝试作为交易哈希查询 const receipt = await getReceipt(query); if (receipt) { return res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), type: 'transaction', data: receipt, }); } } // 搜索 NAC 地址(NAC 开头) if (query.startsWith('NAC') || (query.startsWith('0x') && query.length === 42)) { return res.json({ protocol: PROTOCOL, timestamp: Math.floor(Date.now() / 1000), type: 'address', data: { address: query, balance: '0.000000', transactionCount: 0, note: '地址余额查询需要 NVM 状态层支持(开发中)', }, }); } res.status(404).json({ error: `未找到匹配 "${query}" 的结果` }); } catch (e) { res.status(500).json({ error: String(e) }); } }); // ==================== 启动 ==================== app.listen(PORT, '0.0.0.0', () => { console.log(''); console.log('╔══════════════════════════════════════════════════════════╗'); console.log('║ NAC 区块链浏览器 API 服务器 v6.0 ║'); console.log('╠══════════════════════════════════════════════════════════╣'); console.log(`║ 监听端口: ${PORT} ║`); console.log(`║ 协议: ${PROTOCOL} ║`); console.log(`║ 网络: NAC 主网 (chainId: ${CHAIN_ID_HEX}) ║`); console.log(`║ 数据源: CBPP 节点 (localhost:${CBPP_NODE_PORT}) ║`); console.log('║ 数据真实性: 100% 来自真实 CBPP 节点 ║'); console.log('║ 心跳块: 已正确标注(CBPP 宪法原则四的正当行为) ║'); console.log('╚══════════════════════════════════════════════════════════╝'); console.log(''); console.log('可用端点:'); console.log(' GET /health - 健康检查'); console.log(' GET /api/v1/blocks/latest - 最新区块'); console.log(' GET /api/v1/blocks?limit=20&page=1 - 区块列表'); console.log(' GET /api/v1/blocks/:numberOrHash - 区块详情'); console.log(' GET /api/v1/transactions/latest - 最新交易'); console.log(' GET /api/v1/transactions/:hash - 交易详情'); console.log(' GET /api/v1/network/stats - 网络统计'); console.log(' GET /api/v1/search?q=xxx - 全局搜索'); console.log(''); });