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

181 lines
8.2 KiB
JavaScript

(function() {
var C = window.ExplorerCommon;
var params = new URLSearchParams(window.location.search);
var tokenID = params.get('id');
var nftID = params.get('nft');
function kv(icon, label, valHtml) {
return '<div class="addr-kv-row">' +
'<div class="addr-kv-key"><i data-lucide="' + icon + '"></i> ' + label + '</div>' +
'<div class="addr-kv-val">' + valHtml + '</div>' +
'</div>';
}
function copyBtn(id) {
return '<button class="copy-btn" data-copy-id="' + id + '" title="Copy"><i data-lucide="copy"></i></button>';
}
function hiddenSpan(id, val) {
return '<span id="' + id + '" style="display:none">' + C.esc(val) + '</span>';
}
/* ── Fungible token ──────────────────────────────────────────────────────── */
async function loadToken() {
C.setStatus('Loading token…', 'warn');
try {
var data = await C.fetchJSON('/api/tokens/' + tokenID);
if (!data || !data.token_id) { C.setStatus('Token not found.', 'err'); return; }
document.title = data.symbol + ' | DChain Explorer';
document.getElementById('bannerType').textContent = 'Fungible Token';
document.getElementById('bannerName').textContent = data.name + ' (' + data.symbol + ')';
document.getElementById('bannerBlock').textContent = 'block ' + (data.issued_at || 0);
document.getElementById('bannerIcon').querySelector('i').setAttribute('data-lucide', 'coins');
var d = data.decimals || 0;
var supplyFmt = formatSupply(data.total_supply || 0, d);
var rows = '';
rows += kv('hash', 'Token ID',
'<span class="mono addr-pubkey-text" id="tid">' + C.esc(data.token_id) + '</span>' + copyBtn('tid'));
rows += kv('type', 'Symbol',
'<span class="token-sym">' + C.esc(data.symbol) + '</span>');
rows += kv('tag', 'Name', C.esc(data.name));
rows += kv('layers-3', 'Decimals', '<span class="mono">' + d + '</span>');
rows += kv('bar-chart-2', 'Total Supply',
'<span class="mono">' + C.esc(supplyFmt) + '</span>');
rows += kv('user', 'Issuer',
'<a href="/address?address=' + encodeURIComponent(data.issuer) + '" class="mono">' +
C.short(data.issuer, 20) +
'</a>' +
hiddenSpan('issuerRaw', data.issuer) + copyBtn('issuerRaw'));
rows += kv('blocks', 'Issued at block',
'<span class="mono">' + (data.issued_at || 0) + '</span>');
document.getElementById('kvList').innerHTML = rows;
document.getElementById('rawJSON').textContent = JSON.stringify(data, null, 2);
document.getElementById('tabRaw').style.display = '';
document.getElementById('mainContent').style.display = '';
C.setStatus('', '');
C.refreshIcons();
C.wireClipboard();
} catch(e) {
C.setStatus('Load failed: ' + e.message, 'err');
}
}
/* ── NFT ─────────────────────────────────────────────────────────────────── */
async function loadNFT() {
C.setStatus('Loading NFT…', 'warn');
try {
var data = await C.fetchJSON('/api/nfts/' + nftID);
var n = data && data.nft ? data.nft : data;
if (!n || !n.nft_id) { C.setStatus('NFT not found.', 'err'); return; }
document.title = n.name + ' | DChain Explorer';
document.getElementById('bannerType').textContent = n.burned ? 'NFT (Burned)' : 'NFT';
document.getElementById('bannerName').textContent = n.name;
document.getElementById('bannerBlock').textContent = 'minted block ' + (n.minted_at || 0);
document.getElementById('bannerIcon').querySelector('i').setAttribute('data-lucide', 'image');
if (n.burned) document.getElementById('banner').classList.add('tx-banner-err');
// Show image if URI looks like an image
if (n.uri && /\.(png|jpg|jpeg|gif|svg|webp)/i.test(n.uri)) {
document.getElementById('nftImage').src = n.uri;
document.getElementById('nftImageWrap').style.display = '';
}
var ownerAddr = data.owner_address || '';
var rows = '';
rows += kv('hash', 'NFT ID',
'<span class="mono addr-pubkey-text" id="nid">' + C.esc(n.nft_id) + '</span>' + copyBtn('nid'));
rows += kv('tag', 'Name', C.esc(n.name));
if (n.description)
rows += kv('file-text', 'Description', C.esc(n.description));
if (n.uri)
rows += kv('link', 'Metadata URI',
'<a href="' + C.esc(n.uri) + '" target="_blank" class="mono" style="word-break:break-all">' + C.esc(n.uri) + '</a>');
if (!n.burned && n.owner) {
rows += kv('user', 'Owner',
'<a href="/address?address=' + encodeURIComponent(n.owner) + '" class="mono">' +
C.short(ownerAddr || n.owner, 20) +
'</a>' +
hiddenSpan('ownerRaw', n.owner) + copyBtn('ownerRaw'));
} else if (n.burned) {
rows += kv('flame', 'Status', '<span style="color:var(--err)">Burned</span>');
}
rows += kv('user-check', 'Issuer',
'<a href="/address?address=' + encodeURIComponent(n.issuer) + '" class="mono">' +
C.short(n.issuer, 20) +
'</a>');
rows += kv('blocks', 'Minted at block',
'<span class="mono">' + (n.minted_at || 0) + '</span>');
document.getElementById('kvList').innerHTML = rows;
// Attributes
if (n.attributes) {
try {
var attrs = JSON.parse(n.attributes);
var keys = Object.keys(attrs);
if (keys.length) {
var html = '';
keys.forEach(function(k) {
html += '<div class="attr-chip"><div class="attr-chip-key">' + C.esc(k) + '</div>' +
'<div class="attr-chip-val">' + C.esc(String(attrs[k])) + '</div></div>';
});
document.getElementById('attrsGrid').innerHTML = html;
document.getElementById('attrsSection').style.display = '';
}
} catch(_) {}
}
document.getElementById('rawJSON').textContent = JSON.stringify(data, null, 2);
document.getElementById('tabRaw').style.display = '';
document.getElementById('mainContent').style.display = '';
C.setStatus('', '');
C.refreshIcons();
C.wireClipboard();
} catch(e) {
C.setStatus('Load failed: ' + e.message, 'err');
}
}
/* ── Helpers ─────────────────────────────────────────────────────────────── */
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+$/, '');
}
/* ── Tabs ────────────────────────────────────────────────────────────────── */
document.getElementById('tabOverview').addEventListener('click', function() {
document.getElementById('paneOverview').style.display = '';
document.getElementById('paneRaw').style.display = 'none';
document.getElementById('tabOverview').classList.add('tx-tab-active');
document.getElementById('tabRaw').classList.remove('tx-tab-active');
});
document.getElementById('tabRaw').addEventListener('click', function() {
document.getElementById('paneOverview').style.display = 'none';
document.getElementById('paneRaw').style.display = '';
document.getElementById('tabRaw').classList.add('tx-tab-active');
document.getElementById('tabOverview').classList.remove('tx-tab-active');
});
/* ── Boot ────────────────────────────────────────────────────────────────── */
if (nftID) loadNFT();
else if (tokenID) loadToken();
else C.setStatus('No token or NFT ID provided.', 'err');
})();