From 23634d653d9bc5599cbc76474c5fdc6638c71bcc Mon Sep 17 00:00:00 2001 From: jayteemoney Date: Wed, 8 Jul 2026 16:04:33 +0100 Subject: [PATCH] feat: north-star metric endpoint and CTA rules Implements the remaining marketing feedback items: - openclaw-service: new GET /api/stats returning streamsCreated (the contract's get-stream-nonce), workspacesRegistered, and block height, with a 60s in-memory cache. streamsCreated is the north-star metric; verified live against mainnet (9 streams at block 8,508,895). - MARKETING_PLAN_V2: north-star section (definition, baseline, 30-day target, operating rule) and a CTA rule with the canonical handle table; every public post ends with exactly one destination. - CONTENT_SCHEDULE: north-star and CTA rules added to the working rules block, plus a July-schedule section gated on the engagement review and the team's segment-focus decision. - The 60-second demo video script stays local only (gitignored with the other operational docs). --- .gitignore | 1 + grant-application/CONTENT_SCHEDULE.md | 14 +++++++ grant-application/MARKETING_PLAN_V2.md | 47 +++++++++++++++++++++++ openclaw-service/src/index.ts | 2 + openclaw-service/src/routes/stats.ts | 53 ++++++++++++++++++++++++++ 5 files changed, 117 insertions(+) create mode 100644 openclaw-service/src/routes/stats.ts diff --git a/.gitignore b/.gitignore index 9cad2bc..d495100 100644 --- a/.gitignore +++ b/.gitignore @@ -21,3 +21,4 @@ grant-application/MAINNET_READINESS_AUDIT.md grant-application/DOMAIN_SETUP.md grant-application/TESTER_GUIDE.md grant-application/TEAM_UPDATE_EMAIL.md +grant-application/DEMO_VIDEO_SCRIPT.md diff --git a/grant-application/CONTENT_SCHEDULE.md b/grant-application/CONTENT_SCHEDULE.md index 86a5ca0..d5addd0 100644 --- a/grant-application/CONTENT_SCHEDULE.md +++ b/grant-application/CONTENT_SCHEDULE.md @@ -20,6 +20,10 @@ **Settlement language rule:** never put the every-few-seconds rhythm and Bitcoin settlement in the same clause. The rhythm is how often the stream updates. Bitcoin finality comes when settlement anchors to Bitcoin, on Bitcoin's own schedule. Say "streams update in seconds; settlement inherits Bitcoin finality" for technical readers, or "your balance updates every few seconds, secured by Bitcoin" in social copy. Never say "settling on Bitcoin every few seconds" or "once it lands, it is final." +**North star:** every post is judged by one question, does it give someone a reason to open a stream? The number we track is total streams created (baseline 9 on Jul 8, 2026; target 40 by Aug 7). Pull it live from `GET /api/stats` (`streamsCreated`) before every metrics post. Full definition in `MARKETING_PLAN_V2.md`. + +**CTA rule:** every public post ends with exactly one destination. Default is **stackstream.xyz**. Rotate in the 60 second demo clip or Telegram (**t.me/dev_jaytee**) when they fit the ask better. Official X is @Stackstream0X, personal X is @dev_jayteee, LinkedIn is the founder profile. Forum and Grantees Telegram stay soft ("link in the thread", "DM me"), never salesy. Full handle table in `MARKETING_PLAN_V2.md`. + **Selling points to keep warm (weave in often, never let them go cold):** 1. Settled on Bitcoin. Once a payment is final, it cannot be reversed. 2. Real-time, money moves every few seconds. @@ -401,6 +405,16 @@ Forum: 5 substantial threads only. Grantees group: 6 genuine touches. Both kept --- +## July schedule (next) + +The June run above shipped without CTAs on most X posts; that is fixed by the CTA rule at the top, which applies to everything from here on. The July schedule gets drafted after two inputs land: +1. The 30 minute engagement review of June's posts, so July is built around the top 2 or 3 formats that actually performed. +2. The team's decision on segment focus (whether to concentrate outreach on Stacks-native DAOs first). + +Two assets July will lean on either way: the 60 second demo video (shot-by-shot script kept in the local ops docs) and the weekly Monday metrics post powered by the live `streamsCreated` number. + +--- + ## Notes before posting - Brackets ([X], [Y], [Z], the spotlight team) need real values. If a DAO or demo streams are not ready by Jun 24, 27, or 29, tell me and I will rework those days so nothing rings hollow. diff --git a/grant-application/MARKETING_PLAN_V2.md b/grant-application/MARKETING_PLAN_V2.md index 3bc3acf..aa618e3 100644 --- a/grant-application/MARKETING_PLAN_V2.md +++ b/grant-application/MARKETING_PLAN_V2.md @@ -16,6 +16,53 @@ This version realigns our marketing to a sharper differentiator. The pivot chang 1. After the Nakamoto upgrade, Stacks blocks arrive about every 5 seconds, so streams update in near real time. Settlement is anchored to Bitcoin, and once anchored it inherits Bitcoin finality. 2. Through sBTC, you can stream actual Bitcoin, continuously. +--- + +## North star metric + +**The one number: total streams created.** Everything we post is judged by one question: does this give someone a reason to open a stream? + +| | | +|---|---| +| **Metric** | Total streams created on mainnet (the contract's own counter, `get-stream-nonce`) | +| **Baseline** | 9 streams, as of Jul 8, 2026 (mainnet block 8,508,895) | +| **30-day target** | 40 streams by Aug 7, 2026 (about one new stream a day — team to confirm or adjust) | +| **Where to read it** | `GET /api/stats` on the OpenClaw service returns `streamsCreated` live from the chain. Anyone can verify it against the contract, which is the point. | +| **Secondary (watch, don't chase)** | `workspacesRegistered` from the same endpoint, and unique sender addresses. | + +**Why this metric and not another:** +- It is **on-chain and verifiable**. We never have to trust our own spreadsheet, and neither does anyone reading our metrics posts. +- It is **cumulative**, so it never punishes us when a stream completes naturally (unlike "active streams"). +- It **doubles as grant evidence** for M2/M3 proof-of-usage without extra work. +- "Teams onboarded" is the outcome we want, but it lags and cannot be verified publicly; streams created is its leading indicator. + +**Operating rule:** before drafting any post, name how it could move the number (drives a visit, a demo view, or a direct conversation). If it cannot, it needs a different angle or a different week. The Monday metrics post reports the number weekly, honestly, even when it is flat. + +## Call to action rule + +**Every public post ends with exactly one CTA.** No post ships with nowhere to go. + +| Priority | CTA | Use | +|---|---|---| +| Primary | **stackstream.xyz** | Default on every post: "Try it: stackstream.xyz" | +| Secondary | The 60-second demo clip | When the post's job is belief, not action yet | +| Tertiary | Telegram **t.me/dev_jaytee** | When the ask is "talk to us" (DAO outreach, feedback) | + +**Our handles (use these, never improvise):** + +| Channel | Handle / link | +|---|---| +| Website | https://stackstream.xyz | +| Official X | @Stackstream0X | +| Personal X (founder) | @dev_jayteee | +| Telegram | t.me/dev_jaytee | +| Discord | dev_jaytee (DM handle — no public server yet; stand one up before using Discord as a post CTA) | +| LinkedIn (founder) | https://www.linkedin.com/in/jethro-irmiya-a2153427b/ | + +**Exceptions:** Stacks Forum and the Grantees Telegram stay soft — those channels are for substance and trust, so the CTA there is at most "link in the thread" or "DM me". Selling hard there costs more than it earns. + +--- + **Settlement language rule (applies to every channel):** never fuse the update cadence and Bitcoin settlement into one clause. The ~5 second rhythm is how often *streams update*; *Bitcoin finality* is inherited when Stacks state anchors to Bitcoin, on Bitcoin's own cadence. Approved shapes: "streams update in seconds; settlement inherits Bitcoin finality" (technical audiences), "your balance updates every few seconds, secured by Bitcoin" (social). Never write "settling on Bitcoin every few seconds/every block" or "once it lands, it is final" — crypto Twitter will (correctly) nitpick both. --- diff --git a/openclaw-service/src/index.ts b/openclaw-service/src/index.ts index dc38e85..0ee3c36 100644 --- a/openclaw-service/src/index.ts +++ b/openclaw-service/src/index.ts @@ -10,6 +10,7 @@ import daosRouter from "./routes/daos"; import blocksRouter from "./routes/blocks"; import tokensRouter from "./routes/tokens"; import transactionsRouter from "./routes/transactions"; +import statsRouter from "./routes/stats"; const app = express(); @@ -49,6 +50,7 @@ app.use("/api/daos", daosRouter); app.use("/api/blocks", blocksRouter); app.use("/api/tokens", tokensRouter); app.use("/api/tx", transactionsRouter); +app.use("/api/stats", statsRouter); // Error handler app.use(errorHandler); diff --git a/openclaw-service/src/routes/stats.ts b/openclaw-service/src/routes/stats.ts new file mode 100644 index 0000000..a81de77 --- /dev/null +++ b/openclaw-service/src/routes/stats.ts @@ -0,0 +1,53 @@ +import { Router } from "express"; +import { + getStreamNonce, + getDaoCount, + getCurrentBlockHeight, + getNetwork, +} from "../stacks-client"; + +const router = Router(); + +interface StatsSnapshot { + network: string; + blockHeight: number; + streamsCreated: number; + workspacesRegistered: number; + asOf: string; +} + +// The north-star number changes at most once per new stream, so a short cache +// keeps repeated dashboard/marketing polls from burning Hiro quota. +const CACHE_TTL_MS = 60_000; +let cached: { snapshot: StatsSnapshot; expires: number } | null = null; + +// GET /api/stats — protocol usage counters, all verifiable on-chain. +// streamsCreated mirrors stream-manager's get-stream-nonce (total streams ever +// opened), the project's north-star growth metric. +router.get("/", async (_req, res, next) => { + try { + if (cached && Date.now() < cached.expires) { + res.json(cached.snapshot); + return; + } + const [streamsCreated, workspacesRegistered, blockHeight] = + await Promise.all([ + getStreamNonce(), + getDaoCount(), + getCurrentBlockHeight(), + ]); + const snapshot: StatsSnapshot = { + network: getNetwork(), + blockHeight, + streamsCreated, + workspacesRegistered, + asOf: new Date().toISOString(), + }; + cached = { snapshot, expires: Date.now() + CACHE_TTL_MS }; + res.json(snapshot); + } catch (err) { + next(err); + } +}); + +export default router;