Files
NostrCounter/counter.md
2025-07-19 09:31:12 +02:00

3.0 KiB

Purpose

This guide explains how to create and publish "Days Since / Until" counter events on Nostr using NDK (Nostr Development Kit).

Counters are stored as kind: 30078 events with metadata tags for rendering.


Prerequisites

  • NDK installed
  • Signer available (via NIP-07, NIP-46, or npub/nsec input)
  • Relay pool configured and connected

Example Setup

import NDK, { NDKEvent, NDKPrivateKeySigner } from "@nostr-dev-kit/ndk";

const ndk = new NDK({
  explicitRelayUrls: [
    "wss://relay.azzamo.net",
    "wss://relay.damus.io"
  ]
});
await ndk.connect();

// Login with nsec
const signer = new NDKPrivateKeySigner("nsec1...");
ndk.signer = signer;
await ndk.signer.user();

You can also support login via npub (read-only) or NIP-07 browser extension.


Create Counter Function

async function publishCounter({
  title,
  date,
  type = "since", // or "until"
  visibility = "public", // or "private"
}: {
  title: string;
  date: string; // format YYYY-MM-DD
  type?: "since" | "until";
  visibility?: "public" | "private";
}) {
  const event = new NDKEvent(ndk);
  event.kind = 30078;
  event.tags = [
    ["type", type],
    ["title", title],
    ["date", date],
    ["visibility", visibility],
  ];
  event.content = "";

  await event.sign();
  await event.publish();
  return event;
}

Public Counter Example

const ev = await publishCounter({
  title: "Quit smoking",
  date: "2024-12-01",
  type: "since",
  visibility: "public",
});
console.log("View at /counter/" + ev.id);

Published JSON Output:

{
  "kind": 30078,
  "content": "",
  "tags": [
    ["type", "since"],
    ["title", "Quit smoking"],
    ["date", "2024-12-01"],
    ["visibility", "public"]
  ],
  "created_at": 1725000000,
  "id": "note1...",
  "pubkey": "npub1..."
}

Use the full event ID as the URL slug:

/counter/note1xyz...  ← based on event.id (NIP-19 encoded)

Private Counter Example

await publishCounter({
  title: "Last relapse",
  date: "2025-07-01",
  type: "since",
  visibility: "private",
});

Note: Private counters are still published to relays but can be filtered out in the app logic.


Reading Events

To fetch all public counters:

const events = await ndk.fetchEvents({
  kinds: [30078]
});

const publicEvents = Array.from(events).filter(e =>
  e.tags.find(([k, v]) => k === "visibility" && v === "public")
);

To fetch your own counters:

const user = await ndk.signer?.user();
const events = await ndk.fetchEvents({
  kinds: [30078],
  authors: [user?.pubkey || ""]
});

Notes

  • The URL slug for counters is the event ID (NIP-19 encoded if needed)
  • Event id is used to lookup and display the counter
  • Lightning zaps should be handled via metadata (fetch kind:0 or NIP-05 info)
  • Updating a counter means publishing a new event with new content

Let me know if you want to include zaps, NIP-75 fundraising goals, or update/delete flows.