SC CODE: Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("nameHdr", "blocks-details.js")
31 STORE("descrHdr", "Individual block details renderer")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "explorer.tela")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "1e3344abe2048c8782d419c8081a97223ac8382a3795d61023ac334099f64b9")
37 STORE("fileCheckS", "63131a5d2ed016d1542d71845a8ee3926ae35fecc6a849217308811450d47b4")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*
({
name: 'blocks-details',
version: '1.0.0',
cs: {
c: 'enhanced-card',
h: 'card-header',
ct: 'card-content',
err: 'background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);border-radius:8px;padding:2rem;text-align:center;border-left:4px solid #ef4444',
card: 'background:rgba(0,0,0,0.2);border:1px solid rgba(82,200,219,0.3);padding:1.5rem;border-radius:8px',
btn: 'background:rgba(82,200,219,0.1);border:1px solid #52c8db;color:#52c8db;padding:0.75rem 1.5rem;border-radius:6px;cursor:pointer;font-weight:500;transition:all 0.2s ease'
},
async renderBlock(h, x) {
const b = await x('DERO.GetBlock', {height: parseInt(h)});
if (!b) {
return '<div class="enhanced-card"><div class="card-header"><h2 style="color:#52c8db;">Block Not Found</h2></div><div class="card-content"><div style="color:#ef4444;">Block ' + h + ' could not be retrieved</div></div></div>';
}
const bh = b.block_header || b;
const d = b.json ? JSON.parse(b.json) : {};
const rawTimestamp = bh.timestamp || d.timestamp || 0;
const ts = rawTimestamp > 1000000000000 ? new Date(rawTimestamp) : new Date(rawTimestamp * 1000);
const tc = bh.txcount !== undefined ? bh.txcount : (d.tx_hashes ? d.tx_hashes.length : 0);
const bs = bh.block_size || 0;
const sk = (bs / 1024).toFixed(2);
const df = (bh.difficulty || 0).toLocaleString();
const hs = bh.hash || 'undefined';
const ph = d.tips ? d.tips[0] : 'undefined';
const ad = this.formatAge(Math.abs((Date.now() / 1000) - (bh.timestamp || 0)));
const br = parseInt(h) === 0 ? 18900000 : 1.0;
// Build transactions section
let th = '';
if (d.tx_hashes && d.tx_hashes.length > 0) {
const tr = d.tx_hashes.map(t => {
const shortHash = t.length > 20 ? t.slice(0, 12) + '...' + t.slice(-8) : t;
return '<div style="display:grid;grid-template-columns:1fr 100px 80px 80px;gap:1rem;padding:0.75rem;border-bottom:1px solid rgba(255,255,255,0.05);align-items:center;cursor:pointer;" onclick="window.location.hash=\'tx/' + t + '\'"><div style="font-family:monospace;color:#b959b6;">' + shortHash + '</div><div style="color:#fbbf24;text-align:center;">Normal</div><div style="color:#888;text-align:center;">-</div><div style="color:#888;text-align:center;">-</div></div>';
}).join('');
th = '<div style="background:rgba(0,0,0,0.1);border-radius:6px;border:1px solid rgba(255,255,255,0.05);overflow:hidden;"><div style="display:grid;grid-template-columns:1fr 100px 80px 80px;gap:1rem;padding:0.75rem;border-bottom:2px solid rgba(255,255,255,0.1);font-weight:600;color:#52c8db;background:rgba(69,227,221,0.05);"><div>Transaction Hash</div><div style="text-align:center;">Type</div><div style="text-align:center;">Fee</div><div style="text-align:center;">Size</div></div>' + tr + '</div>';
} else {
th = '<div style="text-align:center;color:#888;padding:2rem;background:rgba(0,0,0,0.1);border-radius:6px;border:1px solid rgba(255,255,255,0.05);"><div style="font-size:1rem;margin-bottom:0.5rem;">No transactions in this block</div><div style="font-size:0.8rem;">This is a coinbase-only block</div></div>';
}
// Professional enhanced-card structure
let result = '<div class="enhanced-card">';
// Header with actions
result += '<div class="card-header">';
result += '<div style="display:flex;align-items:center;gap:1rem;">';
result += '<button onclick="window.location.hash=\'blocks\'" style="' + this.cs.btn + '">← Back to Blocks</button>';
result += '<span style="color:#888;">|</span>';
result += '<span style="color:#52c8db;font-weight:500;">Block Explorer</span>';
result += '</div>';
result += '<div class="actions"><button onclick="window.location.reload()" style="' + this.cs.btn + '">Refresh</button></div>';
result += '</div>';
// Content
result += '<div class="card-content">';
// Title section
result += '<div style="margin-bottom:2rem;">';
result += '<h2 style="color:#fff;margin:0;font-size:1.6rem;font-weight:700;">Block ' + h + '</h2>';
result += '<div style="color:#b3b3b3;font-size:0.9rem;margin-top:0.5rem;">Mined ' + ad + ' • ' + ts.toLocaleString() + '</div>';
result += '</div>';
// Stats grid with professional stat cards
result += '<div class="stats-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1.5rem;margin-bottom:2rem;">';
result += '<div class="stat-card" style="' + this.cs.card + ';text-align:center;"><div style="color:#52c8db;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + h + '</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Block Height</div></div>';
result += '<div class="stat-card" style="' + this.cs.card.replace('82,200,219', '251,191,36') + ';text-align:center;"><div style="color:#fbbf24;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + tc + '</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Transactions</div></div>';
result += '<div class="stat-card" style="' + this.cs.card.replace('82,200,219', '74,222,128') + ';text-align:center;"><div style="color:#4ade80;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + sk + ' KB</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Block Size</div></div>';
result += '<div class="stat-card" style="' + this.cs.card.replace('82,200,219', '139,92,246') + ';text-align:center;"><div style="color:#b959b6;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + br.toLocaleString() + '</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Reward (DERO)</div></div>';
result += '</div>';
// Block Details section
result += '<div style="' + this.cs.card + ';margin-bottom:2rem;">';
result += '<h3 style="color:#52c8db;margin:0 0 1.5rem 0;font-size:1.2rem;font-weight:600;">Block Details</h3>';
result += '<div style="margin-bottom:1.5rem;"><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Block Hash</div><div style="font-family:monospace;color:#b959b6;font-size:0.85rem;word-break:break-all;line-height:1.4;cursor:pointer;" title="Click to copy" onclick="navigator.clipboard.writeText(\'' + hs + '\');">' + hs + '</div></div>';
result += '<div style="margin-bottom:1.5rem;"><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Previous Hash</div><div style="font-family:monospace;color:#b959b6;font-size:0.85rem;word-break:break-all;line-height:1.4;cursor:pointer;" onclick="window.location.hash=\'block/' + (parseInt(h) - 1) + '\'">' + ph + '</div></div>';
result += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;">';
result += '<div><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Timestamp</div><div style="color:#fff;font-weight:500;">' + ts.toLocaleString() + '</div></div>';
result += '<div><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Difficulty</div><div style="color:#4ade80;font-weight:600;">' + df + '</div></div>';
result += '</div></div>';
// Transactions section
result += '<div style="' + this.cs.card + ';margin-bottom:2rem;"><h3 style="color:#52c8db;margin:0 0 1.5rem 0;font-size:1.2rem;font-weight:600;">Transactions (' + tc + ')</h3>' + th + '</div>';
// Navigation
result += '<div style="display:flex;justify-content:space-between;align-items:center;padding:1.5rem;' + this.cs.card + ';">';
result += (h > 0 ? '<button onclick="navigateToBlock(' + (h - 1) + ')" style="' + this.cs.btn + '">← Previous Block</button>' : '<div></div>');
result += '<div style="color:#52c8db;font-weight:600;font-size:1rem;">Block ' + h + '</div>';
result += '<button onclick="navigateToBlock(' + (parseInt(h) + 1) + ')" style="' + this.cs.btn + '">Next Block →</button>';
result += '</div>';
result += '</div></div>';
return result;
},
formatAge: function(seconds) {
if (isNaN(seconds) || seconds < 0 || seconds > 31536000) return '0s';
if (seconds < 60) return Math.floor(seconds) + 's';
if (seconds < 3600) return Math.floor(seconds / 60) + 'm';
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h';
return Math.floor(seconds / 86400) + 'd';
},
async renderRecentBlocksList(x, h, c) {
c = c || 15;
let ht = '';
const s = Math.max(1, h - c + 1);
const bl = [];
for (let i = h; i >= s; i--) {
try {
const ck = 'block_' + i;
let b = window.getCachedData ?
await window.getCachedData(ck, () => x('DERO.GetBlock', {height: i})) :
await x('DERO.GetBlock', {height: i});
if (b && b.block_header) {
const bh = b.block_header;
const now = Date.now() / 1000;
const bt = bh.timestamp || 0;
const age = this.formatAge(Math.abs(now - bt));
const fh = bh.hash || 'N/A';
const bs = bh.block_size || 0;
let sd = '';
if (bs < 1024) sd = bs + 'B';
else if (bs < 1024 * 1024) sd = Math.round(bs / 1024) + 'KB';
else sd = Math.round(bs / (1024 * 1024)) + 'MB';
const mn = bh.miners?.length || b.miners?.length || 1;
const df = (bh.difficulty || 0).toLocaleString();
bl.push({height: i, age: age, sizeDisplay: sd, diff: df, fullHash: fh, miners: mn});
}
} catch (e) {
bl.push({height: i, age: '--', sizeDisplay: '--', diff: '--', fullHash: 'Error', miners: '--', isError: true});
}
}
bl.sort((a, b) => b.height - a.height);
for (const bk of bl) {
if (bk.isError) {
ht += '<div style="color:#ef4444;text-align:center;padding:1rem">Error loading block ' + bk.height + '</div>';
} else {
ht += '<div style="display:grid;grid-template-columns:180px 1fr 70px 80px 100px 70px;gap:0.8rem;padding:0.75rem;border-bottom:1px solid rgba(255,255,255,0.05);align-items:center;font-size:0.85rem;transition:background 0.2s ease;cursor:pointer;" onclick="window.location.hash=\'block/' + bk.height + '\'" onmouseover="this.style.background=\'rgba(255,255,255,0.02)\'" onmouseout="this.style.background=\'transparent\'">';
ht += '<div><div style="color:#52c8db;font-weight:600;font-size:1rem;">' + bk.height.toLocaleString() + '</div><div style="color:#888;font-size:0.75rem;margin-top:0.2rem;">Block Height</div></div>';
ht += '<div style="font-family:monospace;color:#b959b6;font-size:0.75rem;line-height:1.1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + bk.fullHash + '</div>';
ht += '<div style="color:#888;text-align:center;">' + bk.age + '</div>';
ht += '<div style="color:#fbbf24;text-align:center;font-weight:500;">' + bk.sizeDisplay + '</div>';
ht += '<div style="color:#4ade80;text-align:center;font-size:0.8rem;">' + bk.diff + '</div>';
ht += '<div style="color:#888;text-align:right;font-size:0.8rem;">' + bk.miners + '</div>';
ht += '</div>';
}
}
return ht || '<div style="text-align:center;padding:2rem;color:#888;">No blocks available</div>';
},
renderMiniblocksSection: function(m, f, u) {
if (!m || !m.length) {
return '<div style="background:rgba(0,0,0,0.2);border:1px solid rgba(82,200,219,0.3);padding:1.5rem;border-radius:8px;margin-bottom:2rem;"><h3 style="color:#52c8db;">Miniblocks (0)</h3><div style="text-align:center;color:#888;padding:1rem;">No miniblocks found</div></div>';
}
let result = '<div style="background:rgba(0,0,0,0.2);border:1px solid rgba(82,200,219,0.3);padding:1.5rem;border-radius:8px;margin-bottom:2rem;">';
result += '<h3 style="color:#52c8db;margin:0 0 1.5rem 0;font-size:1.2rem;font-weight:600;">Miniblocks (' + m.length + ')</h3>';
result += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:1rem;margin-bottom:1.5rem;">';
result += '<div style="background:rgba(0,0,0,0.2);padding:1rem;border-radius:6px;text-align:center;border:1px solid rgba(255,255,255,0.05);"><div style="color:#52c8db;font-size:1.5rem;font-weight:600;">' + m.length + '</div><div style="color:#888;font-size:0.8rem;margin-top:0.5rem;">Total</div></div>';
result += '<div style="background:rgba(0,0,0,0.2);padding:1rem;border-radius:6px;text-align:center;border:1px solid rgba(255,255,255,0.05);"><div style="color:#4ade80;font-size:1.5rem;font-weight:600;">' + f + '</div><div style="color:#888;font-size:0.8rem;margin-top:0.5rem;">Final</div></div>';
result += '<div style="background:rgba(0,0,0,0.2);padding:1rem;border-radius:6px;text-align:center;border:1px solid rgba(255,255,255,0.05);"><div style="color:#fbbf24;font-size:1.5rem;font-weight:600;">' + u + '</div><div style="color:#888;font-size:0.8rem;margin-top:0.5rem;">Miners</div></div>';
result += '</div>';
const mbRows = m.map((mb, i) => {
const c = mb.Final ? '#4ade80' : '#fbbf24';
const ic = mb.Final ? '✓' : '●';
const txt = mb.Final ? 'Final' : 'Pending';
const h = mb.KeyHash || 'Unknown';
const t = new Date(mb.Timestamp || 0).toLocaleTimeString();
return '<div style="display:grid;grid-template-columns:60px 1fr 80px 70px 70px;gap:0.8rem;padding:0.75rem;border-bottom:1px solid rgba(255,255,255,0.05);align-items:center;font-size:0.85rem;" onmouseover="this.style.background=\'rgba(255,255,255,0.02)\'" onmouseout="this.style.background=\'transparent\'"><div style="color:#52c8db;font-weight:600;text-align:center;">MB' + (i + 1) + '</div><div style="font-family:monospace;color:#b959b6;font-size:0.75rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + h + '</div><div style="color:white;text-align:center;font-size:0.8rem;">' + t + '</div><div style="color:' + c + ';text-align:center;font-weight:500;font-size:0.85rem;">' + ic + ' ' + txt + '</div><div style="color:#888;text-align:center;font-size:0.8rem;">' + (mb.Flags || 0) + '</div></div>';
}).join('');
result += '<div style="background:rgba(0,0,0,0.1);border-radius:6px;border:1px solid rgba(255,255,255,0.05);overflow:hidden;">';
result += '<div style="display:grid;grid-template-columns:60px 1fr 80px 70px 70px;gap:0.8rem;padding:0.75rem;border-bottom:2px solid rgba(255,255,255,0.1);font-weight:600;color:#52c8db;background:rgba(69,227,221,0.05);"><div style="text-align:center;">#</div><div>Miner Key Hash</div><div style="text-align:center;">Time</div><div style="text-align:center;">Status</div><div style="text-align:center;">Flags</div></div>';
result += mbRows;
result += '</div></div>';
return result;
}
})
*/ |
SC Arguments: [Name:SC_ACTION Type:uint64 Value:'1' Name:SC_CODE Type:string Value:'Function InitializePrivate() Uint64
10 IF init() == 0 THEN GOTO 30
20 RETURN 1
30 STORE("nameHdr", "blocks-details.js")
31 STORE("descrHdr", "Individual block details renderer")
32 STORE("iconURLHdr", "")
33 STORE("dURL", "explorer.tela")
34 STORE("docType", "TELA-JS-1")
35 STORE("subDir", "")
36 STORE("fileCheckC", "1e3344abe2048c8782d419c8081a97223ac8382a3795d61023ac334099f64b9")
37 STORE("fileCheckS", "63131a5d2ed016d1542d71845a8ee3926ae35fecc6a849217308811450d47b4")
100 RETURN 0
End Function
Function init() Uint64
10 IF EXISTS("owner") == 0 THEN GOTO 30
20 RETURN 1
30 STORE("owner", address())
50 STORE("docVersion", "1.0.0")
60 STORE("hash", HEX(TXID()))
70 STORE("likes", 0)
80 STORE("dislikes", 0)
100 RETURN 0
End Function
Function address() String
10 DIM s as String
20 LET s = SIGNER()
30 IF IS_ADDRESS_VALID(s) THEN GOTO 50
40 RETURN "anon"
50 RETURN ADDRESS_STRING(s)
End Function
Function Rate(r Uint64) Uint64
10 DIM addr as String
15 LET addr = address()
16 IF r < 100 && EXISTS(addr) == 0 && addr != "anon" THEN GOTO 30
20 RETURN 1
30 STORE(addr, ""+r+"_"+BLOCK_HEIGHT())
40 IF r < 50 THEN GOTO 70
50 STORE("likes", LOAD("likes")+1)
60 RETURN 0
70 STORE("dislikes", LOAD("dislikes")+1)
100 RETURN 0
End Function
/*
({
name: 'blocks-details',
version: '1.0.0',
cs: {
c: 'enhanced-card',
h: 'card-header',
ct: 'card-content',
err: 'background:rgba(239,68,68,0.1);border:1px solid rgba(239,68,68,0.3);border-radius:8px;padding:2rem;text-align:center;border-left:4px solid #ef4444',
card: 'background:rgba(0,0,0,0.2);border:1px solid rgba(82,200,219,0.3);padding:1.5rem;border-radius:8px',
btn: 'background:rgba(82,200,219,0.1);border:1px solid #52c8db;color:#52c8db;padding:0.75rem 1.5rem;border-radius:6px;cursor:pointer;font-weight:500;transition:all 0.2s ease'
},
async renderBlock(h, x) {
const b = await x('DERO.GetBlock', {height: parseInt(h)});
if (!b) {
return '<div class="enhanced-card"><div class="card-header"><h2 style="color:#52c8db;">Block Not Found</h2></div><div class="card-content"><div style="color:#ef4444;">Block ' + h + ' could not be retrieved</div></div></div>';
}
const bh = b.block_header || b;
const d = b.json ? JSON.parse(b.json) : {};
const rawTimestamp = bh.timestamp || d.timestamp || 0;
const ts = rawTimestamp > 1000000000000 ? new Date(rawTimestamp) : new Date(rawTimestamp * 1000);
const tc = bh.txcount !== undefined ? bh.txcount : (d.tx_hashes ? d.tx_hashes.length : 0);
const bs = bh.block_size || 0;
const sk = (bs / 1024).toFixed(2);
const df = (bh.difficulty || 0).toLocaleString();
const hs = bh.hash || 'undefined';
const ph = d.tips ? d.tips[0] : 'undefined';
const ad = this.formatAge(Math.abs((Date.now() / 1000) - (bh.timestamp || 0)));
const br = parseInt(h) === 0 ? 18900000 : 1.0;
// Build transactions section
let th = '';
if (d.tx_hashes && d.tx_hashes.length > 0) {
const tr = d.tx_hashes.map(t => {
const shortHash = t.length > 20 ? t.slice(0, 12) + '...' + t.slice(-8) : t;
return '<div style="display:grid;grid-template-columns:1fr 100px 80px 80px;gap:1rem;padding:0.75rem;border-bottom:1px solid rgba(255,255,255,0.05);align-items:center;cursor:pointer;" onclick="window.location.hash=\'tx/' + t + '\'"><div style="font-family:monospace;color:#b959b6;">' + shortHash + '</div><div style="color:#fbbf24;text-align:center;">Normal</div><div style="color:#888;text-align:center;">-</div><div style="color:#888;text-align:center;">-</div></div>';
}).join('');
th = '<div style="background:rgba(0,0,0,0.1);border-radius:6px;border:1px solid rgba(255,255,255,0.05);overflow:hidden;"><div style="display:grid;grid-template-columns:1fr 100px 80px 80px;gap:1rem;padding:0.75rem;border-bottom:2px solid rgba(255,255,255,0.1);font-weight:600;color:#52c8db;background:rgba(69,227,221,0.05);"><div>Transaction Hash</div><div style="text-align:center;">Type</div><div style="text-align:center;">Fee</div><div style="text-align:center;">Size</div></div>' + tr + '</div>';
} else {
th = '<div style="text-align:center;color:#888;padding:2rem;background:rgba(0,0,0,0.1);border-radius:6px;border:1px solid rgba(255,255,255,0.05);"><div style="font-size:1rem;margin-bottom:0.5rem;">No transactions in this block</div><div style="font-size:0.8rem;">This is a coinbase-only block</div></div>';
}
// Professional enhanced-card structure
let result = '<div class="enhanced-card">';
// Header with actions
result += '<div class="card-header">';
result += '<div style="display:flex;align-items:center;gap:1rem;">';
result += '<button onclick="window.location.hash=\'blocks\'" style="' + this.cs.btn + '">← Back to Blocks</button>';
result += '<span style="color:#888;">|</span>';
result += '<span style="color:#52c8db;font-weight:500;">Block Explorer</span>';
result += '</div>';
result += '<div class="actions"><button onclick="window.location.reload()" style="' + this.cs.btn + '">Refresh</button></div>';
result += '</div>';
// Content
result += '<div class="card-content">';
// Title section
result += '<div style="margin-bottom:2rem;">';
result += '<h2 style="color:#fff;margin:0;font-size:1.6rem;font-weight:700;">Block ' + h + '</h2>';
result += '<div style="color:#b3b3b3;font-size:0.9rem;margin-top:0.5rem;">Mined ' + ad + ' • ' + ts.toLocaleString() + '</div>';
result += '</div>';
// Stats grid with professional stat cards
result += '<div class="stats-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:1.5rem;margin-bottom:2rem;">';
result += '<div class="stat-card" style="' + this.cs.card + ';text-align:center;"><div style="color:#52c8db;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + h + '</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Block Height</div></div>';
result += '<div class="stat-card" style="' + this.cs.card.replace('82,200,219', '251,191,36') + ';text-align:center;"><div style="color:#fbbf24;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + tc + '</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Transactions</div></div>';
result += '<div class="stat-card" style="' + this.cs.card.replace('82,200,219', '74,222,128') + ';text-align:center;"><div style="color:#4ade80;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + sk + ' KB</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Block Size</div></div>';
result += '<div class="stat-card" style="' + this.cs.card.replace('82,200,219', '139,92,246') + ';text-align:center;"><div style="color:#b959b6;font-size:1.8rem;font-weight:700;margin-bottom:0.5rem;">' + br.toLocaleString() + '</div><div style="color:#b3b3b3;font-size:0.85rem;text-transform:uppercase;letter-spacing:0.5px;">Reward (DERO)</div></div>';
result += '</div>';
// Block Details section
result += '<div style="' + this.cs.card + ';margin-bottom:2rem;">';
result += '<h3 style="color:#52c8db;margin:0 0 1.5rem 0;font-size:1.2rem;font-weight:600;">Block Details</h3>';
result += '<div style="margin-bottom:1.5rem;"><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Block Hash</div><div style="font-family:monospace;color:#b959b6;font-size:0.85rem;word-break:break-all;line-height:1.4;cursor:pointer;" title="Click to copy" onclick="navigator.clipboard.writeText(\'' + hs + '\');">' + hs + '</div></div>';
result += '<div style="margin-bottom:1.5rem;"><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Previous Hash</div><div style="font-family:monospace;color:#b959b6;font-size:0.85rem;word-break:break-all;line-height:1.4;cursor:pointer;" onclick="window.location.hash=\'block/' + (parseInt(h) - 1) + '\'">' + ph + '</div></div>';
result += '<div style="display:grid;grid-template-columns:1fr 1fr;gap:1.5rem;">';
result += '<div><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Timestamp</div><div style="color:#fff;font-weight:500;">' + ts.toLocaleString() + '</div></div>';
result += '<div><div style="color:#b3b3b3;font-size:0.8rem;margin-bottom:0.5rem;text-transform:uppercase;letter-spacing:0.5px;">Difficulty</div><div style="color:#4ade80;font-weight:600;">' + df + '</div></div>';
result += '</div></div>';
// Transactions section
result += '<div style="' + this.cs.card + ';margin-bottom:2rem;"><h3 style="color:#52c8db;margin:0 0 1.5rem 0;font-size:1.2rem;font-weight:600;">Transactions (' + tc + ')</h3>' + th + '</div>';
// Navigation
result += '<div style="display:flex;justify-content:space-between;align-items:center;padding:1.5rem;' + this.cs.card + ';">';
result += (h > 0 ? '<button onclick="navigateToBlock(' + (h - 1) + ')" style="' + this.cs.btn + '">← Previous Block</button>' : '<div></div>');
result += '<div style="color:#52c8db;font-weight:600;font-size:1rem;">Block ' + h + '</div>';
result += '<button onclick="navigateToBlock(' + (parseInt(h) + 1) + ')" style="' + this.cs.btn + '">Next Block →</button>';
result += '</div>';
result += '</div></div>';
return result;
},
formatAge: function(seconds) {
if (isNaN(seconds) || seconds < 0 || seconds > 31536000) return '0s';
if (seconds < 60) return Math.floor(seconds) + 's';
if (seconds < 3600) return Math.floor(seconds / 60) + 'm';
if (seconds < 86400) return Math.floor(seconds / 3600) + 'h';
return Math.floor(seconds / 86400) + 'd';
},
async renderRecentBlocksList(x, h, c) {
c = c || 15;
let ht = '';
const s = Math.max(1, h - c + 1);
const bl = [];
for (let i = h; i >= s; i--) {
try {
const ck = 'block_' + i;
let b = window.getCachedData ?
await window.getCachedData(ck, () => x('DERO.GetBlock', {height: i})) :
await x('DERO.GetBlock', {height: i});
if (b && b.block_header) {
const bh = b.block_header;
const now = Date.now() / 1000;
const bt = bh.timestamp || 0;
const age = this.formatAge(Math.abs(now - bt));
const fh = bh.hash || 'N/A';
const bs = bh.block_size || 0;
let sd = '';
if (bs < 1024) sd = bs + 'B';
else if (bs < 1024 * 1024) sd = Math.round(bs / 1024) + 'KB';
else sd = Math.round(bs / (1024 * 1024)) + 'MB';
const mn = bh.miners?.length || b.miners?.length || 1;
const df = (bh.difficulty || 0).toLocaleString();
bl.push({height: i, age: age, sizeDisplay: sd, diff: df, fullHash: fh, miners: mn});
}
} catch (e) {
bl.push({height: i, age: '--', sizeDisplay: '--', diff: '--', fullHash: 'Error', miners: '--', isError: true});
}
}
bl.sort((a, b) => b.height - a.height);
for (const bk of bl) {
if (bk.isError) {
ht += '<div style="color:#ef4444;text-align:center;padding:1rem">Error loading block ' + bk.height + '</div>';
} else {
ht += '<div style="display:grid;grid-template-columns:180px 1fr 70px 80px 100px 70px;gap:0.8rem;padding:0.75rem;border-bottom:1px solid rgba(255,255,255,0.05);align-items:center;font-size:0.85rem;transition:background 0.2s ease;cursor:pointer;" onclick="window.location.hash=\'block/' + bk.height + '\'" onmouseover="this.style.background=\'rgba(255,255,255,0.02)\'" onmouseout="this.style.background=\'transparent\'">';
ht += '<div><div style="color:#52c8db;font-weight:600;font-size:1rem;">' + bk.height.toLocaleString() + '</div><div style="color:#888;font-size:0.75rem;margin-top:0.2rem;">Block Height</div></div>';
ht += '<div style="font-family:monospace;color:#b959b6;font-size:0.75rem;line-height:1.1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + bk.fullHash + '</div>';
ht += '<div style="color:#888;text-align:center;">' + bk.age + '</div>';
ht += '<div style="color:#fbbf24;text-align:center;font-weight:500;">' + bk.sizeDisplay + '</div>';
ht += '<div style="color:#4ade80;text-align:center;font-size:0.8rem;">' + bk.diff + '</div>';
ht += '<div style="color:#888;text-align:right;font-size:0.8rem;">' + bk.miners + '</div>';
ht += '</div>';
}
}
return ht || '<div style="text-align:center;padding:2rem;color:#888;">No blocks available</div>';
},
renderMiniblocksSection: function(m, f, u) {
if (!m || !m.length) {
return '<div style="background:rgba(0,0,0,0.2);border:1px solid rgba(82,200,219,0.3);padding:1.5rem;border-radius:8px;margin-bottom:2rem;"><h3 style="color:#52c8db;">Miniblocks (0)</h3><div style="text-align:center;color:#888;padding:1rem;">No miniblocks found</div></div>';
}
let result = '<div style="background:rgba(0,0,0,0.2);border:1px solid rgba(82,200,219,0.3);padding:1.5rem;border-radius:8px;margin-bottom:2rem;">';
result += '<h3 style="color:#52c8db;margin:0 0 1.5rem 0;font-size:1.2rem;font-weight:600;">Miniblocks (' + m.length + ')</h3>';
result += '<div style="display:grid;grid-template-columns:repeat(auto-fit,minmax(120px,1fr));gap:1rem;margin-bottom:1.5rem;">';
result += '<div style="background:rgba(0,0,0,0.2);padding:1rem;border-radius:6px;text-align:center;border:1px solid rgba(255,255,255,0.05);"><div style="color:#52c8db;font-size:1.5rem;font-weight:600;">' + m.length + '</div><div style="color:#888;font-size:0.8rem;margin-top:0.5rem;">Total</div></div>';
result += '<div style="background:rgba(0,0,0,0.2);padding:1rem;border-radius:6px;text-align:center;border:1px solid rgba(255,255,255,0.05);"><div style="color:#4ade80;font-size:1.5rem;font-weight:600;">' + f + '</div><div style="color:#888;font-size:0.8rem;margin-top:0.5rem;">Final</div></div>';
result += '<div style="background:rgba(0,0,0,0.2);padding:1rem;border-radius:6px;text-align:center;border:1px solid rgba(255,255,255,0.05);"><div style="color:#fbbf24;font-size:1.5rem;font-weight:600;">' + u + '</div><div style="color:#888;font-size:0.8rem;margin-top:0.5rem;">Miners</div></div>';
result += '</div>';
const mbRows = m.map((mb, i) => {
const c = mb.Final ? '#4ade80' : '#fbbf24';
const ic = mb.Final ? '✓' : '●';
const txt = mb.Final ? 'Final' : 'Pending';
const h = mb.KeyHash || 'Unknown';
const t = new Date(mb.Timestamp || 0).toLocaleTimeString();
return '<div style="display:grid;grid-template-columns:60px 1fr 80px 70px 70px;gap:0.8rem;padding:0.75rem;border-bottom:1px solid rgba(255,255,255,0.05);align-items:center;font-size:0.85rem;" onmouseover="this.style.background=\'rgba(255,255,255,0.02)\'" onmouseout="this.style.background=\'transparent\'"><div style="color:#52c8db;font-weight:600;text-align:center;">MB' + (i + 1) + '</div><div style="font-family:monospace;color:#b959b6;font-size:0.75rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;">' + h + '</div><div style="color:white;text-align:center;font-size:0.8rem;">' + t + '</div><div style="color:' + c + ';text-align:center;font-weight:500;font-size:0.85rem;">' + ic + ' ' + txt + '</div><div style="color:#888;text-align:center;font-size:0.8rem;">' + (mb.Flags || 0) + '</div></div>';
}).join('');
result += '<div style="background:rgba(0,0,0,0.1);border-radius:6px;border:1px solid rgba(255,255,255,0.05);overflow:hidden;">';
result += '<div style="display:grid;grid-template-columns:60px 1fr 80px 70px 70px;gap:0.8rem;padding:0.75rem;border-bottom:2px solid rgba(255,255,255,0.1);font-weight:600;color:#52c8db;background:rgba(69,227,221,0.05);"><div style="text-align:center;">#</div><div>Miner Key Hash</div><div style="text-align:center;">Time</div><div style="text-align:center;">Status</div><div style="text-align:center;">Flags</div></div>';
result += mbRows;
result += '</div></div>';
return result;
}
})
*/'] |