NAME

Net::Nostr::Marketplace - NIP-15 Nostr Marketplace

SYNOPSIS

use Net::Nostr::Marketplace;

# Create a stall (kind 30017)
my $stall = Net::Nostr::Marketplace->stall_event(
    pubkey   => $hex_pubkey,
    id       => 'stall-1',
    name     => 'My Stall',
    currency => 'USD',
    shipping => [
        { id => 'zone-1', name => 'US', cost => 5.0, regions => ['US', 'CA'] },
    ],
);

# Create a product (kind 30018)
my $product = Net::Nostr::Marketplace->product_event(
    pubkey     => $hex_pubkey,
    id         => 'prod-1',
    stall_id   => 'stall-1',
    name       => 'Widget',
    currency   => 'USD',
    price      => 10.50,
    quantity   => 100,
    categories => ['electronics'],
);

# Create a marketplace (kind 30019)
my $market = Net::Nostr::Marketplace->marketplace_event(
    pubkey    => $hex_pubkey,
    id        => 'market-1',
    name      => 'My Market',
    merchants => [$merchant_pubkey],
);

# Create an auction (kind 30020)
my $auction = Net::Nostr::Marketplace->auction_event(
    pubkey       => $hex_pubkey,
    id           => 'auction-1',
    stall_id     => 'stall-1',
    name         => 'Rare Item',
    starting_bid => 1000,
    duration     => 86400,
);

# Place a bid (kind 1021)
my $bid = Net::Nostr::Marketplace->bid_event(
    pubkey           => $hex_pubkey,
    amount           => 1500,
    auction_event_id => $auction_event_id,
);

# Confirm a bid (kind 1022)
my $confirm = Net::Nostr::Marketplace->bid_confirmation_event(
    pubkey           => $hex_pubkey,
    bid_event_id     => $bid_event_id,
    auction_event_id => $auction_event_id,
    status           => 'accepted',
);

# Checkout messages (JSON for NIP-04 direct messages)
my $order = Net::Nostr::Marketplace->order_message(
    id          => 'order-1',
    items       => [{ product_id => 'prod-1', quantity => 2 }],
    shipping_id => 'zone-1',
);

my $payment = Net::Nostr::Marketplace->payment_request_message(
    id              => 'order-1',
    payment_options => [{ type => 'ln', link => 'lnbc...' }],
);

my $status = Net::Nostr::Marketplace->order_status_message(
    id      => 'order-1',
    message => 'Shipped!',
    paid    => JSON::true,
    shipped => JSON::true,
);

# Parse events
my $parsed = Net::Nostr::Marketplace->from_event($event);

# Parse checkout messages
my $checkout = Net::Nostr::Marketplace->parse_checkout_message($json_string);

DESCRIPTION

Implements NIP-15 Nostr Marketplace. Merchants publish stalls, products, and auctions as Nostr events. Customers interact through bids and checkout messages sent via NIP-04 direct messages.

Six event kinds are involved:

kind 30017 - Stall (addressable). Contains stall name, currency, and shipping zones. The d tag MUST match the stall id.
kind 30018 - Product (addressable). Contains product details including price, quantity, and optional specs. The d tag MUST match the product id. Categories are stored as t tags.
kind 30019 - Marketplace UI/UX (addressable). Customizes the marketplace experience with name, description, theme, and merchant list.
kind 30020 - Auction (addressable). Similar to products but with starting_bid and duration instead of fixed price. The d tag MUST match the auction id.
kind 1021 - Bid. Content is the bid amount. References the auction event via an e tag. Bids reference the event ID (not UUID) so editing an auction after a bid invalidates the bid.
kind 1022 - Bid confirmation. Merchant confirms/rejects bids. Status can be accepted, rejected, pending, or winner. References both the bid and auction events via e tags. May extend the auction duration.

Checkout is handled via three message types exchanged as NIP-04 encrypted direct messages:

type 0 - Customer order with items, shipping zone, and optional contact info.
type 1 - Merchant payment request with payment options (url, btc, ln, lnurl).
type 2 - Merchant order status update with paid/shipped booleans.

CONSTRUCTOR

new

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

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

my $info = Net::Nostr::Marketplace->new(
    name  => 'Widget',
    price => 10.50,
);

Croaks on unknown arguments.

CLASS METHODS

stall_event

my $event = Net::Nostr::Marketplace->stall_event(
    pubkey      => $hex_pubkey,
    id          => 'stall-1',
    name        => 'My Stall',
    description => 'Optional description',
    currency    => 'USD',
    shipping    => [
        { id => 'zone-1', name => 'US', cost => 5.0, regions => ['US'] },
    ],
);

Creates a kind 30017 stall Net::Nostr::Event. pubkey, id, name, currency, and shipping are required. description is optional. Each shipping zone requires id, cost, and regions; name is optional.

product_event

my $event = Net::Nostr::Marketplace->product_event(
    pubkey      => $hex_pubkey,
    id          => 'prod-1',
    stall_id    => 'stall-1',
    name        => 'Widget',
    currency    => 'USD',
    price       => 10.50,
    quantity    => 100,
    description => 'A fine widget',
    images      => ['https://img.jpg'],
    specs       => [['color', 'blue']],
    shipping    => [{ id => 'zone-1', cost => 2.0 }],
    categories  => ['widgets'],
);

Creates a kind 30018 product Net::Nostr::Event. pubkey, id, stall_id, name, currency, and price are required. quantity can be undef for unlimited items. categories become t tags.

marketplace_event

my $event = Net::Nostr::Marketplace->marketplace_event(
    pubkey    => $hex_pubkey,
    id        => 'market-1',
    name      => 'My Market',
    about     => 'A cool marketplace',
    ui        => { theme => 'dark', darkMode => JSON::true },
    merchants => [$pubkey1, $pubkey2],
);

Creates a kind 30019 marketplace UI/UX Net::Nostr::Event. pubkey and id are required. All other fields are optional.

auction_event

my $event = Net::Nostr::Marketplace->auction_event(
    pubkey       => $hex_pubkey,
    id           => 'auction-1',
    stall_id     => 'stall-1',
    name         => 'Rare Item',
    starting_bid => 1000,
    start_date   => 1700000000,
    duration     => 86400,
);

Creates a kind 30020 auction Net::Nostr::Event. pubkey, id, stall_id, name, starting_bid, and duration are required. start_date is optional; omit if the start date is unknown or hidden.

bid_event

my $event = Net::Nostr::Marketplace->bid_event(
    pubkey           => $hex_pubkey,
    amount           => 1500,
    auction_event_id => $event_id,
);

Creates a kind 1021 bid Net::Nostr::Event. Content is the amount as a string. An e tag references the auction event.

bid_confirmation_event

my $event = Net::Nostr::Marketplace->bid_confirmation_event(
    pubkey             => $hex_pubkey,
    bid_event_id       => $bid_id,
    auction_event_id   => $auction_id,
    status             => 'accepted',
    message            => 'Welcome!',
    duration_extended  => 300,
);

Creates a kind 1022 bid confirmation Net::Nostr::Event. pubkey, bid_event_id, auction_event_id, and status are required. message and duration_extended are optional. Two e tags reference the bid and auction events.

order_message

my $json = Net::Nostr::Marketplace->order_message(
    id          => 'order-1',
    items       => [{ product_id => 'prod-1', quantity => 2 }],
    shipping_id => 'zone-1',
    name        => 'Alice',
    address     => '123 Main St',
    message     => 'Gift wrap please',
    contact     => { nostr => $pubkey, phone => '+1234567890', email => 'a@b.com' },
);

Builds a type 0 order JSON string for NIP-04 checkout. id, items, and shipping_id are required. All other fields are optional.

payment_request_message

my $json = Net::Nostr::Marketplace->payment_request_message(
    id              => 'order-1',
    payment_options => [
        { type => 'ln', link => 'lnbc...' },
        { type => 'btc', link => 'bc1q...' },
    ],
    message => 'Pay within 24 hours',
);

Builds a type 1 payment request JSON string. id and payment_options are required. Payment option types include url, btc, ln, and lnurl. message is optional.

order_status_message

my $json = Net::Nostr::Marketplace->order_status_message(
    id      => 'order-1',
    message => 'Shipped!',
    paid    => JSON::true,
    shipped => JSON::true,
);

Builds a type 2 order status JSON string. All fields are required.

from_event

my $parsed = Net::Nostr::Marketplace->from_event($event);

Parses a marketplace event (kinds 30017, 30018, 30019, 30020, 1021, 1022) into a Net::Nostr::Marketplace object. Returns undef for unrecognized kinds.

parse_checkout_message

my $msg = Net::Nostr::Marketplace->parse_checkout_message($json_string);
say $msg->checkout_type;  # 0, 1, or 2
say $msg->order_id;

Parses a checkout JSON string (type 0 order, type 1 payment request, or type 2 order status) into a Net::Nostr::Marketplace object.

validate

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

Validates a marketplace event. Croaks on invalid structure. Addressable events (30017, 30018, 30019, 30020) must have a d tag. Kind 1021 must have an e tag. Kind 1022 must have two e tags.

ACCESSORS

Available on objects returned by "from_event" and "parse_checkout_message". Which accessors contain data depends on which event kind or message type was parsed.

stall_id

Stall identifier (from kind 30017 or 30018).

product_id

Product or auction identifier (from kind 30018 or 30020).

name

Name of the stall, product, auction, or marketplace.

description

Optional description.

currency

Currency code (e.g. USD, sat).

price

Product price (from kind 30018).

quantity

Available quantity; undef for unlimited (from kind 30018).

images

Arrayref of image URLs.

specs

Arrayref of [$key, $value] specification pairs.

shipping

Arrayref of shipping zone hashrefs.

categories

Arrayref of category strings from t tags (kind 30018).

about

Marketplace description (kind 30019).

ui

UI configuration hashref (kind 30019).

merchants

Arrayref of merchant pubkeys (kind 30019).

starting_bid

Starting bid amount (kind 30020).

start_date

Auction start UNIX timestamp (kind 30020).

duration

Auction duration in seconds (kind 30020).

amount

Bid amount (kind 1021).

auction_event_id

Referenced auction event ID (kinds 1021, 1022).

bid_event_id

Referenced bid event ID (kind 1022).

status

Bid confirmation status: accepted, rejected, pending, or winner (kind 1022).

message

Optional message (kinds 1022, checkout messages).

duration_extended

Number of seconds by which the auction is extended (kind 1022).

checkout_type

Checkout message type: 0 (order), 1 (payment request), or 2 (order status).

order_id

Order identifier (checkout messages).

items

Arrayref of item hashrefs with product_id and quantity (type 0).

shipping_id

Selected shipping zone ID (type 0).

address

Shipping address string (type 0).

contact

Contact hashref with optional nostr, phone, email (type 0).

payment_options

Arrayref of payment option hashrefs with type and link (type 1).

Boolean indicating payment received (type 2).

shipped

Boolean indicating item shipped (type 2).

SEE ALSO

NIP-15, Net::Nostr, Net::Nostr::Event