Files
dchain/node/explorer/tokens.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

122 lines
5.2 KiB
JavaScript

(function() {
var C = window.ExplorerCommon;
var state = { tab: 'fungible' };
function switchTab(name) {
state.tab = name;
document.getElementById('paneFungible').style.display = name === 'fungible' ? '' : 'none';
document.getElementById('paneNFT').style.display = name === 'nft' ? '' : 'none';
document.getElementById('tabFungible').className = 'tx-tab' + (name === 'fungible' ? ' tx-tab-active' : '');
document.getElementById('tabNFT').className = 'tx-tab' + (name === 'nft' ? ' tx-tab-active' : '');
}
/* ── Fungible tokens ─────────────────────────────────────────────────────── */
function formatSupply(supply, decimals) {
if (decimals === 0) return supply.toLocaleString();
var d = Math.pow(10, decimals);
var whole = Math.floor(supply / d);
var frac = supply % d;
if (frac === 0) return whole.toLocaleString();
return whole.toLocaleString() + '.' + String(frac).padStart(decimals, '0').replace(/0+$/, '');
}
async function loadTokens() {
C.setStatus('Loading…', 'warn');
try {
var data = await C.fetchJSON('/api/tokens');
var tokens = (data && Array.isArray(data.tokens)) ? data.tokens : [];
document.getElementById('tokenCount').textContent = tokens.length;
var tbody = document.getElementById('tokenBody');
if (!tokens.length) {
tbody.innerHTML = '<tr><td colspan="7" class="tbl-empty">No fungible tokens issued yet.</td></tr>';
} else {
var rows = '';
tokens.forEach(function(t, i) {
var supply = formatSupply(t.total_supply || 0, t.decimals || 0);
rows +=
'<tr>' +
'<td class="text-muted" style="font-size:12px">' + (i+1) + '</td>' +
'<td>' +
'<a href="/token?id=' + encodeURIComponent(t.token_id) + '" class="token-sym">' +
C.esc(t.symbol) +
'</a>' +
'</td>' +
'<td>' + C.esc(t.name) + '</td>' +
'<td class="mono text-muted">' + (t.decimals || 0) + '</td>' +
'<td class="mono">' + C.esc(supply) + '</td>' +
'<td>' +
'<a href="/address?address=' + encodeURIComponent(t.issuer) + '" class="mono" style="font-size:12px">' +
C.short(t.issuer, 20) +
'</a>' +
'</td>' +
'<td class="mono text-muted" style="font-size:12px">' + (t.issued_at || 0) + '</td>' +
'</tr>';
});
tbody.innerHTML = rows;
}
C.refreshIcons();
} catch(e) {
C.setStatus('Load failed: ' + e.message, 'err');
}
}
/* ── NFTs ────────────────────────────────────────────────────────────────── */
async function loadNFTs() {
try {
var data = await C.fetchJSON('/api/nfts');
var nfts = (data && Array.isArray(data.nfts)) ? data.nfts : [];
document.getElementById('nftCount').textContent = nfts.length;
var grid = document.getElementById('nftGrid');
var empty = document.getElementById('nftEmpty');
if (!nfts.length) {
grid.innerHTML = '';
empty.style.display = '';
return;
}
empty.style.display = 'none';
var cards = '';
nfts.forEach(function(n) {
var burned = n.burned ? ' nft-card-burned' : '';
var imgHtml = '';
if (n.uri && /\.(png|jpg|jpeg|gif|svg|webp)/i.test(n.uri)) {
imgHtml = '<img class="nft-card-img" src="' + C.esc(n.uri) + '" alt="" loading="lazy">';
} else {
imgHtml = '<div class="nft-card-img nft-card-placeholder"><i data-lucide="image"></i></div>';
}
cards +=
'<a class="nft-card' + burned + '" href="/token?nft=' + encodeURIComponent(n.nft_id) + '">' +
imgHtml +
'<div class="nft-card-body">' +
'<div class="nft-card-name">' + C.esc(n.name) + (n.burned ? ' <span class="badge-burned">BURNED</span>' : '') + '</div>' +
'<div class="nft-card-id mono">' + C.short(n.nft_id, 16) + '</div>' +
(n.owner ? '<div class="nft-card-owner text-muted">Owner: ' + C.short(n.owner, 16) + '</div>' : '') +
'</div>' +
'</a>';
});
grid.innerHTML = cards;
C.refreshIcons();
} catch(e) {
document.getElementById('nftCount').textContent = '?';
}
}
/* ── Boot ────────────────────────────────────────────────────────────────── */
document.getElementById('tabFungible').addEventListener('click', function() { switchTab('fungible'); });
document.getElementById('tabNFT').addEventListener('click', function() { switchTab('nft'); });
document.getElementById('refreshBtn').addEventListener('click', function() { loadTokens(); loadNFTs(); });
// Check URL hash for tab.
if (window.location.hash === '#nft') switchTab('nft');
Promise.all([loadTokens(), loadNFTs()]).then(function() {
C.setStatus('', '');
});
})();