Files
dchain/node/explorer/node.js
vsecoder 7e7393e4f8 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
2026-04-17 14:16:44 +03:00

111 lines
5.1 KiB
JavaScript

(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);
}
})();