From 67a4f4a275b69e2357108d30ff0c0cccc45d29a6 Mon Sep 17 00:00:00 2001
From: jayteemoney
Date: Fri, 9 Oct 2026 16:16:20 +0100
Subject: [PATCH 1/2] feat(frontend): show the total streams on mainnet
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The app never displayed the protocol-wide stream count: the dashboard shows
the connected wallet's streams and /organisations showed only streams linked
to an organisation, so the 11 streams opened on mainnet were visible only in
the raw /api/stats JSON.
- /organisations: the readout now shows "Streams on mainnet" (get-stream-nonce,
opened by anyone) beside "From organisations" (linked to a listed team),
and /api/organisations returns streamsCreated.
- Home page: a live line under the hero buttons, "N streams on mainnet ·
N organisations", linking to /organisations. It renders nothing until the
numbers arrive, so no placeholder figure is ever shown.
---
frontend/src/app/organisations/page.tsx | 17 +++---
frontend/src/components/landing/hero.tsx | 11 ++++
.../src/components/landing/live-stats.tsx | 54 +++++++++++++++++++
frontend/src/lib/organisations.ts | 14 ++++-
4 files changed, 89 insertions(+), 7 deletions(-)
create mode 100644 frontend/src/components/landing/live-stats.tsx
diff --git a/frontend/src/app/organisations/page.tsx b/frontend/src/app/organisations/page.tsx
index f92ee3f..a591f1c 100644
--- a/frontend/src/app/organisations/page.tsx
+++ b/frontend/src/app/organisations/page.tsx
@@ -166,18 +166,23 @@ function Hero({ directory }: { directory: OrganisationDirectory | null }) {
diff --git a/frontend/src/components/landing/hero.tsx b/frontend/src/components/landing/hero.tsx
index 83d60f9..068af86 100644
--- a/frontend/src/components/landing/hero.tsx
+++ b/frontend/src/components/landing/hero.tsx
@@ -4,6 +4,7 @@ import Link from "next/link";
import { motion } from "framer-motion";
import { Button } from "@/components/ui/button";
import { LiveStreamCard } from "@/components/landing/live-stream-card";
+import { LiveStats } from "@/components/landing/live-stats";
import { ArrowRight, Zap } from "lucide-react";
export function Hero() {
@@ -81,6 +82,16 @@ export function Hero() {
+ {/* Live usage, read from the contract */}
+
+
+
+
{/* Animated streaming preview */}
{
+ const res = await fetch(`${OPENCLAW_API_URL}/api/stats`);
+ if (!res.ok) throw new Error(`stats ${res.status}`);
+ return (await res.json()) as Stats;
+}
+
+/**
+ * The live usage line under the hero. Read from the contract through
+ * /api/stats; it renders nothing until the numbers arrive, so a visitor never
+ * sees a placeholder or a guessed figure.
+ */
+export function LiveStats() {
+ const { data } = useQuery({
+ queryKey: ["live-stats"],
+ queryFn: fetchStats,
+ refetchInterval: 60_000,
+ staleTime: 30_000,
+ });
+ if (!data) return null;
+
+ const orgs = data.workspacesRegistered;
+ return (
+
+
+
+
+
+
+ {data.streamsCreated} streams on
+ mainnet
+
+ ·
+
+ {orgs}{" "}
+ {orgs === 1 ? "organisation" : "organisations"}
+
+ · read live from the contract
+
+ );
+}
diff --git a/frontend/src/lib/organisations.ts b/frontend/src/lib/organisations.ts
index 1a3af10..2984ad4 100644
--- a/frontend/src/lib/organisations.ts
+++ b/frontend/src/lib/organisations.ts
@@ -15,6 +15,7 @@ import {
getDao,
getDaoCount,
getNetwork,
+ getStreamNonce,
HIRO_API_BASE,
STREAM_FACTORY_CONTRACT,
} from "./openclaw-server";
@@ -36,6 +37,12 @@ export interface OrganisationDirectory {
network: "mainnet" | "testnet";
/** `get-dao-count`, the authoritative total. */
total: number;
+ /**
+ * Every stream ever opened on mainnet (`get-stream-nonce`), by anyone, whether
+ * or not an organisation sent it. Distinct from the streams organisations
+ * have linked, which is the sum of `streamsTracked`.
+ */
+ streamsCreated: number;
/** Newest first. */
organisations: Organisation[];
/**
@@ -148,7 +155,11 @@ let cached: { directory: OrganisationDirectory; expires: number } | null = null;
export async function getOrganisationDirectory(): Promise {
if (cached && Date.now() < cached.expires) return cached.directory;
- const [rows, total] = await Promise.all([fetchFactoryTransactions(), getDaoCount()]);
+ const [rows, total, streamsCreated] = await Promise.all([
+ fetchFactoryTransactions(),
+ getDaoCount(),
+ getStreamNonce(),
+ ]);
const registrations = parseRegistrations(rows, STREAM_FACTORY_CONTRACT);
// The contract is the source of truth for each record. A registration whose
@@ -172,6 +183,7 @@ export async function getOrganisationDirectory(): Promise
const directory: OrganisationDirectory = {
network: getNetwork(),
total,
+ streamsCreated,
organisations,
unlisted: Math.max(0, total - organisations.length),
asOf: new Date().toISOString(),
From bb98ff258c689dee4f2e8d55b08159ee0b5609f4 Mon Sep 17 00:00:00 2001
From: jayteemoney
Date: Fri, 9 Oct 2026 16:32:21 +0100
Subject: [PATCH 2/2] feat(frontend): stream total as prominent as the
organisation count
- /organisations: "Organisations" and "Streams" now sit side by side at the
same size, with "From organisations" and "Newest" below.
- Dashboard overview: a "StackStream on mainnet" panel at the top shows
streams created and organisations registered, linking to /organisations.
It also shows before a wallet is connected, and the wallet's own figures
now sit under a "Your streams" heading.
- Both read one shared hook (useNetworkStats) over /api/stats, which the
home page line now uses too. Every new stream or registration, by anyone,
raises the numbers within about a minute.
---
frontend/src/app/dashboard/page.tsx | 19 ++++---
frontend/src/app/organisations/page.tsx | 33 +++++++-----
.../src/components/landing/live-stats.tsx | 21 +-------
.../src/components/layout/network-stats.tsx | 54 +++++++++++++++++++
frontend/src/hooks/use-network-stats.ts | 33 ++++++++++++
5 files changed, 122 insertions(+), 38 deletions(-)
create mode 100644 frontend/src/components/layout/network-stats.tsx
create mode 100644 frontend/src/hooks/use-network-stats.ts
diff --git a/frontend/src/app/dashboard/page.tsx b/frontend/src/app/dashboard/page.tsx
index dc7c2bb..8120333 100644
--- a/frontend/src/app/dashboard/page.tsx
+++ b/frontend/src/app/dashboard/page.tsx
@@ -20,6 +20,7 @@ import { useTokenMetadata } from "@/hooks/use-token-metadata";
import type { StreamData } from "@/lib/stacks";
import { STREAM_STATUS, unresolvableTokenLabel } from "@/lib/constants";
import { TopUpDialog } from "@/components/stream/top-up-dialog";
+import { NetworkStats } from "@/components/layout/network-stats";
import { Skeleton } from "@/components/ui/skeleton";
import Link from "next/link";
import { PlusCircle, Zap, Users, Coins, TrendingUp } from "lucide-react";
@@ -47,17 +48,23 @@ export default function DashboardPage() {
if (!isConnected) {
return (
- }
- title="Connect your wallet"
- description="Connect a Stacks wallet to open streams and manage real-time payments, settled on Bitcoin."
- />
+
+
+ }
+ title="Connect your wallet"
+ description="Connect a Stacks wallet to open streams and manage real-time payments, settled on Bitcoin."
+ />
+
diff --git a/frontend/src/components/landing/live-stats.tsx b/frontend/src/components/landing/live-stats.tsx
index 2247640..95e3862 100644
--- a/frontend/src/components/landing/live-stats.tsx
+++ b/frontend/src/components/landing/live-stats.tsx
@@ -1,19 +1,7 @@
"use client";
import Link from "next/link";
-import { useQuery } from "@tanstack/react-query";
-import { OPENCLAW_API_URL } from "@/lib/constants";
-
-interface Stats {
- streamsCreated: number;
- workspacesRegistered: number;
-}
-
-async function fetchStats(): Promise {
- const res = await fetch(`${OPENCLAW_API_URL}/api/stats`);
- if (!res.ok) throw new Error(`stats ${res.status}`);
- return (await res.json()) as Stats;
-}
+import { useNetworkStats } from "@/hooks/use-network-stats";
/**
* The live usage line under the hero. Read from the contract through
@@ -21,12 +9,7 @@ async function fetchStats(): Promise {
* sees a placeholder or a guessed figure.
*/
export function LiveStats() {
- const { data } = useQuery({
- queryKey: ["live-stats"],
- queryFn: fetchStats,
- refetchInterval: 60_000,
- staleTime: 30_000,
- });
+ const { stats: data } = useNetworkStats();
if (!data) return null;
const orgs = data.workspacesRegistered;
diff --git a/frontend/src/components/layout/network-stats.tsx b/frontend/src/components/layout/network-stats.tsx
new file mode 100644
index 0000000..c02f395
--- /dev/null
+++ b/frontend/src/components/layout/network-stats.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import Link from "next/link";
+import { ArrowRight } from "lucide-react";
+import { Skeleton } from "@/components/ui/skeleton";
+import { useNetworkStats } from "@/hooks/use-network-stats";
+
+/**
+ * StackStream's live usage on mainnet, shown at the top of the dashboard
+ * whether or not a wallet is connected. Both figures come from the contract.
+ */
+export function NetworkStats() {
+ const { stats, isLoading } = useNetworkStats();
+
+ if (isLoading) return ;
+ if (!stats) return null;
+
+ return (
+