(function() { var C = window.ExplorerCommon; var currentContractID = ''; /* ── Tab switching ───────────────────────────────────────────────────────── */ function switchTab(active) { var tabs = ['Overview', 'State', 'Logs', 'Raw']; tabs.forEach(function(name) { var btn = document.getElementById('tab' + name); var pane = document.getElementById('pane' + name); if (!btn || !pane) return; var isActive = (name === active); btn.className = 'tx-tab' + (isActive ? ' tx-tab-active' : ''); pane.style.display = isActive ? '' : 'none'; }); // Lazy-load logs when tab is opened if (active === 'Logs' && currentContractID) { loadLogs(currentContractID); } } document.getElementById('tabOverview').addEventListener('click', function() { switchTab('Overview'); }); document.getElementById('tabState').addEventListener('click', function() { switchTab('State'); }); document.getElementById('tabLogs').addEventListener('click', function() { switchTab('Logs'); }); document.getElementById('tabRaw').addEventListener('click', function() { switchTab('Raw'); }); /* ── ABI rendering ───────────────────────────────────────────────────────── */ function renderABI(abiJson) { var abi = null; try { abi = typeof abiJson === 'string' ? JSON.parse(abiJson) : abiJson; } catch (e) { return; } if (!abi || !Array.isArray(abi.methods) || abi.methods.length === 0) return; var tbody = document.getElementById('abiMethodsBody'); tbody.innerHTML = ''; abi.methods.forEach(function(m) { var args = Array.isArray(m.args) && m.args.length > 0 ? m.args.map(function(a) { return C.esc(a.name || '') + ': ' + C.esc(a.type || '?'); }).join(', ') : 'none'; var tr = document.createElement('tr'); tr.innerHTML = '' + C.esc(m.name || '?') + '' + '' + args + ''; tbody.appendChild(tr); }); document.getElementById('abiSection').style.display = ''; } /* ── Main render ─────────────────────────────────────────────────────────── */ function renderContract(contract) { document.title = 'Contract ' + C.short(contract.contract_id, 16) + ' | DChain Explorer'; currentContractID = contract.contract_id; // Banner document.getElementById('bannerContractId').textContent = contract.contract_id || '—'; document.getElementById('bannerDeployedAt').textContent = contract.deployed_at != null ? 'Block #' + contract.deployed_at : '—'; // Overview fields document.getElementById('contractId').textContent = contract.contract_id || '—'; if (contract.deployer_pub) { document.getElementById('contractDeployer').innerHTML = '' + C.esc(C.shortAddress(contract.deployer_pub)) + ''; document.getElementById('contractDeployerRaw').textContent = contract.deployer_pub; } else { document.getElementById('contractDeployer').textContent = '—'; } document.getElementById('contractDeployedBlock').textContent = contract.deployed_at != null ? '#' + contract.deployed_at : '—'; document.getElementById('contractWasmSize').textContent = contract.wasm_size != null ? contract.wasm_size.toLocaleString() + ' bytes' : '—'; // ABI if (contract.abi_json) { renderABI(contract.abi_json); } // Raw JSON document.getElementById('contractRaw').textContent = JSON.stringify(contract, null, 2); document.getElementById('mainContent').style.display = ''; C.refreshIcons(); } /* ── Contracts list ──────────────────────────────────────────────────────── */ async function loadContractsList() { try { var data = await C.fetchJSON('/api/contracts'); var contracts = data.contracts || []; var loading = document.getElementById('contractsLoading'); var empty = document.getElementById('contractsEmpty'); var wrap = document.getElementById('contractsTableWrap'); var count = document.getElementById('contractsCount'); if (loading) loading.style.display = 'none'; count.textContent = contracts.length + ' contract' + (contracts.length !== 1 ? 's' : ''); if (contracts.length === 0) { empty.style.display = ''; return; } var tbody = document.getElementById('contractsBody'); tbody.innerHTML = ''; contracts.forEach(function(c) { var tr = document.createElement('tr'); var idShort = C.short(c.contract_id, 20); var deployer = c.deployer_pub ? C.shortAddress(c.deployer_pub) : '—'; var deployerHref = c.deployer_pub ? '' + C.esc(deployer) + '' : ''; tr.innerHTML = '' + C.esc(idShort) + '' + '' + deployerHref + '' + '' + (c.deployed_at != null ? '#' + c.deployed_at : '—') + '' + '' + (c.wasm_size != null ? c.wasm_size.toLocaleString() + ' B' : '—') + ''; tbody.appendChild(tr); }); wrap.style.display = ''; C.refreshIcons(); } catch (e) { var loading = document.getElementById('contractsLoading'); if (loading) loading.textContent = 'Failed to load contracts: ' + e.message; } } /* ── Load contract ───────────────────────────────────────────────────────── */ async function loadContract(id) { if (!id) return; C.setStatus('Loading…', 'warn'); document.getElementById('contractsList').style.display = 'none'; document.getElementById('mainContent').style.display = 'none'; document.getElementById('abiSection').style.display = 'none'; switchTab('Overview'); try { var contract = await C.fetchJSON('/api/contracts/' + encodeURIComponent(id)); renderContract(contract); window.history.replaceState({}, '', '/contract?id=' + encodeURIComponent(id)); C.setStatus('', ''); } catch (e) { C.setStatus('Contract not found: ' + e.message, 'err'); document.getElementById('contractsList').style.display = ''; } } /* ── State browser ───────────────────────────────────────────────────────── */ async function queryState(key) { if (!currentContractID || !key) return; C.setStatus('Querying state…', 'warn'); document.getElementById('stateResult').style.display = 'none'; document.getElementById('stateEmpty').style.display = 'none'; try { var data = await C.fetchJSON( '/api/contracts/' + encodeURIComponent(currentContractID) + '/state/' + encodeURIComponent(key) ); C.setStatus('', ''); document.getElementById('stateKey').textContent = data.key || key; if (data.value_hex === null || data.value_hex === undefined) { // Key not set document.getElementById('stateU64Row').style.display = 'none'; document.getElementById('stateHex').textContent = '—'; document.getElementById('stateB64').textContent = '—'; document.getElementById('stateNullRow').style.display = ''; } else { document.getElementById('stateNullRow').style.display = 'none'; document.getElementById('stateHex').textContent = data.value_hex || '—'; document.getElementById('stateB64').textContent = data.value_b64 || '—'; if (data.value_u64 !== null && data.value_u64 !== undefined) { document.getElementById('stateU64').textContent = data.value_u64.toLocaleString(); document.getElementById('stateU64Row').style.display = ''; } else { document.getElementById('stateU64Row').style.display = 'none'; } } document.getElementById('stateResult').style.display = ''; C.refreshIcons(); } catch (e) { C.setStatus('State query failed: ' + e.message, 'err'); document.getElementById('stateEmpty').style.display = ''; } } document.getElementById('stateLoadBtn').addEventListener('click', function() { var key = (document.getElementById('stateKeyInput').value || '').trim(); if (key) queryState(key); }); document.getElementById('stateKeyInput').addEventListener('keydown', function(e) { if (e.key === 'Enter') document.getElementById('stateLoadBtn').click(); }); /* ── Logs ────────────────────────────────────────────────────────────────── */ var logsLoaded = false; function makeLogRow(entry) { var tr = document.createElement('tr'); var txShort = entry.tx_id ? C.short(entry.tx_id, 12) : '—'; var txLink = entry.tx_id ? '' + C.esc(txShort) + '' : ''; tr.innerHTML = '#' + entry.block_height + '' + '' + txLink + '' + '' + entry.seq + '' + '' + C.esc(entry.message) + ''; return tr; } async function loadLogs(contractID) { if (logsLoaded) return; logsLoaded = true; try { var data = await C.fetchJSON('/api/contracts/' + encodeURIComponent(contractID) + '/logs?limit=100'); var logs = data.logs || []; if (logs.length === 0) { document.getElementById('logsEmpty').style.display = ''; document.getElementById('logsTableWrap').style.display = 'none'; return; } var tbody = document.getElementById('logsBody'); tbody.innerHTML = ''; logs.forEach(function(entry) { tbody.appendChild(makeLogRow(entry)); }); document.getElementById('logsEmpty').style.display = 'none'; document.getElementById('logsTableWrap').style.display = ''; } catch (e) { document.getElementById('logsEmpty').textContent = 'Failed to load logs: ' + e.message; document.getElementById('logsEmpty').style.display = ''; } } // SSE — prepend live contract_log entries for the currently viewed contract. C.connectSSE({ contract_log: function(entry) { if (!currentContractID || entry.contract_id !== currentContractID) return; var tbody = document.getElementById('logsBody'); if (!tbody) return; tbody.insertBefore(makeLogRow(entry), tbody.firstChild); document.getElementById('logsEmpty').style.display = 'none'; document.getElementById('logsTableWrap').style.display = ''; } }); /* ── 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('contractBtn').addEventListener('click', function() { var val = (document.getElementById('contractInput').value || '').trim(); if (val) { logsLoaded = false; loadContract(val); } }); document.getElementById('contractInput').addEventListener('keydown', function(e) { if (e.key === 'Enter') document.getElementById('contractBtn').click(); }); var initial = C.q('id'); if (initial) { document.getElementById('contractInput').value = initial; loadContract(initial); } else { loadContractsList(); } })();