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
69 lines
2.5 KiB
JavaScript
69 lines
2.5 KiB
JavaScript
(function() {
|
|
var C = window.ExplorerCommon;
|
|
|
|
function fmtFee(ut) {
|
|
if (!ut) return '<span class="text-muted">Free</span>';
|
|
if (ut < 1000) return ut + ' µT';
|
|
return C.toToken(ut);
|
|
}
|
|
|
|
async function loadRelays() {
|
|
C.setStatus('Loading…', 'warn');
|
|
try {
|
|
var data = await C.fetchJSON('/api/relays');
|
|
var relays = Array.isArray(data) ? data : [];
|
|
|
|
document.getElementById('relayCount').textContent = relays.length;
|
|
|
|
var tbody = document.getElementById('relayBody');
|
|
if (!relays.length) {
|
|
tbody.innerHTML = '<tr><td colspan="6" class="tbl-empty">No relay nodes registered yet.</td></tr>';
|
|
C.setStatus('No relay nodes found.', 'warn');
|
|
return;
|
|
}
|
|
|
|
var rows = '';
|
|
relays.forEach(function(info, i) {
|
|
var pubKey = info.pub_key || '';
|
|
var addr = info.address || '—';
|
|
var x25519 = (info.relay && info.relay.x25519_pub_key) || '—';
|
|
var feeUT = (info.relay && info.relay.fee_per_msg_ut) || 0;
|
|
var multiaddr = (info.relay && info.relay.multiaddr) || '';
|
|
|
|
rows +=
|
|
'<tr>' +
|
|
'<td class="text-muted" style="font-size:12px">' + (i + 1) + '</td>' +
|
|
'<td>' +
|
|
(pubKey
|
|
? '<a class="mono" href="/address?address=' + encodeURIComponent(pubKey) + '">' + C.esc(addr) + '</a>'
|
|
: C.esc(addr)) +
|
|
'</td>' +
|
|
'<td class="mono" style="font-size:11px;color:var(--muted)">' +
|
|
C.esc(C.short(x25519, 28)) +
|
|
'</td>' +
|
|
'<td>' + fmtFee(feeUT) + '</td>' +
|
|
'<td class="mono" style="font-size:11px;color:var(--muted);max-width:200px;overflow:hidden;text-overflow:ellipsis">' +
|
|
(multiaddr ? C.esc(multiaddr) : '<span class="text-muted">—</span>') +
|
|
'</td>' +
|
|
'<td>' +
|
|
(pubKey
|
|
? '<a href="/node?node=' + encodeURIComponent(pubKey) + '" class="pill-link" style="font-size:12px;padding:4px 10px">' +
|
|
'<i data-lucide="server"></i> Node' +
|
|
'</a>'
|
|
: '') +
|
|
'</td>' +
|
|
'</tr>';
|
|
});
|
|
tbody.innerHTML = rows;
|
|
|
|
C.setStatus(relays.length + ' relay node' + (relays.length !== 1 ? 's' : '') + ' registered.', 'ok');
|
|
C.refreshIcons();
|
|
} catch (e) {
|
|
C.setStatus('Load failed: ' + e.message, 'err');
|
|
}
|
|
}
|
|
|
|
document.getElementById('refreshBtn').addEventListener('click', loadRelays);
|
|
loadRelays();
|
|
})();
|