diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e19fc17..9a21eb2 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -5,6 +5,24 @@ on:
branches: [main]
jobs:
+ fmt:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: denoland/setup-deno@v2
+ with:
+ deno-version: v2.x
+ - run: deno fmt --check
+
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+ - uses: denoland/setup-deno@v2
+ with:
+ deno-version: v2.x
+ - run: deno lint
+
build:
runs-on: ubuntu-latest
steps:
diff --git a/README.md b/README.md
index a380895..31cba3b 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,19 @@
# Network Dashboard
-Public dashboard showing the state of the Moonlight network. Serves as the council discovery layer — councils register their contracts and jurisdictions, anyone can browse.
+Public dashboard showing the state of the Moonlight network. Serves as the
+council discovery layer — councils register their contracts and jurisdictions,
+anyone can browse.
## What it does
-- **Map view**: World map with councils plotted by declared jurisdiction, dot size reflects number of channels
-- **Council list**: All registered councils with on-chain state (supply, provider count, channel assets)
-- **Transaction feed**: Recent on-chain events across all channels (bundles, provider changes)
-- **Council detail**: Drill into a council to see its channels, providers, and activity
+- **Map view**: World map with councils plotted by declared jurisdiction, dot
+ size reflects number of channels
+- **Council list**: All registered councils with on-chain state (supply,
+ provider count, channel assets)
+- **Transaction feed**: Recent on-chain events across all channels (bundles,
+ provider changes)
+- **Council detail**: Drill into a council to see its channels, providers, and
+ activity
## Development
@@ -27,7 +33,8 @@ deno task test
## Testing
-Unit tests cover DOM helpers, SVG sanitization, provider counting logic, URL validation, config, and routing.
+Unit tests cover DOM helpers, SVG sanitization, provider counting logic, URL
+validation, config, and routing.
```bash
deno task test
@@ -35,17 +42,21 @@ deno task test
## Deployment
-Static files are deployed to a public [Tigris](https://www.tigrisdata.com/) bucket on Fly.io.
+Static files are deployed to a public [Tigris](https://www.tigrisdata.com/)
+bucket on Fly.io.
- **Bucket**: `network-dashboard`
- **URL**: https://network-dashboard.fly.storage.tigris.dev/index.html
- **Auto-deploy**: bump `version` in `deno.json`, push to `main`
-- **Secrets** (set in GitHub repo settings): `TIGRIS_ACCESS_KEY_ID`, `TIGRIS_SECRET_ACCESS_KEY`, `COUNCILS_JSON`
+- **Secrets** (set in GitHub repo settings): `TIGRIS_ACCESS_KEY_ID`,
+ `TIGRIS_SECRET_ACCESS_KEY`, `COUNCILS_JSON`
Pipeline:
-1. Push to `main` triggers `auto-version.yml` (reads version from `deno.json`, creates git tag)
-2. Tag push (`v*`) triggers `deploy.yml` (generates config from secrets, builds production bundle, deploys to Tigris)
+1. Push to `main` triggers `auto-version.yml` (reads version from `deno.json`,
+ creates git tag)
+2. Tag push (`v*`) triggers `deploy.yml` (generates config from secrets, builds
+ production bundle, deploys to Tigris)
### Manual deploy
@@ -58,7 +69,9 @@ aws s3 sync public/ s3://network-dashboard/ \
## Architecture
-Static SPA (no backend, no auth). Reads on-chain state via Stellar RPC. World map data fetched from jsDelivr CDN (Natural Earth TopoJSON). Council registry is a hardcoded config for MVP (eventually an API).
+Static SPA (no backend, no auth). Reads on-chain state via Stellar RPC. World
+map data fetched from jsDelivr CDN (Natural Earth TopoJSON). Council registry is
+a hardcoded config for MVP (eventually an API).
```
Browser
@@ -71,9 +84,9 @@ Browser
Required for CI deploys:
-| Secret | Purpose |
-|--------|---------|
-| `TIGRIS_ACCESS_KEY_ID` | Tigris CDN upload |
-| `TIGRIS_SECRET_ACCESS_KEY` | Tigris CDN upload |
-| `COUNCILS_JSON` | Council registry (JSON array) |
-| `AUTO_VERSION_TOKEN` | PAT for auto-tag workflow |
+| Secret | Purpose |
+| -------------------------- | ----------------------------- |
+| `TIGRIS_ACCESS_KEY_ID` | Tigris CDN upload |
+| `TIGRIS_SECRET_ACCESS_KEY` | Tigris CDN upload |
+| `COUNCILS_JSON` | Council registry (JSON array) |
+| `AUTO_VERSION_TOKEN` | PAT for auto-tag workflow |
diff --git a/deno.json b/deno.json
index dd66b3c..831862d 100644
--- a/deno.json
+++ b/deno.json
@@ -1,5 +1,5 @@
{
- "version": "0.2.3",
+ "version": "0.2.4",
"license": "MIT",
"tasks": {
"dev": "deno run --allow-all --watch src/server.ts",
diff --git a/public/config.js b/public/config.js
index 285bfbc..da784ba 100644
--- a/public/config.js
+++ b/public/config.js
@@ -1,6 +1,6 @@
-window.__DASHBOARD_CONFIG__ = {
+globalThis.__DASHBOARD_CONFIG__ = {
environment: "development",
stellarNetwork: "testnet",
rpcUrl: "https://soroban-testnet.stellar.org",
- councilPlatformUrl: "http://localhost:3115"
+ councilPlatformUrl: "http://localhost:3115",
};
diff --git a/public/index.html b/public/index.html
index 1e1143e..7cbc843 100644
--- a/public/index.html
+++ b/public/index.html
@@ -1,14 +1,14 @@
-
-
-
- Moonlight Network Dashboard
-
-
-
-
-
-
-
+
+
+
+ Moonlight Network Dashboard
+
+
+
+
+
+
+
diff --git a/public/styles.css b/public/styles.css
index 222a405..6f3518e 100644
--- a/public/styles.css
+++ b/public/styles.css
@@ -13,7 +13,11 @@
--font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
-* { margin: 0; padding: 0; box-sizing: border-box; }
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
body {
font-family: var(--font-sans);
@@ -43,7 +47,9 @@ nav {
text-decoration: none;
}
-.nav-brand:hover { opacity: 0.9; }
+.nav-brand:hover {
+ opacity: 0.9;
+}
.nav-links {
display: flex;
@@ -58,7 +64,9 @@ nav {
transition: color 0.15s;
}
-.nav-links a:hover { color: var(--text); }
+.nav-links a:hover {
+ color: var(--text);
+}
.container {
max-width: 1200px;
@@ -84,9 +92,15 @@ nav {
gap: 0.25rem;
}
-.stat-card.active { border-color: var(--active); }
-.stat-card.pending { border-color: var(--pending); }
-.stat-card.inactive { border-color: var(--inactive); }
+.stat-card.active {
+ border-color: var(--active);
+}
+.stat-card.pending {
+ border-color: var(--pending);
+}
+.stat-card.inactive {
+ border-color: var(--inactive);
+}
.stat-value {
font-size: 1.5rem;
@@ -125,7 +139,9 @@ th {
word-break: break-all;
}
-.text-muted { color: var(--text-muted); }
+.text-muted {
+ color: var(--text-muted);
+}
.badge {
display: inline-block;
@@ -136,9 +152,18 @@ th {
text-transform: uppercase;
}
-.badge-active { background: rgba(34, 197, 94, 0.15); color: var(--active); }
-.badge-pending { background: rgba(245, 158, 11, 0.15); color: var(--pending); }
-.badge-inactive { background: rgba(239, 68, 68, 0.15); color: var(--inactive); }
+.badge-active {
+ background: rgba(34, 197, 94, 0.15);
+ color: var(--active);
+}
+.badge-pending {
+ background: rgba(245, 158, 11, 0.15);
+ color: var(--pending);
+}
+.badge-inactive {
+ background: rgba(239, 68, 68, 0.15);
+ color: var(--inactive);
+}
.btn-link {
background: none;
@@ -150,7 +175,9 @@ th {
text-decoration: none;
}
-.btn-link:hover { color: var(--text); }
+.btn-link:hover {
+ color: var(--text);
+}
.login-container {
display: flex;
@@ -184,8 +211,16 @@ th {
font-size: 0.875rem;
}
-h2 { margin-bottom: 0.5rem; }
-h3 { margin: 1.5rem 0 0.5rem; color: var(--text-muted); font-size: 0.875rem; text-transform: uppercase; letter-spacing: 0.05em; }
+h2 {
+ margin-bottom: 0.5rem;
+}
+h3 {
+ margin: 1.5rem 0 0.5rem;
+ color: var(--text-muted);
+ font-size: 0.875rem;
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
.empty-state {
background: var(--surface);
@@ -195,7 +230,9 @@ h3 { margin: 1.5rem 0 0.5rem; color: var(--text-muted); font-size: 0.875rem; tex
margin-top: 1rem;
}
-.empty-state p { margin-bottom: 0.75rem; }
+.empty-state p {
+ margin-bottom: 0.75rem;
+}
.version-badge {
font-size: 0.7rem;
diff --git a/public/world-map.svg b/public/world-map.svg
index 8f4ca25..10435de 100644
--- a/public/world-map.svg
+++ b/public/world-map.svg
@@ -1 +1,836 @@
-
\ No newline at end of file
+
diff --git a/src/app.ts b/src/app.ts
index e5d4059..e00f669 100644
--- a/src/app.ts
+++ b/src/app.ts
@@ -1,4 +1,4 @@
-import { route, startRouter, navigate } from "./lib/router.ts";
+import { navigate, route, startRouter } from "./lib/router.ts";
import { mapView } from "./views/map.ts";
import { councilsView } from "./views/councils.ts";
import { councilDetailView } from "./views/council-detail.ts";
@@ -17,7 +17,8 @@ route("/", () => {
route("/404", () => {
const el = document.createElement("div");
el.className = "login-container";
- el.innerHTML = ``;
+ el.innerHTML =
+ ``;
return el;
});
diff --git a/src/build.ts b/src/build.ts
index 6fe2991..e8392e9 100644
--- a/src/build.ts
+++ b/src/build.ts
@@ -2,7 +2,9 @@
* Bundles src/app.ts into public/app.js for the browser.
* Uses esbuild via Deno with denoPlugins for import map resolution.
*/
+// deno-lint-ignore no-import-prefix -- build script intentionally pins the URL
import * as esbuild from "https://deno.land/x/esbuild@v0.20.1/mod.js";
+// deno-lint-ignore no-import-prefix -- build script intentionally pins the version
import { denoPlugins } from "jsr:@luca/esbuild-deno-loader@0.10";
const isProduction = Deno.args.includes("--production");
@@ -11,7 +13,9 @@ const version = denoJson.version ?? "0.0.0";
async function resolveSorobanCoreVersion(): Promise {
try {
- const res = await fetch("https://api.github.com/repos/Moonlight-Protocol/soroban-core/releases/latest");
+ const res = await fetch(
+ "https://api.github.com/repos/Moonlight-Protocol/soroban-core/releases/latest",
+ );
if (!res.ok) return "unknown";
const release = await res.json();
return ((release.tag_name as string) ?? "unknown").replace(/^v/, "");
diff --git a/src/components/nav.ts b/src/components/nav.ts
index e070ca5..0c0e244 100644
--- a/src/components/nav.ts
+++ b/src/components/nav.ts
@@ -7,7 +7,9 @@ export function renderNav(): HTMLElement {
const nav = document.createElement("nav");
nav.innerHTML = `
-
Moonlight Network v${escapeHtml(appVersion)}
+
Moonlight Network v${
+ escapeHtml(appVersion)
+ }
Map
Councils
diff --git a/src/lib/config.ts b/src/lib/config.ts
index f65862a..df36e5b 100644
--- a/src/lib/config.ts
+++ b/src/lib/config.ts
@@ -31,12 +31,14 @@ declare global {
// Read config from globalThis to work in both browser and Deno test environments.
// window.__DASHBOARD_CONFIG__ is set by config.js which loads before app.js.
-const config: DashboardConfig | undefined =
- "__DASHBOARD_CONFIG__" in globalThis
- ? (globalThis as Record
).__DASHBOARD_CONFIG__ as DashboardConfig
- : undefined;
+const config: DashboardConfig | undefined = "__DASHBOARD_CONFIG__" in globalThis
+ ? (globalThis as Record)
+ .__DASHBOARD_CONFIG__ as DashboardConfig
+ : undefined;
if (!config && typeof document !== "undefined") {
- console.warn("Dashboard config not found — using testnet defaults. Ensure config.js loads before app.js.");
+ console.warn(
+ "Dashboard config not found — using testnet defaults. Ensure config.js loads before app.js.",
+ );
}
const c = config ?? {};
@@ -49,9 +51,12 @@ export const COUNCIL_PLATFORM_URL = c.councilPlatformUrl ?? "";
export function getNetworkPassphrase(): string {
switch (STELLAR_NETWORK) {
- case "mainnet": return "Public Global Stellar Network ; September 2015";
- case "standalone": return "Standalone Network ; February 2017";
- default: return "Test SDF Network ; September 2015";
+ case "mainnet":
+ return "Public Global Stellar Network ; September 2015";
+ case "standalone":
+ return "Standalone Network ; February 2017";
+ default:
+ return "Test SDF Network ; September 2015";
}
}
@@ -63,7 +68,9 @@ interface PlatformCouncilEntry {
function mapPlatformCouncils(entries: PlatformCouncilEntry[]): CouncilConfig[] {
return entries
- .filter((e): e is PlatformCouncilEntry & { council: { channelAuthId: string } } =>
+ .filter((
+ e,
+ ): e is PlatformCouncilEntry & { council: { channelAuthId: string } } =>
!!e.council?.channelAuthId
)
.map((e) => ({
@@ -95,15 +102,21 @@ export function getCouncils(): Promise {
if (councilsCache) return councilsCache;
if (!COUNCIL_PLATFORM_URL) {
- console.warn("councilPlatformUrl not configured — council list will be empty.");
+ console.warn(
+ "councilPlatformUrl not configured — council list will be empty.",
+ );
councilsCache = Promise.resolve([]);
return councilsCache;
}
- const url = `${COUNCIL_PLATFORM_URL.replace(/\/+$/, "")}/api/v1/public/councils`;
+ const url = `${
+ COUNCIL_PLATFORM_URL.replace(/\/+$/, "")
+ }/api/v1/public/councils`;
councilsCache = fetch(url)
.then(async (res) => {
- if (!res.ok) throw new Error(`council-platform returned HTTP ${res.status}`);
+ if (!res.ok) {
+ throw new Error(`council-platform returned HTTP ${res.status}`);
+ }
const body = await res.json();
const data = Array.isArray(body?.data) ? body.data : [];
return mapPlatformCouncils(data);
diff --git a/src/lib/dom.ts b/src/lib/dom.ts
index 0123457..2d5b281 100644
--- a/src/lib/dom.ts
+++ b/src/lib/dom.ts
@@ -2,7 +2,11 @@
* Safe DOM helpers to avoid innerHTML XSS.
*/
-export function renderError(container: HTMLElement, title: string, message: string): void {
+export function renderError(
+ container: HTMLElement,
+ title: string,
+ message: string,
+): void {
container.textContent = "";
const h2 = document.createElement("h2");
h2.textContent = title;
@@ -36,19 +40,28 @@ export function formatAmount(stroops: bigint | number | string): string {
} else if (typeof stroops === "number" && isFinite(stroops)) {
bi = BigInt(Math.trunc(stroops));
} else if (typeof stroops === "string") {
- try { bi = BigInt(stroops || "0"); } catch { bi = 0n; }
+ try {
+ bi = BigInt(stroops || "0");
+ } catch {
+ bi = 0n;
+ }
} else {
bi = 0n;
}
const whole = bi / 10_000_000n;
const frac = bi % 10_000_000n;
- const fracStr = (frac < 0n ? -frac : frac).toString().padStart(7, "0").slice(0, 2);
+ const fracStr = (frac < 0n ? -frac : frac).toString().padStart(7, "0").slice(
+ 0,
+ 2,
+ );
const wholeStr = whole.toLocaleString();
return `${wholeStr}.${fracStr}`;
}
export function timeAgo(isoOrSeconds: string | number): string {
- const ts = typeof isoOrSeconds === "string" ? new Date(isoOrSeconds).getTime() : isoOrSeconds * 1000;
+ const ts = typeof isoOrSeconds === "string"
+ ? new Date(isoOrSeconds).getTime()
+ : isoOrSeconds * 1000;
const diff = Math.floor((Date.now() - ts) / 1000);
if (diff < 60) return `${diff}s ago`;
if (diff < 3600) return `${Math.floor(diff / 60)}m ago`;
diff --git a/src/lib/dom_test.ts b/src/lib/dom_test.ts
index b2f0421..2c84e7e 100644
--- a/src/lib/dom_test.ts
+++ b/src/lib/dom_test.ts
@@ -1,5 +1,5 @@
import { assertEquals } from "@std/assert";
-import { truncateAddress, formatAmount, timeAgo, sanitizeUrl } from "./dom.ts";
+import { formatAmount, sanitizeUrl, timeAgo, truncateAddress } from "./dom.ts";
Deno.test("truncateAddress shortens long addresses", () => {
const addr = "CAF7DFHTPSYIW5543WBXJODZCDI5WF5SSHBXGMPKFOYPFRDVWFDNBGX7";
diff --git a/src/lib/geo_test.ts b/src/lib/geo_test.ts
index 3ffe5af..4455a06 100644
--- a/src/lib/geo_test.ts
+++ b/src/lib/geo_test.ts
@@ -1,5 +1,10 @@
import { assertEquals } from "@std/assert";
-import { COUNTRIES, getCountryName, projectCountry, sanitizeSvgPath } from "./world-map.ts";
+import {
+ COUNTRIES,
+ getCountryName,
+ projectCountry,
+ sanitizeSvgPath,
+} from "./world-map.ts";
Deno.test("getCountryName returns full name for known code", () => {
assertEquals(getCountryName("US"), "United States");
diff --git a/src/lib/router.ts b/src/lib/router.ts
index 8c72558..b05b0c1 100644
--- a/src/lib/router.ts
+++ b/src/lib/router.ts
@@ -4,7 +4,9 @@
*/
import { renderError } from "./dom.ts";
-type RouteHandler = (params?: Record) => HTMLElement | Promise;
+type RouteHandler = (
+ params?: Record,
+) => HTMLElement | Promise;
const routes = new Map();
let cleanups: (() => void)[] = [];
@@ -14,15 +16,17 @@ export function route(path: string, handler: RouteHandler): void {
}
export function navigate(path: string, opts?: { force?: boolean }): void {
- const current = window.location.hash.replace(/^#/, "");
+ const current = globalThis.location.hash.replace(/^#/, "");
if (opts?.force && current === path) {
render();
} else {
- window.location.hash = path;
+ globalThis.location.hash = path;
}
}
-function matchRoute(path: string): { handler: RouteHandler; params: Record } | null {
+function matchRoute(
+ path: string,
+): { handler: RouteHandler; params: Record } | null {
// Exact match first
const exact = routes.get(path);
if (exact) return { handler: exact, params: {} };
@@ -50,10 +54,13 @@ function matchRoute(path: string): { handler: RouteHandler; params: Record {
- const hash = window.location.hash || "#/";
- const path = hash.startsWith("#") ? hash.slice(1).split("?")[0] : hash.split("?")[0];
+ const hash = globalThis.location.hash || "#/";
+ const path = hash.startsWith("#")
+ ? hash.slice(1).split("?")[0]
+ : hash.split("?")[0];
- const matched = matchRoute(path) || (routes.has("/404") ? { handler: routes.get("/404")!, params: {} } : null);
+ const matched = matchRoute(path) ||
+ (routes.has("/404") ? { handler: routes.get("/404")!, params: {} } : null);
if (!matched) return;
for (const fn of cleanups) {
@@ -81,11 +88,11 @@ async function render(): Promise {
app.appendChild(container);
}
- window.scrollTo(0, 0);
+ globalThis.scrollTo(0, 0);
}
export function startRouter(): void {
- window.addEventListener("hashchange", render);
+ globalThis.addEventListener("hashchange", render);
render();
}
diff --git a/src/lib/router_test.ts b/src/lib/router_test.ts
index 3734cc1..c7579ef 100644
--- a/src/lib/router_test.ts
+++ b/src/lib/router_test.ts
@@ -31,7 +31,11 @@ function matchRoute(
}
Deno.test("exact route match", () => {
- const result = matchRoute("/councils", ["/map", "/councils", "/transactions"]);
+ const result = matchRoute("/councils", [
+ "/map",
+ "/councils",
+ "/transactions",
+ ]);
assertEquals(result?.pattern, "/councils");
assertEquals(result?.params, {});
});
diff --git a/src/lib/stellar.ts b/src/lib/stellar.ts
index 1ee2a6e..f1519ef 100644
--- a/src/lib/stellar.ts
+++ b/src/lib/stellar.ts
@@ -2,12 +2,13 @@
* Read-only Stellar/Soroban helpers for querying contract state.
* No wallet, no signing — purely read operations.
*/
-import { RPC_URL, getNetworkPassphrase } from "./config.ts";
+import { getNetworkPassphrase, RPC_URL } from "./config.ts";
const REQUEST_TIMEOUT_MS = 15_000;
/** Errors encountered during the current view's queries. Cleared on each navigation. */
-export const queryErrors: { source: string; message: string; time: number }[] = [];
+export const queryErrors: { source: string; message: string; time: number }[] =
+ [];
/** Generation counter to scope errors to the current view load. */
let queryGeneration = 0;
@@ -28,12 +29,25 @@ function recordError(source: string, err: unknown, generation: number): void {
}
/** Wrap a promise with a timeout. */
-function withTimeout(promise: Promise, ms: number, label: string): Promise {
+function withTimeout(
+ promise: Promise,
+ ms: number,
+ label: string,
+): Promise {
return new Promise((resolve, reject) => {
- const timer = setTimeout(() => reject(new Error(`${label} timed out after ${ms}ms`)), ms);
+ const timer = setTimeout(
+ () => reject(new Error(`${label} timed out after ${ms}ms`)),
+ ms,
+ );
promise.then(
- (v) => { clearTimeout(timer); resolve(v); },
- (e) => { clearTimeout(timer); reject(e); },
+ (v) => {
+ clearTimeout(timer);
+ resolve(v);
+ },
+ (e) => {
+ clearTimeout(timer);
+ reject(e);
+ },
);
});
}
@@ -45,7 +59,10 @@ function withTimeout(promise: Promise, ms: number, label: string): Promise
interface StellarSdkSubset {
Contract: new (id: string) => StellarContract;
Account: new (publicKey: string, sequence: string) => StellarAccount;
- TransactionBuilder: new (account: StellarAccount, opts: { fee: string; networkPassphrase: string }) => TxBuilder;
+ TransactionBuilder: new (
+ account: StellarAccount,
+ opts: { fee: string; networkPassphrase: string },
+ ) => TxBuilder;
scValToNative(val: unknown): unknown;
rpc: {
Server: new (url: string) => RpcServer;
@@ -139,8 +156,12 @@ export async function getChannelSupply(contractId: string): Promise {
"getChannelSupply",
);
if (typeof result === "bigint") return result;
- if (typeof result === "number" && isFinite(result)) return BigInt(Math.trunc(result));
- if (typeof result === "string" && /^-?\d+$/.test(result)) return BigInt(result);
+ if (typeof result === "number" && isFinite(result)) {
+ return BigInt(Math.trunc(result));
+ }
+ if (typeof result === "string" && /^-?\d+$/.test(result)) {
+ return BigInt(result);
+ }
throw new Error(`Unexpected supply type: ${typeof result}`);
} catch (err) {
recordError(`getChannelSupply(${contractId.slice(0, 8)})`, err, gen);
@@ -248,7 +269,9 @@ export function countProvidersFromEvents(events: ContractEvent[]): string[] {
if (e.type === "ProviderRemoved") state.set(addr, false);
}
- return [...state.entries()].filter(([, active]) => active).map(([addr]) => addr);
+ return [...state.entries()].filter(([, active]) => active).map(([addr]) =>
+ addr
+ );
}
function safeScValToNative(s: StellarSdkSubset, val: unknown): unknown {
diff --git a/src/lib/stellar_test.ts b/src/lib/stellar_test.ts
index 75702d6..ffafd17 100644
--- a/src/lib/stellar_test.ts
+++ b/src/lib/stellar_test.ts
@@ -1,9 +1,25 @@
import { assertEquals } from "@std/assert";
-import { queryErrors, clearQueryErrors, countProvidersFromEvents } from "./stellar.ts";
+import {
+ clearQueryErrors,
+ countProvidersFromEvents,
+ queryErrors,
+} from "./stellar.ts";
import type { ContractEvent } from "./stellar.ts";
-function mockEvent(type: string, value: string | null, ledger = 1): ContractEvent {
- return { id: "e1", type, contractId: "C...", ledger, timestamp: "", topic: [], value };
+function mockEvent(
+ type: string,
+ value: string | null,
+ ledger = 1,
+): ContractEvent {
+ return {
+ id: "e1",
+ type,
+ contractId: "C...",
+ ledger,
+ timestamp: "",
+ topic: [],
+ value,
+ };
}
Deno.test("queryErrors starts empty", () => {
diff --git a/src/lib/version-check.ts b/src/lib/version-check.ts
index f2a51d4..1cbd330 100644
--- a/src/lib/version-check.ts
+++ b/src/lib/version-check.ts
@@ -14,7 +14,9 @@ interface VersionEntry {
async function fetchLatestRelease(repo: string): Promise {
try {
- const res = await fetch(`https://api.github.com/repos/Moonlight-Protocol/${repo}/releases/latest`);
+ const res = await fetch(
+ `https://api.github.com/repos/Moonlight-Protocol/${repo}/releases/latest`,
+ );
if (!res.ok) return null;
const data = await res.json();
return (data.tag_name ?? "").replace(/^v/, "");
@@ -24,7 +26,8 @@ async function fetchLatestRelease(repo: string): Promise {
}
function esc(s: string): string {
- return s.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """);
+ return s.replace(/&/g, "&").replace(//g, ">")
+ .replace(/"/g, """);
}
function renderBanner(entries: VersionEntry[]): HTMLElement {
@@ -47,7 +50,9 @@ function renderBanner(entries: VersionEntry[]): HTMLElement {
return `${text}`;
});
- banner.innerHTML = spans.join(' · ');
+ banner.innerHTML = spans.join(
+ ' · ',
+ );
return banner;
}
@@ -56,12 +61,22 @@ export async function checkVersions(): Promise {
const entries: VersionEntry[] = [];
const appLatest = await fetchLatestRelease("network-dashboard");
- entries.push({ name: "network-dashboard", local: __APP_VERSION__, latest: appLatest });
+ entries.push({
+ name: "network-dashboard",
+ local: __APP_VERSION__,
+ latest: appLatest,
+ });
- const scVersion = typeof __SOROBAN_CORE_VERSION__ !== "undefined" ? __SOROBAN_CORE_VERSION__ : null;
+ const scVersion = typeof __SOROBAN_CORE_VERSION__ !== "undefined"
+ ? __SOROBAN_CORE_VERSION__
+ : null;
if (scVersion && scVersion !== "unknown") {
const scLatest = await fetchLatestRelease("soroban-core");
- entries.push({ name: "soroban-core", local: scVersion, latest: scLatest });
+ entries.push({
+ name: "soroban-core",
+ local: scVersion,
+ latest: scLatest,
+ });
}
if (entries.length === 0) return null;
diff --git a/src/lib/world-map.ts b/src/lib/world-map.ts
index d0bf6f7..6c49290 100644
--- a/src/lib/world-map.ts
+++ b/src/lib/world-map.ts
@@ -46,7 +46,10 @@ export async function fetchWorldSvg(): Promise {
* Country coordinates (centroids, lon/lat).
* Projected at render time into the SVG's coordinate space.
*/
-export const COUNTRIES: Record = {
+export const COUNTRIES: Record<
+ string,
+ { lon: number; lat: number; name: string }
+> = {
// Americas
US: { lon: -98, lat: 39, name: "United States" },
CA: { lon: -106, lat: 56, name: "Canada" },
diff --git a/src/server.ts b/src/server.ts
index 69ce0db..fefcc96 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -2,7 +2,7 @@
* Static file server for the network dashboard.
* Serves files from public/ with security headers and path sanitization.
*/
-import { resolve, normalize } from "@std/path";
+import { normalize, resolve } from "@std/path";
const rawPort = Deno.env.get("PORT") || "3030";
const PORT = /^\d+$/.test(rawPort) ? Number(rawPort) : 3030;
@@ -17,7 +17,9 @@ const SECURITY_HEADERS: Record = {
function getCSP(): string {
const environment = Deno.env.get("ENVIRONMENT") || "development";
- const devSources = environment !== "production" ? " https://api.github.com" : "";
+ const devSources = environment !== "production"
+ ? " https://api.github.com"
+ : "";
return [
"default-src 'self'",
"script-src 'self'",
@@ -81,12 +83,14 @@ Deno.serve({ port: PORT }, async (req) => {
const cacheControl = ext === "html"
? "no-cache, no-store, must-revalidate"
: "public, max-age=3600";
- return addSecurityHeaders(new Response(file, {
- headers: {
- "Content-Type": contentTypes[ext] || "application/octet-stream",
- "Cache-Control": cacheControl,
- },
- }));
+ return addSecurityHeaders(
+ new Response(file, {
+ headers: {
+ "Content-Type": contentTypes[ext] || "application/octet-stream",
+ "Cache-Control": cacheControl,
+ },
+ }),
+ );
} catch {
const ext = pathname.split("/").pop()?.includes(".") ?? false;
if (ext) {
@@ -94,12 +98,14 @@ Deno.serve({ port: PORT }, async (req) => {
}
try {
const index = await Deno.readFile(resolve(PUBLIC_ROOT, "index.html"));
- return addSecurityHeaders(new Response(index, {
- headers: {
- "Content-Type": "text/html; charset=utf-8",
- "Cache-Control": "no-cache, no-store, must-revalidate",
- },
- }));
+ return addSecurityHeaders(
+ new Response(index, {
+ headers: {
+ "Content-Type": "text/html; charset=utf-8",
+ "Cache-Control": "no-cache, no-store, must-revalidate",
+ },
+ }),
+ );
} catch {
return addSecurityHeaders(new Response("Not Found", { status: 404 }));
}
diff --git a/src/views/council-detail.ts b/src/views/council-detail.ts
index 92279e1..49934be 100644
--- a/src/views/council-detail.ts
+++ b/src/views/council-detail.ts
@@ -3,14 +3,29 @@
*/
import { renderNav } from "../components/nav.ts";
import { getCouncils } from "../lib/config.ts";
-import { getChannelSupply, getContractEvents, countProvidersFromEvents, queryErrors, clearQueryErrors } from "../lib/stellar.ts";
-import { escapeHtml, truncateAddress, formatAmount, timeAgo, sanitizeUrl } from "../lib/dom.ts";
+import {
+ clearQueryErrors,
+ countProvidersFromEvents,
+ getChannelSupply,
+ getContractEvents,
+ queryErrors,
+} from "../lib/stellar.ts";
+import {
+ escapeHtml,
+ formatAmount,
+ sanitizeUrl,
+ timeAgo,
+ truncateAddress,
+} from "../lib/dom.ts";
import { getCountryName } from "../lib/world-map.ts";
import { onCleanup } from "../lib/router.ts";
import type { CouncilConfig } from "../lib/config.ts";
import type { ContractEvent } from "../lib/stellar.ts";
-export async function councilDetailView(params?: Record): Promise {
+// deno-lint-ignore require-await -- view fn satisfies router's Promise contract
+export async function councilDetailView(
+ params?: Record,
+): Promise {
const el = document.createElement("div");
el.appendChild(renderNav());
@@ -28,7 +43,9 @@ export async function councilDetailView(params?: Record): Promis
el.appendChild(main);
const ctx = { cancelled: false };
- onCleanup(() => { ctx.cancelled = true; });
+ onCleanup(() => {
+ ctx.cancelled = true;
+ });
getCouncils()
.then((councils) => {
@@ -38,7 +55,9 @@ export async function councilDetailView(params?: Record): Promis
if (!council) {
main.innerHTML = `
Council Not Found
- No council registered with ID ${escapeHtml(truncateAddress(councilId))}
+ No council registered with ID ${
+ escapeHtml(truncateAddress(councilId))
+ }
Back to councils
`;
return;
@@ -51,8 +70,20 @@ export async function councilDetailView(params?: Record): Promis
← All Councils
${escapeHtml(council.name)}
-
${escapeHtml(council.channelAuthId)}
-
${council.jurisdictions.map((j) => escapeHtml(getCountryName(j))).join(", ")}${safeWebsite ? ` · ${escapeHtml(council.website!)}` : ""}
+
${
+ escapeHtml(council.channelAuthId)
+ }
+
${
+ council.jurisdictions.map((j) => escapeHtml(getCountryName(j))).join(
+ ", ",
+ )
+ }${
+ safeWebsite
+ ? ` · ${escapeHtml(council.website!)}`
+ : ""
+ }
`;
@@ -70,7 +101,11 @@ export async function councilDetailView(params?: Record
): Promis
return el;
}
-async function loadCouncilDetail(main: HTMLElement, council: CouncilConfig, ctx: { cancelled: boolean }): Promise {
+async function loadCouncilDetail(
+ main: HTMLElement,
+ council: CouncilConfig,
+ ctx: { cancelled: boolean },
+): Promise {
clearQueryErrors();
const channelData: { id: string; asset: string; supply: bigint }[] = [];
const allEvents: ContractEvent[] = [];
@@ -79,21 +114,25 @@ async function loadCouncilDetail(main: HTMLElement, council: CouncilConfig, ctx:
for (const ch of council.channels) {
promises.push(
- getChannelSupply(ch.privacyChannelId).then(supply => {
- channelData.push({ id: ch.privacyChannelId, asset: ch.assetCode, supply });
+ getChannelSupply(ch.privacyChannelId).then((supply) => {
+ channelData.push({
+ id: ch.privacyChannelId,
+ asset: ch.assetCode,
+ supply,
+ });
}),
);
}
promises.push(
- getContractEvents(council.channelAuthId, undefined, 100).then(events => {
+ getContractEvents(council.channelAuthId, undefined, 100).then((events) => {
allEvents.push(...events);
}),
);
for (const ch of council.channels) {
promises.push(
- getContractEvents(ch.privacyChannelId, undefined, 100).then(events => {
+ getContractEvents(ch.privacyChannelId, undefined, 100).then((events) => {
allEvents.push(...events);
}),
);
@@ -108,12 +147,14 @@ async function loadCouncilDetail(main: HTMLElement, council: CouncilConfig, ctx:
if (!content) return;
const totalSupply = channelData.reduce((sum, c) => sum + c.supply, 0n);
- const txEvents = allEvents.filter(e =>
- !["ProviderAdded", "ProviderRemoved", "ContractInitialized"].includes(e.type)
+ const txEvents = allEvents.filter((e) =>
+ !["ProviderAdded", "ProviderRemoved", "ContractInitialized"].includes(
+ e.type,
+ )
);
// Derive active providers using chronological event processing
- const authEvents = allEvents.filter(e =>
+ const authEvents = allEvents.filter((e) =>
e.contractId === council.channelAuthId &&
(e.type === "ProviderAdded" || e.type === "ProviderRemoved")
);
@@ -123,7 +164,11 @@ async function loadCouncilDetail(main: HTMLElement, council: CouncilConfig, ctx:
const hasErrors = queryErrors.length > 0;
content.innerHTML = `
- ${hasErrors ? `Some data may be incomplete — network queries failed.
` : ""}
+ ${
+ hasErrors
+ ? `Some data may be incomplete — network queries failed.
`
+ : ""
+ }
@@ -154,17 +199,21 @@ async function loadCouncilDetail(main: HTMLElement, council: CouncilConfig, ctx:
- ${channelData.map(ch => `
+ ${
+ channelData.map((ch) => `
| ${truncateAddress(ch.id)} |
${escapeHtml(ch.asset)} |
${formatAmount(ch.supply)} |
- `).join("")}
+ `).join("")
+ }
- ${activeProviders.length > 0 ? `
+ ${
+ activeProviders.length > 0
+ ? `
Registered Providers
@@ -173,33 +222,53 @@ async function loadCouncilDetail(main: HTMLElement, council: CouncilConfig, ctx:
- ${activeProviders.map(p => `
+ ${
+ activeProviders.map((p) => `
| ${escapeHtml(p)} |
- `).join("")}
+ `).join("")
+ }
- ` : `
+ `
+ : `
Registered Providers
No providers discovered from recent on-chain events.
- `}
+ `
+ }
Recent Activity
- ${allEvents.length > 0 ? `
+ ${
+ allEvents.length > 0
+ ? `
- ${allEvents.slice(0, 50).map(e => `
+ ${
+ allEvents.slice(0, 50).map((e) => `
-
${truncateAddress(e.contractId)}
+
${
+ truncateAddress(e.contractId)
+ }
- `).join("")}
+ `).join("")
+ }
- ` : `
+ `
+ : `
No recent activity found.
- `}
+ `
+ }
`;
}
diff --git a/src/views/councils.ts b/src/views/councils.ts
index 33fabed..e001292 100644
--- a/src/views/councils.ts
+++ b/src/views/councils.ts
@@ -3,8 +3,14 @@
*/
import { renderNav } from "../components/nav.ts";
import { getCouncils } from "../lib/config.ts";
-import { getChannelSupply, getContractEvents, countProvidersFromEvents, queryErrors, clearQueryErrors } from "../lib/stellar.ts";
-import { escapeHtml, truncateAddress, formatAmount } from "../lib/dom.ts";
+import {
+ clearQueryErrors,
+ countProvidersFromEvents,
+ getChannelSupply,
+ getContractEvents,
+ queryErrors,
+} from "../lib/stellar.ts";
+import { escapeHtml, formatAmount, truncateAddress } from "../lib/dom.ts";
import { getCountryName } from "../lib/world-map.ts";
import { onCleanup } from "../lib/router.ts";
@@ -22,6 +28,7 @@ interface CouncilState {
loading: boolean;
}
+// deno-lint-ignore require-await -- view fn satisfies router's Promise
contract
export async function councilsView(): Promise {
const el = document.createElement("div");
el.appendChild(renderNav());
@@ -36,19 +43,24 @@ export async function councilsView(): Promise {
el.appendChild(main);
const ctx = { cancelled: false };
- onCleanup(() => { ctx.cancelled = true; });
+ onCleanup(() => {
+ ctx.cancelled = true;
+ });
loadCouncilData(main, ctx).catch(() => {});
return el;
}
-async function loadCouncilData(main: HTMLElement, ctx: { cancelled: boolean }): Promise {
+async function loadCouncilData(
+ main: HTMLElement,
+ ctx: { cancelled: boolean },
+): Promise {
clearQueryErrors();
const councils = await getCouncils();
if (ctx.cancelled) return;
- const states: CouncilState[] = councils.map(council => ({
+ const states: CouncilState[] = councils.map((council) => ({
name: council.name,
channelAuthId: council.channelAuthId,
jurisdictions: council.jurisdictions,
@@ -63,11 +75,13 @@ async function loadCouncilData(main: HTMLElement, ctx: { cancelled: boolean }):
const promises: Promise[] = [];
for (const state of states) {
- const council = councils.find(c => c.channelAuthId === state.channelAuthId)!;
+ const council = councils.find((c) =>
+ c.channelAuthId === state.channelAuthId
+ )!;
for (const ch of council.channels) {
promises.push(
- getChannelSupply(ch.privacyChannelId).then(supply => {
+ getChannelSupply(ch.privacyChannelId).then((supply) => {
state.channels.push({
privacyChannelId: ch.privacyChannelId,
assetCode: ch.assetCode,
@@ -78,7 +92,7 @@ async function loadCouncilData(main: HTMLElement, ctx: { cancelled: boolean }):
}
promises.push(
- getContractEvents(state.channelAuthId).then(events => {
+ getContractEvents(state.channelAuthId).then((events) => {
state.providerCount = countProvidersFromEvents(events).length;
}),
);
@@ -99,19 +113,28 @@ function renderCouncilTable(main: HTMLElement, states: CouncilState[]): void {
if (!content) return;
if (states.length === 0) {
- content.innerHTML = `No councils registered yet.
`;
+ content.innerHTML =
+ `No councils registered yet.
`;
return;
}
const totalChannels = states.reduce((sum, s) => sum + s.channels.length, 0);
const totalProviders = states.reduce((sum, s) => sum + s.providerCount, 0);
- const totalSupply = states.reduce((sum, s) =>
- sum + s.channels.reduce((cs, c) => cs + c.supply, 0n), 0n);
+ const totalSupply = states.reduce(
+ (sum, s) => sum + s.channels.reduce((cs, c) => cs + c.supply, 0n),
+ 0n,
+ );
const hasErrors = queryErrors.length > 0;
content.innerHTML = `
- ${hasErrors ? `Some data may be incomplete — network queries failed. (${queryErrors.length} error${queryErrors.length !== 1 ? "s" : ""})
` : ""}
+ ${
+ hasErrors
+ ? `Some data may be incomplete — network queries failed. (${queryErrors.length} error${
+ queryErrors.length !== 1 ? "s" : ""
+ })
`
+ : ""
+ }
@@ -144,30 +167,42 @@ function renderCouncilTable(main: HTMLElement, states: CouncilState[]): void {
- ${states.map(s => {
- const supply = s.channels.reduce((sum, c) => sum + c.supply, 0n);
- return `
-
+ ${
+ states.map((s) => {
+ const supply = s.channels.reduce((sum, c) => sum + c.supply, 0n);
+ return `
+
|
${escapeHtml(s.name)}
- ${truncateAddress(s.channelAuthId)}
+ ${
+ truncateAddress(s.channelAuthId)
+ }
|
- ${s.jurisdictions.map(j => escapeHtml(getCountryName(j))).join(", ")} |
+ ${
+ s.jurisdictions.map((j) => escapeHtml(getCountryName(j))).join(", ")
+ } |
${s.channels.length} |
- ${s.loading ? '...' : s.providerCount} |
- ${s.loading ? '...' : formatAmount(supply)} |
+ ${
+ s.loading ? '...' : s.providerCount
+ } |
+ ${
+ s.loading ? '...' : formatAmount(supply)
+ } |
Active |
`;
- }).join("")}
+ }).join("")
+ }
`;
- content.querySelectorAll(".clickable-row").forEach(row => {
+ content.querySelectorAll(".clickable-row").forEach((row) => {
row.addEventListener("click", () => {
const href = row.getAttribute("data-href");
- if (href) window.location.hash = href;
+ if (href) globalThis.location.hash = href;
});
});
}
diff --git a/src/views/map.ts b/src/views/map.ts
index 6f57b05..15f476e 100644
--- a/src/views/map.ts
+++ b/src/views/map.ts
@@ -3,8 +3,12 @@
* Uses a static SVG map (simple-world-map, CC BY-SA 3.0).
*/
import { renderNav } from "../components/nav.ts";
-import { getCouncils, type CouncilConfig } from "../lib/config.ts";
-import { fetchWorldSvg, projectCountry, getCountryName } from "../lib/world-map.ts";
+import { type CouncilConfig, getCouncils } from "../lib/config.ts";
+import {
+ fetchWorldSvg,
+ getCountryName,
+ projectCountry,
+} from "../lib/world-map.ts";
import { escapeHtml, truncateAddress } from "../lib/dom.ts";
import { onCleanup } from "../lib/router.ts";
@@ -26,7 +30,9 @@ export async function mapView(): Promise
{
el.appendChild(main);
const ctx = { cancelled: false };
- onCleanup(() => { ctx.cancelled = true; });
+ onCleanup(() => {
+ ctx.cancelled = true;
+ });
const councils = await getCouncils();
if (ctx.cancelled) return el;
@@ -35,16 +41,24 @@ export async function mapView(): Promise {
if (grid) {
grid.innerHTML = councils.length === 0
? `No councils registered yet.
`
- : councils.map(c => `
-
+ : councils.map((c) => `
+
- ${c.jurisdictions.map(j => escapeHtml(getCountryName(j))).join(", ")}
+ ${
+ c.jurisdictions.map((j) => escapeHtml(getCountryName(j))).join(", ")
+ }
- ${truncateAddress(c.channelAuthId)}
+ ${
+ truncateAddress(c.channelAuthId)
+ }
`).join("");
}
@@ -66,7 +80,7 @@ export async function mapView(): Promise {
// Extract all path elements
const pathElements = svgDoc.querySelectorAll("path");
const pathStrings: string[] = [];
- pathElements.forEach(p => {
+ pathElements.forEach((p) => {
const d = p.getAttribute("d");
if (d) pathStrings.push(d);
});
@@ -82,7 +96,7 @@ export async function mapView(): Promise {
- ${pathStrings.map(d => ``).join("\n ")}
+ ${pathStrings.map((d) => ``).join("\n ")}
@@ -93,7 +107,8 @@ export async function mapView(): Promise {
console.warn("[map] Failed to load world map:", err);
if (!ctx.cancelled) {
const mapContainer = main.querySelector(".map-container")!;
- mapContainer.innerHTML = `Failed to load map. Please try again later.
`;
+ mapContainer.innerHTML =
+ `Failed to load map. Please try again later.
`;
}
}
@@ -112,17 +127,27 @@ function buildCouncilMarkers(councils: CouncilConfig[]): string {
const r = Math.min(4 + channels * 2, 10);
markers.push(
- ``,
+ ``,
);
markers.push(
- ``,
+ ``,
);
markers.push(
- `` +
- `${escapeHtml(council.name)} — ${escapeHtml(getCountryName(code))}`,
+ `` +
+ `${escapeHtml(council.name)} — ${
+ escapeHtml(getCountryName(code))
+ }`,
);
markers.push(
- `${escapeHtml(council.name)}`,
+ `${escapeHtml(council.name)}`,
);
}
}
diff --git a/src/views/transactions.ts b/src/views/transactions.ts
index b412c8b..2d93ebb 100644
--- a/src/views/transactions.ts
+++ b/src/views/transactions.ts
@@ -3,8 +3,12 @@
*/
import { renderNav } from "../components/nav.ts";
import { getCouncils } from "../lib/config.ts";
-import { getContractEvents, queryErrors, clearQueryErrors } from "../lib/stellar.ts";
-import { escapeHtml, truncateAddress, timeAgo } from "../lib/dom.ts";
+import {
+ clearQueryErrors,
+ getContractEvents,
+ queryErrors,
+} from "../lib/stellar.ts";
+import { escapeHtml, timeAgo, truncateAddress } from "../lib/dom.ts";
import { onCleanup } from "../lib/router.ts";
import type { ContractEvent } from "../lib/stellar.ts";
@@ -15,6 +19,7 @@ interface FeedEntry {
channelId: string;
}
+// deno-lint-ignore require-await -- view fn satisfies router's Promise contract
export async function transactionsView(): Promise {
const el = document.createElement("div");
el.appendChild(renderNav());
@@ -29,14 +34,19 @@ export async function transactionsView(): Promise {
el.appendChild(main);
const ctx = { cancelled: false };
- onCleanup(() => { ctx.cancelled = true; });
+ onCleanup(() => {
+ ctx.cancelled = true;
+ });
loadTransactions(main, ctx).catch(() => {});
return el;
}
-async function loadTransactions(main: HTMLElement, ctx: { cancelled: boolean }): Promise {
+async function loadTransactions(
+ main: HTMLElement,
+ ctx: { cancelled: boolean },
+): Promise {
clearQueryErrors();
const councils = await getCouncils();
if (ctx.cancelled) return;
@@ -46,7 +56,7 @@ async function loadTransactions(main: HTMLElement, ctx: { cancelled: boolean }):
for (const council of councils) {
promises.push(
- getContractEvents(council.channelAuthId, undefined, 50).then(events => {
+ getContractEvents(council.channelAuthId, undefined, 50).then((events) => {
for (const event of events) {
feed.push({
event,
@@ -60,7 +70,7 @@ async function loadTransactions(main: HTMLElement, ctx: { cancelled: boolean }):
for (const ch of council.channels) {
promises.push(
- getContractEvents(ch.privacyChannelId, undefined, 50).then(events => {
+ getContractEvents(ch.privacyChannelId, undefined, 50).then((events) => {
for (const event of events) {
feed.push({
event,
@@ -85,19 +95,27 @@ async function loadTransactions(main: HTMLElement, ctx: { cancelled: boolean }):
function eventIcon(type: string): string {
switch (type) {
- case "ProviderAdded": return "+PP";
- case "ProviderRemoved": return "-PP";
- case "ContractInitialized": return "INIT";
- default: return "TX";
+ case "ProviderAdded":
+ return "+PP";
+ case "ProviderRemoved":
+ return "-PP";
+ case "ContractInitialized":
+ return "INIT";
+ default:
+ return "TX";
}
}
function eventBadgeClass(type: string): string {
switch (type) {
- case "ProviderAdded": return "badge-active";
- case "ProviderRemoved": return "badge-inactive";
- case "ContractInitialized": return "badge-pending";
- default: return "badge-active";
+ case "ProviderAdded":
+ return "badge-active";
+ case "ProviderRemoved":
+ return "badge-inactive";
+ case "ContractInitialized":
+ return "badge-pending";
+ default:
+ return "badge-active";
}
}
@@ -110,16 +128,26 @@ function renderFeed(main: HTMLElement, feed: FeedEntry[]): void {
content.innerHTML = `
No recent transactions found.
- ${hasErrors
- ? `
Network queries encountered errors. The RPC may be unreachable.
`
- : `
Transactions will appear here as channels process bundles.
`}
+ ${
+ hasErrors
+ ? `
Network queries encountered errors. The RPC may be unreachable.
`
+ : `
Transactions will appear here as channels process bundles.
`
+ }
`;
return;
}
- const txCount = feed.filter(f => !["ProviderAdded", "ProviderRemoved", "ContractInitialized"].includes(f.event.type)).length;
- const providerEvents = feed.filter(f => f.event.type === "ProviderAdded" || f.event.type === "ProviderRemoved").length;
+ const txCount =
+ feed.filter((f) =>
+ !["ProviderAdded", "ProviderRemoved", "ContractInitialized"].includes(
+ f.event.type,
+ )
+ ).length;
+ const providerEvents =
+ feed.filter((f) =>
+ f.event.type === "ProviderAdded" || f.event.type === "ProviderRemoved"
+ ).length;
content.innerHTML = `
@@ -138,20 +166,38 @@ function renderFeed(main: HTMLElement, feed: FeedEntry[]): void {
- ${feed.slice(0, 100).map(entry => `
+ ${
+ feed.slice(0, 100).map((entry) => `
- Council: ${escapeHtml(entry.councilName)}
- ${entry.channelAsset !== "\u2014" ? `Asset: ${escapeHtml(entry.channelAsset)}` : ""}
+ Council: ${
+ escapeHtml(entry.councilName)
+ }
+ ${
+ entry.channelAsset !== "\u2014"
+ ? `Asset: ${
+ escapeHtml(entry.channelAsset)
+ }`
+ : ""
+ }
-
${truncateAddress(entry.channelId)}
+
${
+ truncateAddress(entry.channelId)
+ }
- `).join("")}
+ `).join("")
+ }
`;
}