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%
3.7 KB · 81 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>transfers — coinexplorer</title>8<link rel="stylesheet" href="/assets/style.css">9</head>10<body>11<main>12  <div class="pagehead"><h1>Transfer explorer</h1>13    <span class="sub">browse the index chain by chain · newest first</span></div>1415  <div class="filters">16    <select id="chainSel" aria-label="Chain"></select>17    <select id="symSel"><option value="">all tokens</option></select>18    <input id="minUsd" type="number" placeholder="min USD" style="width:120px">19    <input id="addr" placeholder="filter by address (from/to)" style="flex:1;min-width:220px">20    <button class="btn" id="apply">Apply</button>21  </div>2223  <section class="card"><h2 id="tableTitle">Transfers</h2>24    <div class="scroll" style="max-height:70vh"><table>25      <thead><tr><th>block</th><th>token</th><th class="num">amount</th><th>from</th><th>to</th><th>tx</th><th>when</th></tr></thead>26      <tbody id="rows"></tbody></table></div>27    <div style="margin-top:8px"><button class="btn" id="more">Load more</button>28      <span class="sub" id="count" style="margin-left:8px"></span></div></section>29</main>3031<script type="module">32import { $, qs, j, fmtUsd, fmtN, fmtAmt, mountNav, addrLink, txLink, tokenLink, ago } from "/assets/app.js";33mountNav("Transfers");34let CHAIN = qs.get("chain") || "ethereum";35let beforeBlock = null, total = 0;3637async function load(append = false) {38  const sym = $("symSel").value, min = $("minUsd").value, addr = $("addr").value.trim();39  let rows;40  if (addr) {41    rows = (await j(`/v1/address/${encodeURIComponent(addr)}/transfers?limit=200`))42      .filter((r) => r.chain === CHAIN || !CHAIN)43      .filter((r) => !sym || r.symbol === sym)44      .filter((r) => !min || parseFloat(r.value) >= +min);45    $("more").style.display = "none";46  } else {47    let url = `/v1/${CHAIN}/transfers?limit=100`;48    if (sym) url += `&symbol=${sym}`;49    if (min) url += `&min_amount=${min}`;50    if (append && beforeBlock) url += `&before_block=${beforeBlock}`;51    rows = await j(url);52    if (rows.length) beforeBlock = rows[rows.length - 1].block;53    $("more").style.display = rows.length ? "" : "none";54  }55  total = append ? total + rows.length : rows.length;56  $("count").textContent = `${fmtN(total)} shown`;57  $("tableTitle").textContent = `Transfers — ${CHAIN}${sym ? " · " + sym : ""}${addr ? " · " + addr.slice(0, 12) + "…" : ""}`;58  const html = rows.map((r) =>59    `<tr><td>${fmtN(r.block)}</td><td>${tokenLink(r.symbol)}</td>60     <td class="num">${fmtAmt(r)}</td><td>${addrLink(r.from)}</td>61     <td>${addrLink(r.to)}</td><td>${txLink(r.chain, r.tx_hash)}</td>62     <td>${ago(r.timestamp)} ago</td></tr>`).join("");63  if (append) $("rows").insertAdjacentHTML("beforeend", html);64  else $("rows").innerHTML = html || `<tr><td colspan="7" class="empty">no matches</td></tr>`;65}6667Promise.all([j("/v1/chains"), j("/v1/tokens")]).then(([chains, tokens]) => {68  $("chainSel").innerHTML = Object.keys(chains).sort()69    .map((c) => `<option ${c === CHAIN ? "selected" : ""}>${c}</option>`).join("");70  $("symSel").innerHTML = `<option value="">all tokens</option>` +71    Object.keys(tokens).sort().map((s) => `<option>${s}</option>`).join("");72  load();73});74$("chainSel").addEventListener("change", (e) => { CHAIN = e.target.value; beforeBlock = null; load(); });75$("apply").addEventListener("click", () => { beforeBlock = null; load(); });76$("addr").addEventListener("keydown", (e) => { if (e.key === "Enter") { beforeBlock = null; load(); } });77$("more").addEventListener("click", () => load(true));78</script>79</body>80</html>81