NAME

Net::Nostr::ClassifiedListing - NIP-99 classified listings

SYNOPSIS

use Net::Nostr::ClassifiedListing;

my $pubkey = 'aa' x 32;

# Create a classified listing (kind 30402)
my $event = Net::Nostr::ClassifiedListing->listing(
    pubkey       => $pubkey,
    content      => "# Vintage Guitar\n\nGreat condition, barely played.",
    identifier   => 'vintage-guitar',
    title        => 'Vintage Guitar',
    summary      => 'A beautiful vintage guitar in great condition',
    published_at => 1296962229,
    location     => 'NYC',
    price        => ['500', 'USD'],
    status       => 'active',
    hashtags     => ['music', 'instruments'],
    images       => [
        ['https://example.com/guitar1.jpg', '800x600'],
        ['https://example.com/guitar2.jpg'],
    ],
);

# Create a draft/inactive listing (kind 30403, same structure)
my $draft = Net::Nostr::ClassifiedListing->draft(
    pubkey     => $pubkey,
    content    => "# WIP Listing\n\nNot ready yet.",
    identifier => 'my-draft',
    title      => 'Work in Progress',
);

# Recurring price (e.g. monthly rent)
my $rental = Net::Nostr::ClassifiedListing->listing(
    pubkey     => $pubkey,
    content    => "Apartment for rent.",
    identifier => 'apartment-rent',
    title      => '2BR Apartment',
    price      => ['1500', 'USD', 'month'],
    location   => 'Brooklyn',
);

# Parse listing metadata from an event
my $info = Net::Nostr::ClassifiedListing->from_event($event);
say $info->title;        # 'Vintage Guitar'
say $info->location;     # 'NYC'
say $info->price->[0];   # '500'
say $info->price->[1];   # 'USD'

# Generate an naddr for linking
my $naddr = Net::Nostr::ClassifiedListing->to_naddr($event,
    relays => ['wss://relay.example.com'],
);

# Validate a listing event
Net::Nostr::ClassifiedListing->validate($event);

DESCRIPTION

Implements NIP-99 classified listings. Classified listings are kind 30402 addressable events that describe products, services, or other things for sale or offer. The structure is similar to NIP-23 long-form content, with additional metadata tags for pricing, location, and status.

Content should be a Markdown description of what is being offered. The pubkey field identifies the party creating the listing.

Draft or inactive listings use kind 30403, which has the same structure as kind 30402.

CONSTRUCTOR

new

my $info = Net::Nostr::ClassifiedListing->new(%fields);

Creates a new Net::Nostr::ClassifiedListing object. Typically returned by "from_event"; calling new directly is useful for testing or manual construction.

my $info = Net::Nostr::ClassifiedListing->new(
    identifier => 'my-listing',
    title      => 'Guitar',
    price      => '500 USD',
    location   => 'NYC',
);

Accepted fields: identifier, title, summary, published_at, location, price, status, images (defaults to []), hashtags (defaults to []). Croaks on unknown arguments.

CLASS METHODS

listing

my $event = Net::Nostr::ClassifiedListing->listing(
    pubkey       => $hex_pubkey,         # required
    content      => $markdown,           # required
    identifier   => 'listing-slug',      # required (d tag)
    title        => 'Listing Title',     # optional
    summary      => 'Short tagline.',    # optional
    published_at => 1296962229,          # optional (unix timestamp)
    location     => 'NYC',               # optional
    price        => ['100', 'USD'],      # optional
    status       => 'active',            # optional ("active" or "sold")
    hashtags     => ['electronics'],      # optional (t tags)
    images       => [['url', '256x256']],# optional (image tags)
    extra_tags   => [['g', 'dr5regw']],  # optional (additional tags)
    created_at   => time(),              # optional
);

Creates a kind 30402 classified listing Net::Nostr::Event. pubkey, content, and identifier are required.

The price parameter is an arrayref of [amount, currency] or [amount, currency, frequency] where currency is an ISO 4217 code (or crypto code like btc) and frequency is optional (e.g. month, year).

# One-time: $50 USD
price => ['50', 'USD']

# Recurring: 15 EUR/month
price => ['15', 'EUR', 'month']

The images parameter is an arrayref of arrayrefs. Each inner arrayref contains a URL and an optional dimensions string (WxH in pixels), per NIP-58.

images => [
    ['https://example.com/photo.jpg', '800x600'],
    ['https://example.com/detail.jpg'],
]

draft

my $event = Net::Nostr::ClassifiedListing->draft(
    pubkey     => $hex_pubkey,
    content    => $markdown,
    identifier => 'draft-slug',
    # same optional params as listing()
);

Creates a kind 30403 draft/inactive listing. Accepts the same parameters as "listing".

from_event

my $info = Net::Nostr::ClassifiedListing->from_event($event);

Parses listing metadata from a kind 30402 or 30403 Net::Nostr::Event. Returns a Net::Nostr::ClassifiedListing object with accessors, or undef if the event is not a listing kind.

my $info = Net::Nostr::ClassifiedListing->from_event($event);
say $info->identifier;   # 'my-listing'
say $info->title;        # 'Vintage Guitar' or undef
say $info->price->[1] if $info->price;  # 'USD'

validate

Net::Nostr::ClassifiedListing->validate($event);

Validates that an event is a well-formed NIP-99 listing. Croaks if:

  • Kind is not 30402 or 30403

  • Missing d tag

eval { Net::Nostr::ClassifiedListing->validate($event) };
warn "Invalid listing: $@" if $@;

to_naddr

my $naddr = Net::Nostr::ClassifiedListing->to_naddr($event,
    relays => ['wss://relay.com'],
);

Generates a NIP-19 naddr bech32 string for linking to the listing. The relays parameter is optional.

ACCESSORS

These are available on objects returned by "from_event".

identifier

my $id = $info->identifier;

The d tag value identifying the listing.

title

my $title = $info->title;  # or undef

The listing title, or undef if not set.

summary

my $text = $info->summary;  # or undef

Short tagline or summary for the listing, or undef.

published_at

my $ts = $info->published_at;  # '1296962229' or undef

The original publication timestamp (stringified unix seconds), or undef.

location

my $loc = $info->location;  # 'NYC' or undef

The listing location, or undef.

price

my $price = $info->price;  # ['100', 'USD'] or ['15', 'EUR', 'month'] or undef

Arrayref of [amount, currency] or [amount, currency, frequency], or undef if no price tag is present.

status

my $status = $info->status;  # 'active', 'sold', or undef

The listing status, or undef.

images

my $imgs = $info->images;  # [['url', '256x256'], ['url2']]

Arrayref of arrayrefs, each containing a URL and optional dimensions. Empty arrayref if no image tags are present.

hashtags

my $tags = $info->hashtags;  # ['electronics', 'gadgets']

Arrayref of hashtag strings from t tags. Empty arrayref if none.

SEE ALSO

NIP-99, Net::Nostr::Article, Net::Nostr, Net::Nostr::Event