NAME
Net::Nostr::Group - NIP-29 relay-based groups
SYNOPSIS
use Net::Nostr::Group;
use Net::Nostr::Key;
my $key = Net::Nostr::Key->new;
# Format and parse a group identifier (naddr for kind 39000 metadata)
my $group_naddr = Net::Nostr::Group->format_id(
pubkey => $relay_pubkey,
group_id => 'pizza',
relay => 'wss://groups.nostr.com',
);
my $parsed = Net::Nostr::Group->parse_id($group_naddr);
# { group_id => 'pizza', pubkey => $relay_pubkey, kind => 39000, ... }
# Validate a group_id
Net::Nostr::Group->validate_group_id('my-group_1'); # 1
Net::Nostr::Group->validate_group_id('Pizza Fans'); # 1
Net::Nostr::Group->validate_group_id(''); # 0
# Join a group (kind 9021)
my $join = Net::Nostr::Group->join_request(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
reason => 'I love pizza',
code => 'invite-abc', # optional invite code
);
$key->sign_event($join);
$client->publish($join);
# Leave a group (kind 9022)
my $leave = Net::Nostr::Group->leave_request(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
);
# Add a user to a group (kind 9000, admin)
my $put = Net::Nostr::Group->put_user(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
target => $user_pubkey,
roles => ['moderator'],
reason => 'promoted',
);
# Remove a user (kind 9001, admin)
my $rm = Net::Nostr::Group->remove_user(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
target => $user_pubkey,
reason => 'spamming',
);
# Edit group metadata (kind 9002, admin)
my $edit = Net::Nostr::Group->edit_metadata(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
name => 'Pizza Lovers',
about => 'We love pizza',
private => 1,
closed => 1,
);
# Delete an event from the group (kind 9005, admin)
my $del = Net::Nostr::Group->delete_event(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
event_id => $spam_event_id,
);
# Create a group (kind 9007)
my $create = Net::Nostr::Group->create_group(
pubkey => $key->pubkey_hex,
group_id => 'new-group',
);
# Delete a group (kind 9008)
my $delete = Net::Nostr::Group->delete_group(
pubkey => $key->pubkey_hex,
group_id => 'old-group',
);
# Create an invite code (kind 9009)
my $invite = Net::Nostr::Group->create_invite(
pubkey => $key->pubkey_hex,
group_id => 'pizza',
code => 'secret-code-123',
);
# Generate group metadata (kind 39000, relay-generated)
my $meta = Net::Nostr::Group->metadata(
pubkey => $relay_pubkey,
group_id => 'pizza',
name => 'Pizza Lovers',
picture => 'https://pizza.com/pizza.png',
about => 'a group for pizza fans',
private => 1,
closed => 1,
livekit => 1,
supported_kinds => [9, 11],
);
# Generate admin list (kind 39001, relay-generated)
my $admin_event = Net::Nostr::Group->admins(
pubkey => $relay_pubkey,
group_id => 'pizza',
members => [
{ pubkey => $admin_pk, roles => ['admin'] },
{ pubkey => $mod_pk, roles => ['moderator'] },
],
);
# Generate member list (kind 39002, relay-generated)
my $member_event = Net::Nostr::Group->members(
pubkey => $relay_pubkey,
group_id => 'pizza',
members => [$pk1, $pk2, $pk3],
);
# Generate roles list (kind 39003, relay-generated)
my $role_event = Net::Nostr::Group->roles(
pubkey => $relay_pubkey,
group_id => 'pizza',
roles => [
{ name => 'admin', description => 'full control' },
{ name => 'moderator', description => 'can delete messages' },
],
);
# Generate LiveKit participant list (kind 39004, relay-generated)
my $participant_event = Net::Nostr::Group->participants(
pubkey => $relay_pubkey,
group_id => 'pizza',
participants => [$pk1, $pk2],
);
# Parse received events
my $meta_info = Net::Nostr::Group->metadata_from_event($event);
my $admin_info = Net::Nostr::Group->admins_from_event($event);
my $member_info = Net::Nostr::Group->members_from_event($event);
my $role_info = Net::Nostr::Group->roles_from_event($event);
my $part_info = Net::Nostr::Group->participants_from_event($event);
my $gid = Net::Nostr::Group->group_id_from_event($event);
DESCRIPTION
Implements NIP-29 relay-based groups. Groups have arbitrary non-empty group_id strings in event h and d tags. Public group identifiers are naddr references to the group's kind 39000 metadata event. Group state is managed through moderation events (kinds 9000-9009) and user events (kinds 9021-9022). Relay-generated group state is published as addressable events (kinds 39000-39004) signed by the relay.
All user and moderation events MUST include an h tag with the group id. Group metadata events use a d tag instead.
To store a user's list of groups, use a kind 10009 Net::Nostr::List with group and r tags per NIP-51:
use Net::Nostr::List;
my $groups = Net::Nostr::List->new(kind => 10009);
my $group_id = Net::Nostr::Group->format_id(
pubkey => $relay_pubkey,
group_id => 'pizza',
relay => 'wss://groups.nostr.com',
);
$groups->add('group', $group_id, 'wss://groups.nostr.com', 'Pizza Lovers');
$groups->add('r', 'wss://groups.nostr.com');
my $event = $groups->to_event(pubkey => $key->pubkey_hex);
CLASS METHODS
parse_id
my $parsed = Net::Nostr::Group->parse_id($naddr);
# { group_id => 'pizza', pubkey => $relay_pubkey, kind => 39000, ... }
Parses a group identifier naddr that references a kind 39000 metadata event. Returns the raw group_id, relay pubkey, kind, relay hints, and the first relay hint as relay. Croaks for legacy host-based identifiers, invalid bech32 data, or naddr values that do not reference kind 39000.
format_id
my $id = Net::Nostr::Group->format_id(
pubkey => $relay_pubkey,
group_id => 'pizza',
relay => 'wss://groups.nostr.com',
);
# naddr1...
Formats a public group identifier as an naddr referencing the group's kind 39000 metadata event. pubkey is the relay's self pubkey. relay or relays may be supplied as relay hints. relays must be an arrayref.
validate_group_id
Net::Nostr::Group->validate_group_id('my-group'); # 1
Net::Nostr::Group->validate_group_id('Pizza Fans'); # 1
Net::Nostr::Group->validate_group_id(''); # 0
Returns true if the group id is a defined non-empty scalar. Current NIP-29 does not restrict group ids to a specific character set.
put_user
my $event = Net::Nostr::Group->put_user(
pubkey => $hex_pubkey,
group_id => 'pizza',
target => $user_pubkey,
roles => ['admin', 'moderator'], # optional
reason => 'promoted', # optional
previous => ['abcd1234'], # optional timeline refs
);
Creates a kind 9000 moderation event to add a user to the group or update their roles. The p tag contains the target pubkey followed by any role strings.
remove_user
my $event = Net::Nostr::Group->remove_user(
pubkey => $hex_pubkey,
group_id => 'pizza',
target => $user_pubkey,
reason => 'spamming', # optional
);
Creates a kind 9001 moderation event to remove a user from the group.
All user and moderation event builders (put_user, remove_user, edit_metadata, delete_event, create_group, delete_group, create_invite, join_request, leave_request) accept an optional previous parameter for timeline references. See put_user for an example.
edit_metadata
my $event = Net::Nostr::Group->edit_metadata(
pubkey => $hex_pubkey,
group_id => 'pizza',
name => 'Pizza Lovers', # optional
picture => 'https://pic.url', # optional
about => 'description', # optional
private => 1, # optional flag
restricted => 1, # optional flag
hidden => 1, # optional flag
closed => 1, # optional flag
unrestricted => 1, # optional flag
open => 1, # optional flag
visible => 1, # optional flag
public => 1, # optional flag
);
Creates a kind 9002 moderation event to update group metadata. Metadata fields become tags. Boolean flags become single-element tags when true.
delete_event
my $event = Net::Nostr::Group->delete_event(
pubkey => $hex_pubkey,
group_id => 'pizza',
event_id => $event_id_hex,
reason => 'spam content', # optional
);
Creates a kind 9005 moderation event to delete an event from the group.
create_group
my $event = Net::Nostr::Group->create_group(
pubkey => $hex_pubkey,
group_id => 'new-group',
);
Creates a kind 9007 event requesting the relay to create a new group.
delete_group
my $event = Net::Nostr::Group->delete_group(
pubkey => $hex_pubkey,
group_id => 'old-group',
reason => 'inactive', # optional
);
Creates a kind 9008 event requesting the relay to delete a group.
create_invite
my $event = Net::Nostr::Group->create_invite(
pubkey => $hex_pubkey,
group_id => 'pizza',
code => 'secret-code-123',
);
Creates a kind 9009 event with an invite code for the group.
join_request
my $event = Net::Nostr::Group->join_request(
pubkey => $hex_pubkey,
group_id => 'pizza',
reason => 'I love pizza', # optional
code => 'invite-abc', # optional invite code
);
Creates a kind 9021 join request event. The optional code tag can be used with invite codes created by create_invite.
leave_request
my $event = Net::Nostr::Group->leave_request(
pubkey => $hex_pubkey,
group_id => 'pizza',
reason => 'moving on', # optional
);
Creates a kind 9022 leave request event.
metadata
my $event = Net::Nostr::Group->metadata(
pubkey => $relay_pubkey,
group_id => 'pizza',
name => 'Pizza Lovers',
picture => 'https://pizza.com/pizza.png',
about => 'a group for pizza fans',
private => 1, # only members can read
restricted => 1, # only members can write
hidden => 1, # hide metadata from non-members
closed => 1, # ignore join requests
livekit => 1, # group supports LiveKit A/V rooms
supported_kinds => [9, 11], # text event kinds supported by the group
);
Creates a kind 39000 addressable event describing group metadata. This event should be signed by the relay's master key. Uses a d tag (not h) with the group id. supported_kinds, when supplied, must be an arrayref.
admins
my $event = Net::Nostr::Group->admins(
pubkey => $relay_pubkey,
group_id => 'pizza',
content => 'admin list', # optional
members => [
{ pubkey => $pk, roles => ['admin'] },
],
);
Creates a kind 39001 addressable event listing group admins with roles.
members
my $event = Net::Nostr::Group->members(
pubkey => $relay_pubkey,
group_id => 'pizza',
content => 'member list', # optional
members => [$pk1, $pk2],
);
Creates a kind 39002 addressable event listing group members.
roles
my $event = Net::Nostr::Group->roles(
pubkey => $relay_pubkey,
group_id => 'pizza',
content => 'role definitions', # optional
roles => [
{ name => 'admin', description => 'full control' },
{ name => 'moderator' },
],
);
Creates a kind 39003 addressable event listing supported roles.
participants
my $event = Net::Nostr::Group->participants(
pubkey => $relay_pubkey,
group_id => 'pizza',
content => 'participant list', # optional
participants => [$pk1, $pk2],
);
Creates a kind 39004 addressable event listing current LiveKit participants. participants must be an arrayref. This event should be signed by the relay's master key.
metadata_from_event
my $meta = Net::Nostr::Group->metadata_from_event($event);
# { group_id => '...', name => '...', picture => '...',
# about => '...', private => 1, closed => 1 }
Parses a kind 39000 event. Returns a hashref with group metadata. Boolean flags (private, restricted, hidden, closed, livekit) are set to 1 when present. supported_kinds is returned as an arrayref when present. Croaks if the event is not kind 39000.
admins_from_event
my $result = Net::Nostr::Group->admins_from_event($event);
# { group_id => '...', admins => [{ pubkey => '...', roles => [...] }] }
Parses a kind 39001 event. Returns a hashref with group_id and an arrayref of admin entries. Croaks if the event is not kind 39001.
for my $admin (@{$result->{admins}}) {
say "$admin->{pubkey}: " . join(', ', @{$admin->{roles}});
}
members_from_event
my $result = Net::Nostr::Group->members_from_event($event);
# { group_id => '...', members => [$pk1, $pk2] }
Parses a kind 39002 event. Returns a hashref with group_id and an arrayref of member pubkeys. Croaks if the event is not kind 39002.
roles_from_event
my $result = Net::Nostr::Group->roles_from_event($event);
# { group_id => '...', roles => [{ name => '...', description => '...' }] }
Parses a kind 39003 event. Returns a hashref with group_id and an arrayref of role definitions. Croaks if the event is not kind 39003.
for my $role (@{$result->{roles}}) {
say "$role->{name}: $role->{description}";
}
participants_from_event
my $result = Net::Nostr::Group->participants_from_event($event);
# { group_id => '...', participants => [$pk1, $pk2] }
Parses a kind 39004 event. Returns a hashref with group_id and an arrayref of participant pubkeys. Croaks if the event is not kind 39004.
group_id_from_event
my $gid = Net::Nostr::Group->group_id_from_event($event);
Extracts the group id from an event's h tag (user/moderation events) or d tag (metadata events). If both tags are present, the h tag takes priority. Returns undef if neither is found.
my $gid = Net::Nostr::Group->group_id_from_event($join_event);
say "Group: $gid";
SEE ALSO
NIP-29, Net::Nostr, Net::Nostr::Event, Net::Nostr::List (kind 10009 group storage)