chore: initial commit for v0.0.1
DChain single-node blockchain + React Native messenger client. Core: - PBFT consensus with multi-sig validator admission + equivocation slashing - BadgerDB + schema migration scaffold (CurrentSchemaVersion=0) - libp2p gossipsub (tx/v1, blocks/v1, relay/v1, version/v1) - Native Go contracts (username_registry) alongside WASM (wazero) - WebSocket gateway with topic-based fanout + Ed25519-nonce auth - Relay mailbox with NaCl envelope encryption (X25519 + Ed25519) - Prometheus /metrics, per-IP rate limit, body-size cap Deployment: - Single-node compose (deploy/single/) with Caddy TLS + optional Prometheus - 3-node dev compose (docker-compose.yml) with mocked internet topology - 3-validator prod compose (deploy/prod/) for federation - Auto-update from Gitea via /api/update-check + systemd timer - Build-time version injection (ldflags → node --version) - UI / Swagger toggle flags (DCHAIN_DISABLE_UI, DCHAIN_DISABLE_SWAGGER) Client (client-app/): - Expo / React Native / NativeWind - E2E NaCl encryption, typing indicator, contact requests - Auto-discovery of canonical contracts, chain_id aware, WS reconnect on node switch Documentation: - README.md, CHANGELOG.md, CONTEXT.md - deploy/single/README.md with 6 operator scenarios - deploy/UPDATE_STRATEGY.md with 4-layer forward-compat design - docs/contracts/*.md per contract
This commit is contained in:
110
node/explorer/node.js
Normal file
110
node/explorer/node.js
Normal file
@@ -0,0 +1,110 @@
|
||||
(function() {
|
||||
var C = window.ExplorerCommon;
|
||||
|
||||
function badge(text, variant) {
|
||||
return '<span class="addr-badge addr-badge-' + variant + '">' + C.esc(text) + '</span>';
|
||||
}
|
||||
|
||||
function renderNode(data) {
|
||||
document.title = 'Node ' + C.short(data.address || data.pub_key || '', 16) + ' | DChain Explorer';
|
||||
|
||||
// Profile card
|
||||
document.getElementById('nodeNickname').textContent = data.address ? C.short(data.address, 24) : 'Node';
|
||||
document.getElementById('nodeAddressShort').textContent = data.pub_key ? C.short(data.pub_key, 32) : '—';
|
||||
document.getElementById('nodeBalanceVal').textContent = data.node_balance || C.toToken(data.node_balance_ut || 0);
|
||||
document.getElementById('nodeReputationRank').textContent = 'Rank: ' + (data.reputation_rank || '—');
|
||||
|
||||
// Badges
|
||||
var badges = '';
|
||||
if (data.blocks_produced > 0) badges += badge('Validator', 'accent');
|
||||
if (data.relay_proofs > 0) badges += badge('Relay Node', 'relay');
|
||||
if (!badges) badges = badge('Observer', 'muted');
|
||||
document.getElementById('nodeBadges').innerHTML = badges;
|
||||
|
||||
// Address field
|
||||
var addrEl = document.getElementById('nodeAddress');
|
||||
addrEl.textContent = data.address || '—';
|
||||
addrEl.href = data.pub_key ? '/address?address=' + encodeURIComponent(data.pub_key) : '#';
|
||||
document.getElementById('nodeAddressRaw').textContent = data.address || '';
|
||||
|
||||
// PubKey field
|
||||
document.getElementById('nodePubKey').textContent = data.pub_key || '—';
|
||||
|
||||
// Wallet binding
|
||||
if (data.wallet_binding_address) {
|
||||
var link = document.getElementById('bindingLink');
|
||||
link.textContent = data.wallet_binding_address;
|
||||
link.href = '/address?address=' + encodeURIComponent(data.wallet_binding_pub_key || data.wallet_binding_address);
|
||||
document.getElementById('bindingBalance').textContent = data.wallet_binding_balance || '—';
|
||||
document.getElementById('bindingRow').style.display = '';
|
||||
} else {
|
||||
document.getElementById('bindingRow').style.display = 'none';
|
||||
}
|
||||
|
||||
// Stats
|
||||
document.getElementById('repScore').textContent = String(data.reputation_score || 0);
|
||||
document.getElementById('repRank').textContent = data.reputation_rank || '—';
|
||||
document.getElementById('repBlocks').textContent = String(data.blocks_produced || 0);
|
||||
var rw = data.recent_window_blocks || 0;
|
||||
var rp = data.recent_blocks_produced || 0;
|
||||
document.getElementById('recentProduced').textContent = 'last ' + rw + ' blocks: ' + rp;
|
||||
document.getElementById('repRelay').textContent = String(data.relay_proofs || 0);
|
||||
document.getElementById('repHeartbeats').textContent = String(data.heartbeats || 0);
|
||||
var slash = data.slash_count || 0;
|
||||
document.getElementById('repSlash').textContent = slash > 0 ? slash + ' slashes' : '';
|
||||
|
||||
// Rewards
|
||||
document.getElementById('recentRewards').textContent = data.recent_rewards || C.toToken(data.recent_rewards_ut || 0);
|
||||
document.getElementById('windowBlocks').textContent = 'window: last ' + rw + ' blocks';
|
||||
document.getElementById('lifetimeReward').textContent = data.lifetime_base_reward || C.toToken(0);
|
||||
document.getElementById('nodeBalance').textContent = data.node_balance || C.toToken(data.node_balance_ut || 0);
|
||||
|
||||
document.getElementById('mainContent').style.display = '';
|
||||
C.refreshIcons();
|
||||
}
|
||||
|
||||
async function loadNode(nodeID) {
|
||||
if (!nodeID) return;
|
||||
C.setStatus('Loading…', 'warn');
|
||||
document.getElementById('mainContent').style.display = 'none';
|
||||
try {
|
||||
var data = await C.fetchJSON('/api/node/' + encodeURIComponent(nodeID) + '?window=300');
|
||||
renderNode(data);
|
||||
window.history.replaceState({}, '', '/node?node=' + encodeURIComponent(nodeID));
|
||||
C.setStatus('', '');
|
||||
} catch (e) {
|
||||
C.setStatus('Load failed: ' + e.message, 'err');
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Copy buttons ────────────────────────────────────────────────────────── */
|
||||
document.addEventListener('click', function(e) {
|
||||
var t = e.target;
|
||||
if (!t) return;
|
||||
var btn = t.closest ? t.closest('.copy-btn') : null;
|
||||
if (btn) {
|
||||
var src = document.getElementById(btn.dataset.copyId);
|
||||
if (src) {
|
||||
navigator.clipboard.writeText(src.textContent || '').catch(function() {});
|
||||
btn.classList.add('copy-btn-done');
|
||||
setTimeout(function() { btn.classList.remove('copy-btn-done'); }, 1200);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
/* ── Wiring ────────────────────────────────────────────────────────────── */
|
||||
document.getElementById('nodeBtn').addEventListener('click', function() {
|
||||
var val = (document.getElementById('nodeInput').value || '').trim();
|
||||
if (val) loadNode(val);
|
||||
});
|
||||
|
||||
document.getElementById('nodeInput').addEventListener('keydown', function(e) {
|
||||
if (e.key === 'Enter') document.getElementById('nodeBtn').click();
|
||||
});
|
||||
|
||||
var initial = C.q('node') || C.q('pk');
|
||||
if (initial) {
|
||||
document.getElementById('nodeInput').value = initial;
|
||||
loadNode(initial);
|
||||
}
|
||||
})();
|
||||
Reference in New Issue
Block a user