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%
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>token — coinexplorer</title>8<link rel="stylesheet" href="/assets/style.css">9</head>10<body>11<main>12 <div class="pagehead"><h1 id="title">Token</h1><span class="sub" id="issuerNote"></span></div>13 <div class="filters">14 <select id="tokenSel" aria-label="Token"></select>15 <div class="presets" role="group" aria-label="Window">16 <button data-w="24h" aria-pressed="true">24h</button><button data-w="7d">7d</button>17 </div>18 </div>1920 <section class="card tile"><h2>On-chain supply</h2>21 <div class="big" id="tSupply">—</div><div class="note" id="tSupplyN"></div></section>22 <section class="card tile"><h2>Volume <span class="sub" id="tVolW"></span></h2>23 <div class="big" id="tVol">—</div><div class="note" id="tVolN"></div></section>24 <section class="card tile"><h2>Active senders <span class="sub" id="tActW"></span></h2>25 <div class="big" id="tAct">—</div><div class="note">distinct sending addresses</div></section>26 <section class="card tile"><h2>Net issuance <span class="sub" id="tNetW"></span></h2>27 <div class="big" id="tNet">—</div><div class="note" id="tNetN"></div></section>2829 <section class="card half"><h2 id="volTitle">Volume over time</h2><div id="volChart"></div></section>30 <section class="card half"><h2 id="supTitle">Supply over time</h2><div id="supChart"></div></section>3132 <section class="card third"><h2>Supply by chain</h2><div class="bars" id="supplyBars"></div></section>33 <section class="card twothird"><h2>Largest transfers <span class="sub" id="whaleW"></span></h2>34 <div class="scroll"><table>35 <thead><tr><th>chain</th><th class="num">USD</th><th>from</th><th>to</th><th>tx</th><th>when</th></tr></thead>36 <tbody id="whales"></tbody></table></div></section>3738 <section class="card half"><h2>Mints & burns <span class="sub" id="mbW"></span></h2>39 <div class="scroll"><table>40 <thead><tr><th></th><th>chain</th><th class="num">amount</th><th>tx</th><th>when</th></tr></thead>41 <tbody id="mintburn"></tbody></table></div></section>42 <section class="card half"><h2>Deployments by chain</h2>43 <div class="scroll"><table>44 <thead><tr><th>chain</th><th>identifier</th><th>issuance</th><th class="num">decimals</th></tr></thead>45 <tbody id="deploys"></tbody></table></div></section>46</main>4748<script type="module">49import { $, qs, j, fmtUsd, fmtN, fmtAmt, mountNav, hbars, lineChart, topSeries,50 addrLink, txLink, chainLink, coinIcon, ago } from "/assets/app.js";51mountNav("Tokens");52let TOKEN = (qs.get("symbol") || "USDT").toUpperCase(), WINDOW = "24h";53const ivl = () => ({ "24h": "1h", "7d": "6h" }[WINDOW]);5455async function refresh() {56 document.title = `${TOKEN} — coinexplorer`;57 $("title").innerHTML = `${coinIcon(TOKEN, 26)} ${TOKEN}`;58 const [supply, vol, volSeries, supSeries, flows, tokens] = await Promise.all([59 j(`/v1/stablecoins/supply?token=${TOKEN}`),60 j(`/v1/stablecoins/volume?token=${TOKEN}&window=${WINDOW}`),61 j(`/v1/stablecoins/volume/series?token=${TOKEN}&window=${WINDOW}&interval=${ivl()}`),62 j(`/v1/stablecoins/supply/series?token=${TOKEN}&window=7d`),63 j(`/v1/stablecoins/flows?token=${TOKEN}&window=${WINDOW}&interval=${ivl()}`),64 j("/v1/tokens"),65 ]);66 const s = supply[TOKEN];67 $("tSupply").textContent = s ? fmtUsd(s.total_usd ?? s.total) : "—";68 $("tSupplyN").textContent = s ? `across ${Object.keys(s.chains).length} indexed chains` : "no snapshot yet";69 $("tVol").textContent = fmtUsd(vol.total_volume);70 ["tVolW", "tActW", "tNetW", "whaleW", "mbW"].forEach((id) => $(id).textContent = "· " + WINDOW);71 $("tVolN").textContent = `${fmtN(Object.values(vol.chains).reduce((a, c) => a + c.transfers, 0))} transfers`;72 $("tAct").textContent = fmtN(Object.values(vol.chains).reduce((a, c) => a + c.senders, 0));73 const fl = (flows.tokens[TOKEN] || []);74 const net = fl.reduce((a, b) => a + b.net, 0);75 $("tNet").textContent = (net >= 0 ? "+" : "") + fmtUsd(net);76 $("tNet").className = "big " + (net >= 0 ? "up" : "down");77 $("tNetN").textContent = `minted ${fmtUsd(fl.reduce((a, b) => a + b.minted, 0))} · burned ${fmtUsd(fl.reduce((a, b) => a + b.burned, 0))}`;7879 $("volTitle").textContent = `${TOKEN} volume — ${WINDOW}`;80 lineChart($("volChart"), topSeries(volSeries.chains, 3));81 $("supTitle").textContent = `${TOKEN} supply — 7d (hourly snapshots)`;82 lineChart($("supChart"), topSeries(supSeries.chains, 3, "supply"), { area: false });8384 hbars($("supplyBars"), Object.entries(s ? s.chains : {})85 .map(([k, v]) => ({ k, v: v.usd ?? v.supply, link: chainLink(k) }))86 .sort((a, b) => b.v - a.v).slice(0, 14));8788 const info = tokens[TOKEN] || { chains: {} };89 $("deploys").innerHTML = Object.entries(info.chains).map(([c, d]) =>90 `<tr><td>${chainLink(c)}</td><td class="mono" title="${d.id}">${d.id}</td>91 <td><span class="badge ${d.discontinued ? "discontinued" : d.native ? "native" : "bridged"}">92 ${d.discontinued ? "discontinued" : d.native ? "native" : "bridged"}</span></td>93 <td class="num">${d.decimals ?? "—"}</td></tr>`).join("");94}9596async function refreshLists() {97 const [whales, mb] = await Promise.all([98 j(`/v1/stablecoins/whales?token=${TOKEN}&window=${WINDOW}&limit=25`),99 j(`/v1/stablecoins/mints-burns?token=${TOKEN}&window=${WINDOW}&limit=25`),100 ]);101 $("whales").innerHTML = whales.map((x) =>102 `<tr><td>${chainLink(x.chain)}</td><td class="num"><b>${fmtUsd(x.usd)}</b></td>103 <td>${addrLink(x.from)}</td><td>${addrLink(x.to)}</td>104 <td>${txLink(x.chain, x.tx_hash)}</td><td>${ago(x.timestamp)} ago</td></tr>`).join("") ||105 `<tr><td colspan="6" class="empty">none in window</td></tr>`;106 $("mintburn").innerHTML = mb.map((m) =>107 `<tr><td class="${m.direction}">${m.direction === "mint" ? "▲ mint" : "▼ burn"}</td>108 <td>${chainLink(m.chain)}</td><td class="num">${fmtAmt(m)}</td>109 <td>${txLink(m.chain, m.tx_hash)}</td><td>${ago(m.timestamp)} ago</td></tr>`).join("") ||110 `<tr><td colspan="5" class="empty">none in window</td></tr>`;111}112113j("/v1/tokens").then((t) => {114 $("tokenSel").innerHTML = Object.keys(t).sort()115 .map((s) => `<option ${s === TOKEN ? "selected" : ""}>${s}</option>`).join("");116});117$("tokenSel").addEventListener("change", (e) => {118 TOKEN = e.target.value;119 history.replaceState(null, "", `?symbol=${TOKEN}`);120 refresh(); refreshLists();121});122document.querySelectorAll(".presets button").forEach((b) => b.addEventListener("click", () => {123 document.querySelectorAll(".presets button").forEach((x) => x.setAttribute("aria-pressed", "false"));124 b.setAttribute("aria-pressed", "true"); WINDOW = b.dataset.w; refresh(); refreshLists();125}));126refresh(); refreshLists();127setInterval(refresh, 60_000);128</script>129</body>130</html>131