NAME

Net::Nostr::Message - Nostr protocol message serialization and parsing

SYNOPSIS

use Net::Nostr::Message;

# Client-to-relay: publish an event
my $msg = Net::Nostr::Message->new(type => 'EVENT', event => $event);
my $json = $msg->serialize;  # '["EVENT",{...}]'

# Client-to-relay: subscribe
my $msg = Net::Nostr::Message->new(
    type            => 'REQ',
    subscription_id => 'feed',
    filters         => [$filter1, $filter2],
);

# Client-to-relay: close subscription
my $msg = Net::Nostr::Message->new(
    type            => 'CLOSE',
    subscription_id => 'feed',
);

# Parse a relay EVENT message
my $msg = Net::Nostr::Message->parse($json_string);
say $msg->type;             # 'EVENT'
say $msg->subscription_id;  # 'feed'
say $msg->event->content;   # event content

# Client-to-relay: NIP-42 authentication
my $msg = Net::Nostr::Message->new(type => 'AUTH', event => $auth_event);

# Relay-to-client: AUTH challenge
my $msg = Net::Nostr::Message->parse('["AUTH","challenge-string"]');
say $msg->challenge;  # 'challenge-string'

DESCRIPTION

Handles all NIP-01 message types for both client-to-relay and relay-to-client communication. Messages are constructed with new, serialized with serialize, and parsed from JSON with parse.

CONSTRUCTOR

new

my $msg = Net::Nostr::Message->new(type => $type, ...);

Creates a new message. type is required and must be one of: EVENT, REQ, CLOSE, OK, EOSE, NOTICE, CLOSED, COUNT, AUTH, NEG-OPEN, NEG-MSG, NEG-CLOSE, NEG-ERR.

Required fields by type:

EVENT  - event (Net::Nostr::Event object), optional subscription_id
REQ    - subscription_id, filters (arrayref of Net::Nostr::Filter)
CLOSE  - subscription_id
OK     - event_id (64-char lowercase hex), accepted (bool), optional message (defaults to '')
EOSE   - subscription_id
NOTICE - message (string)
CLOSED - subscription_id, message (string)
COUNT     - subscription_id, filters (client-to-relay) or count (non-negative integer, relay-to-client); mutually exclusive
AUTH       - challenge (string) or event (Net::Nostr::Event object); mutually exclusive
NEG-OPEN  - subscription_id, filter (Net::Nostr::Filter), neg_msg (even-length hex string)
NEG-MSG   - subscription_id, neg_msg (even-length hex string)
NEG-CLOSE - subscription_id
NEG-ERR   - subscription_id, message (string), optional neg_limit (non-negative integer)

subscription_id must be a non-empty string of at most 64 characters for REQ, CLOSE, and COUNT messages. For EOSE and CLOSED, subscription_id must be defined but is not length-validated (relay-to-client messages echo back whatever the client sent). event_id must be 64-character lowercase hex. event must be a Net::Nostr::Event object. filters must be an arrayref of Net::Nostr::Filter objects. message and challenge must be non-reference scalars.

For AUTH, passing both event and challenge is rejected. For COUNT, passing both count and filters is rejected. Croaks on missing required fields, invalid field formats, mutually exclusive arguments, type mismatches, unknown type, or unknown arguments.

METHODS

serialize

my $json = $msg->serialize;

Returns the JSON-encoded message string per the NIP-01 wire format.

my $msg = Net::Nostr::Message->new(type => 'CLOSE', subscription_id => 'x');
say $msg->serialize;  # '["CLOSE","x"]'

parse

my $msg = Net::Nostr::Message->parse($json_string);

Class method. Parses a JSON message string and returns a new Net::Nostr::Message object. Croaks on invalid JSON, unknown message types, or malformed messages. Validates structural format of each message type (element count, field types). String fields (subscription_id, message, challenge) are rejected if they are JSON objects or arrays. event_id in OK messages must be 64-character lowercase hex. For EVENT and AUTH messages, the contained event is constructed via Net::Nostr::Event->from_wire which requires all seven NIP-01 fields (id, pubkey, created_at, kind, tags, content, sig) and rejects missing or undefined fields.

Trust boundary: parse validates message structure and field formats but does not verify event signatures, event ID hashes, or authenticity. The caller is responsible for verifying event integrity via $event->validate, which recomputes the ID hash and verifies the signature against the event's own pubkey.

my $msg = Net::Nostr::Message->parse('["NOTICE","hello"]');
say $msg->type;     # 'NOTICE'
say $msg->message;  # 'hello'

type

my $type = $msg->type;  # 'EVENT', 'OK', 'REQ', etc.

subscription_id

my $sub_id = $msg->subscription_id;

The subscription ID. Present on EVENT (relay-to-client), REQ, CLOSE, EOSE, and CLOSED messages.

event

my $event = $msg->event;  # Net::Nostr::Event

The event object. Present on EVENT and AUTH (client-to-relay) messages.

event_id

my $id = $msg->event_id;

The event ID string. Present on OK messages.

accepted

my $bool = $msg->accepted;  # 1 or 0

Whether the event was accepted. Present on OK messages.

message

my $text = $msg->message;

The message string. Present on OK, NOTICE, and CLOSED messages. For OK and CLOSED, may include a machine-readable prefix like "duplicate: already have this event".

prefix

my $prefix = $msg->prefix;  # 'duplicate', 'blocked', etc. or undef

The machine-readable prefix extracted from the message string. Standard prefixes: duplicate, pow, blocked, rate-limited, invalid, restricted, auth-required, mute, error.

my $msg = Net::Nostr::Message->parse(
    '["OK","aa...",false,"blocked: you are banned"]'
);
say $msg->prefix;  # 'blocked'

count

my $count = $msg->count;  # non-negative integer

The event count. Present on COUNT response messages (NIP-45). Must be a non-negative integer; croaks on references, non-numeric strings, negative values, or floats.

my $msg = Net::Nostr::Message->parse('["COUNT","q1",{"count":42}]');
say $msg->count;  # 42

approximate

my $approx = $msg->approximate;  # 1 or undef

Whether the count is probabilistic. Present on COUNT response messages when the relay uses approximate counting.

filters

my $filters = $msg->filters;  # arrayref of Net::Nostr::Filter

The filter objects. Present on REQ and COUNT (client-to-relay) messages.

challenge

my $challenge = $msg->challenge;

The challenge string. Present on AUTH messages from relays.

my $msg = Net::Nostr::Message->parse('["AUTH","challenge123"]');
say $msg->challenge;  # 'challenge123'

neg_msg

my $hex = $msg->neg_msg;

The negentropy protocol message as an even-length hex string. Present on NEG-OPEN and NEG-MSG messages.

filter

my $filter = $msg->filter;  # Net::Nostr::Filter

A single NIP-01 filter. Present on NEG-OPEN messages.

neg_limit

my $limit = $msg->neg_limit;  # non-negative integer or undef

Optional maximum number of records the relay will process. Present on NEG-ERR messages when the relay rejects a query as too large. Must be a non-negative integer; croaks on references, non-numeric strings, negative values, or floats.

my $msg = Net::Nostr::Message->parse('["NEG-ERR","x","blocked: too big",100000]');
say $msg->neg_limit;  # 100000

SEE ALSO

NIP-01, NIP-42, NIP-45, NIP-77, Net::Nostr, Net::Nostr::Event, Net::Nostr::Filter