Nostr Surface Gap — Codebase vs Live Relay
Nostr Surface Gap — Codebase vs Live Relay
The Disconnect
Codebase (kapnet-nostr crate):
- Uses nostr-sdk (Rust)
- Publishes custom kinds: raw_txxm (30000-30099), knot_commitment (30300-30399), governance (30400-30499), braid_sync (30500-30599)
- Subscribes to:
Kind::Custom(30000),Kind::Custom(30078),Kind::Custom(30100),Kind::Custom(30300),Kind::Custom(30500),Kind::Custom(30501) - Assumes: Kapnet-aware relays accept custom kinds
Live (what we’re doing now):
- Uses nostr-tools (Node.js)
- Publishes: kind-1 (text), kind-30078 (custom data)
- Subscribes to: #kapnet hashtag, mentions of our npub
- Reality: Public relays REJECT custom kinds (30000+)
Conflict
If we start the kapnetd daemon with its built-in Nostr adapter:
- It will subscribe to Kind::Custom(30000) etc.
- Public relays will NOT send those events
- The daemon’s Nostr sync will appear “dead” — no events received
- Meanwhile, our Node.js listener is receiving kind-1 and kind-30078 just fine
Resolution Options
Option A: Bimap Adapter (Recommended)
Build a thin adapter that:
- Takes internal Kapnet TXXM events (custom kinds 30000+)
- Wraps them as kind-30078 with structured content + #kapnet #[type] tags
- Publishes to public relay
- On receive: unwraps kind-30078 content back to TXXM types
Kapnet Internal Public Nostr Bridge Public Relay
Kind::Custom(30001) → kind-30078 content JSON → stored as-is
raw_txxm { type: "raw_txxm", ... }
+ t: ["kapnet", "raw_txxm"]
Kind::Custom(30301) → kind-30078 content JSON → stored as-is
knot_commitment { type: "knot_commitment", ... }
+ t: ["kapnet", "knot_commitment"]
kind-30078 receive ← unwrap content JSON ← received event
→ dispatch to KapnetTxxm::RawTxxm / KnotCommitment etc.
Option B: Patch kapnet-nostr to use public kinds
Change the subscribe filter to include kind-1 + kind-30078 with #kapnet tag.
Option C: Run a local Kapnet-aware relay
Run strfry with custom kind acceptance. Then kapnetd uses it directly.
Recommended: Option A
Build a lightweight bimap in Node.js:
// kapnet-nostr-bridge.mjs
const KAPNET_KIND_MAP = {
// raw_txxm: 30000-30099 → kind-30078 + tag "raw_txxm"
raw_txxm: { kind: 30078, tag: "raw_txxm" },
// knot_commitment: 30300-30399 → kind-30078 + tag "knot_commitment"
knot_commitment: { kind: 30078, tag: "knot_commitment" },
// governance: 30400-30499 → kind-30078 + tag "governance"
governance: { kind: 30078, tag: "governance" },
// braid_sync: 30500-30599 → kind-30078 + tag "braid_sync"
braid_sync: { kind: 30078, tag: "braid_sync" },
};
// Publish: wrap Kapnet TXXM → public Nostr
function publishTxxm(txxmEvent) {
const mapping = KAPNET_KIND_MAP[txxmEvent.type];
const content = JSON.stringify(txxmEvent);
const tags = [
["t", "kapnet"],
["t", mapping.tag],
["k", String(txxmEvent.kind)], // preserve original kind
["d", txxmEvent.id],
];
// Sign and publish to relay
}
// Subscribe: unwrap public Nostr → Kapnet Txxm
function onEvent(event) {
if (event.kind === 30078) {
try {
const txxm = JSON.parse(event.content);
const originalKind = event.tags.find(t => t[0] === "k")?.[1];
// Dispatch to appropriate Kapnet handler
dispatchTxxm(txxm);
} catch(e) { /* not our event */ }
}
}
This lets:
- kapnetd publish TXXMs via our bridge (Node.js nostr-tools)
- Other Kapnet nodes receive via our bridge
- Public relay stores everything as kind-30078
- Full Kapnet semantics preserved in content + tags
Action Items
- Build kapnet-nostr-bridge.mjs (Node.js bridge)
- Integrate with kapnetd startup
- Test round-trip: publish → relay → receive → unwrap
Write a comment