Files
buzz/docs/nips/NIP-CL.md
Logan Johnson 8bb49a1b6a feat(relay): publish channel type and creator on every channel-state event (#8174)
🤖
## Summary
Clients cannot tell who created a channel, and only the channel-metadata
event says what type of channel it is. This PR makes every relay-signed
channel-state event carry both facts, so a client can find "the channel
that this key created" with one ordinary Nostr filter.

- Channel metadata, admin list and member list events (kinds 39000,
39001 and 39002) now all carry the channel type as their first `t` tag,
then the creator as a `P` tag.
- The creator comes from the relay's own record of who signed the
channel's create request. No client can set it, and it does not change
when ownership moves.
- Channel metadata (kind:39000) also carries `["created_at", <unix
seconds>]`, the time the channel was created. The event's own
`created_at` changes on every edit, so before this a client could not
tell which of two channels is older.

This is part 1 of a 3-part stack (channel labels and creator lookup).
The system channels stack (#8180–#8182) builds on it.

### Related issue
None found.

### Details
- **Why `P`.** Several Nostr specs (NIP-22, NIP-34, NIP-72) use
uppercase `P` for the author of the root object, and lowercase `p` for
people involved. Lowercase `p` is already used on these events for
members and DM participants, and a filter matches tag values, not their
meaning, so `#p` would mix them.
- **Trust.** A client that trusts a channel because of its creator must
also check that the relay key signed the event.
- **Order.** The type `t` tag comes first, so clients that read the
first `t` tag still get the type after part 2 adds labels as more `t`
tags.
- **Creation time.** The relay reads it from `channels.created_at`, so
an edit does not change it. #8176 uses it to pick one channel when two
creates race.
- `buzz-admin reconcile` writes the same tags when it rebuilds these
events. `NOSTR.md` documents the tags.
- **Spec.** New `docs/nips/NIP-CL.md` defines these tags, the trust rule
and lookup by creator. Later PRs in both stacks add their parts to it.
`NOSTR.md` links to it.

### Testing
- Relay tests in `group_state_postgres_tests.rs`: each of the three
kinds carries the type first, then the creator; the creator stays the
same after an ownership transfer; kind:39000 carries the stored creation
time, and still does after an edit.
- Unit test for the shared tag builder that both the relay and
`buzz-admin reconcile` use.

### Stack
These are two stacks. Merge them in this order. Client support
(creating, labeling and finding these channels) will be in
block/buzz-app, not the buzz SDK and CLI.

**Stack 1: channel labels and creator lookup (#8174–#8176)**

1. #8174 channel type and creator tags, and the channel creation time ←
this PR
2. #8175 channel labels
3. #8176 database-side label and creator match, and the rule for
choosing one channel

**Stack 2: system channels (#8180–#8182)**, stacked on stack 1 because
it uses labels and the creator tag

4. #8180 the system channel type
5. #8181 leave system channels out of reads that do not name them
6. #8182 channel-state filters opt in to system channels

---------

Signed-off-by: Logan Johnson <loganj@squareup.com>
Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
2026-10-14 09:22:42 -04:00

3.5 KiB
Raw Permalink Blame History

NIP-CL

Channel Identity and Discovery

draft optional relay

This NIP adds identity facts to the relay-signed NIP-29 channel-state events (kinds 39000–39002). With these facts, a client can find channels with ordinary NIP-01 filters. It needs no new filter key and no relay-specific API.

Motivation

Plugins and agents often create a channel for their own use and must find it again later, for example "the config channel that this agent created". With plain NIP-29, a client cannot:

  • tell who created a channel;
  • read the channel type from any event except kind:39000; or
  • tell which of two channels is older. The created_at of a kind:39000 event is the time of the last edit, not the time of creation.

Channel identity tags

On every channel-state event that the relay signs, the relay MUST put these tags in this order, before any other t or P tag:

  1. ["t", <channel type>], for example stream, forum, dm, workflow.
  2. ["P", <creator public key, hex>].

kind:39000 also carries ["created_at", <unix seconds>].

Example kind:39000 (signing fields and unrelated tags omitted):

{
  "kind": 39000,
  "pubkey": "<relay public key>",
  "created_at": 1760025600,
  "tags": [
    ["d", "9b353519-f4fe-4757-aef4-bec6cc0ae54c"],
    ["name", "agent-config"],
    ["t", "stream"],
    ["P", "<creator public key>"],
    ["created_at", "1759939200"]
  ]
}
Tag Kinds Meaning
first t 39000–39002 Channel type.
P 39000–39002 Public key that signed the kind:9007 that created the channel.
created_at 39000 Time the channel was created, in unix seconds, as a decimal string.

Channel type. Clients MUST read the channel type from the first t tag. A relay can add other t tags after it.

Creator. The relay writes P from its own record of the kind:9007 that created the channel. No client can set it. It does not change when ownership moves to another member or when the channel is edited.

Uppercase P follows NIP-22, NIP-34 and NIP-72, where P names the author of the root object. On these kinds, lowercase p already lists members, admins and DM participants. A filter matches tag values, not their roles, so #p would mix creators with members.

Creation time. The created_at tag does not change when the channel is edited. Use it, not the event's created_at, to compare the age of channels.

Older events. A relay adds these tags only when it publishes a channel-state event. An event that a relay version without this NIP published can lack them, including one published during an upgrade. Such an event stays until the channel next changes, for example on an edit or a membership change. A client MUST treat a missing tag as unknown. A missing P does not mean that a key did not create the channel.

Finding channels

Use ordinary NIP-01 filters on the channel-state kinds.

To find Filter
Channels that a key created {"kinds": [39000], "#P": ["<creator>"]}
Channels of one type {"kinds": [39000], "#t": ["<type>"]}

A #P filter does not return a channel whose events lack P (see Older events). So it can miss channels that a key created before the relay supported this NIP.

Trust

A client that relies on these tags MUST check that the relay's key signed the event, as for all NIP-29 group-state events.

Relation to other NIPs

  • NIP-29: the group-state events that this NIP adds tags to.
  • NIP-01: the filters that find channels.
  • NIP-22, NIP-34 and NIP-72: the convention for uppercase P.