SPB Git

spb/coinexplorer Public MIT

Self-hosted, zero-API-key explorer for stablecoins and major crypto.

Python 60.3% HTML 23.6% JavaScript 8.1% CSS 6.8% SQL 1%
4.1 KB · 85 lines html
Raw Blame History
1<!doctype html>2<!-- Author: Simon-Pierre Boucher -->3<!-- Mail: contact@spboucher.ai -->4<html lang="en">5<head>6<meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">7<title>flows — coinexplorer</title>8<link rel="stylesheet" href="/assets/style.css">9</head>10<body>11<main>12  <div class="pagehead"><h1>Issuance flows</h1>13    <span class="sub">mints and burns detected on-chain (zero-address transfers, EVM + Tron)</span></div>1415  <div class="filters">16    <select id="tokenSel"></select>17    <div class="presets" role="group" aria-label="Window">18      <button data-w="24h">24h</button><button data-w="7d" aria-pressed="true">7d</button>19    </div>20  </div>2122  <section class="card tile"><h2>Minted <span class="sub" id="w1"></span></h2>23    <div class="big up" id="tMint">—</div></section>24  <section class="card tile"><h2>Burned <span class="sub" id="w2"></span></h2>25    <div class="big down" id="tBurn">—</div></section>26  <section class="card tile"><h2>Net issuance <span class="sub" id="w3"></span></h2>27    <div class="big" id="tNet">—</div></section>28  <section class="card tile"><h2>Events <span class="sub" id="w4"></span></h2>29    <div class="big" id="tEvents">—</div><div class="note">mint/burn transactions listed</div></section>3031  <section class="card"><h2 id="chartTitle">Net issuance over time</h2><div id="netChart"></div>32    <div class="legend"><span><span class="sw" style="background:var(--s1)"></span>net positive (minted)</span>33      <span><span class="sw" style="background:var(--div-neg)"></span>net negative (burned)</span></div></section>3435  <section class="card"><h2>Events</h2>36    <div class="scroll" style="max-height:60vh"><table>37      <thead><tr><th></th><th>chain</th><th>token</th><th class="num">amount</th><th>counterparty</th><th>tx</th><th>when</th></tr></thead>38      <tbody id="rows"></tbody></table></div></section>39</main>4041<script type="module">42import { $, j, fmtUsd, fmtN, fmtAmt, mountNav, columns, addrLink, txLink, chainLink, tokenLink, ago } from "/assets/app.js";43mountNav("Flows");44let TOKEN = "USDC", WINDOW = "7d";45const ivl = () => ({ "24h": "1h", "7d": "6h" }[WINDOW]);4647async function load() {48  const [flows, events] = await Promise.all([49    j(`/v1/stablecoins/flows?token=${TOKEN}&window=${WINDOW}&interval=${ivl()}`),50    j(`/v1/stablecoins/mints-burns?token=${TOKEN}&window=${WINDOW}&limit=100`),51  ]);52  ["w1", "w2", "w3", "w4"].forEach((id) => $(id).textContent = "· " + WINDOW);53  const pts = (flows.tokens[TOKEN] || []).map((b) => ({ t: b.t, v: b.net, minted: b.minted, burned: b.burned }));54  const minted = pts.reduce((a, b) => a + b.minted, 0), burned = pts.reduce((a, b) => a + b.burned, 0);55  $("tMint").textContent = "+" + fmtUsd(minted);56  $("tBurn").textContent = "−" + fmtUsd(burned);57  const net = minted - burned;58  $("tNet").textContent = (net >= 0 ? "+" : "") + fmtUsd(net);59  $("tNet").className = "big " + (net >= 0 ? "up" : "down");60  $("tEvents").textContent = fmtN(events.length);61  $("chartTitle").textContent = `${TOKEN} net issuance — ${WINDOW} (${ivl()} buckets)`;62  columns($("netChart"), pts);63  $("rows").innerHTML = events.map((m) =>64    `<tr><td class="${m.direction}">${m.direction === "mint" ? "▲ mint" : "▼ burn"}</td>65     <td>${chainLink(m.chain)}</td><td>${tokenLink(m.symbol)}</td>66     <td class="num">${fmtAmt(m)}</td>67     <td>${addrLink(m.direction === "mint" ? m.to : m.from)}</td>68     <td>${txLink(m.chain, m.tx_hash)}</td><td>${ago(m.timestamp)} ago</td></tr>`).join("") ||69    `<tr><td colspan="7" class="empty">none in window</td></tr>`;70}7172j("/v1/tokens").then((t) => {73  $("tokenSel").innerHTML = Object.keys(t).sort()74    .map((s) => `<option ${s === TOKEN ? "selected" : ""}>${s}</option>`).join("");75});76$("tokenSel").addEventListener("change", (e) => { TOKEN = e.target.value; load(); });77document.querySelectorAll(".presets button").forEach((b) => b.addEventListener("click", () => {78  document.querySelectorAll(".presets button").forEach((x) => x.setAttribute("aria-pressed", "false"));79  b.setAttribute("aria-pressed", "true"); WINDOW = b.dataset.w; load();80}));81load();82</script>83</body>84</html>85