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
dtag MUST match the stallid. - 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_bidanddurationinstead of fixed price. Thedtag MUST match the auctionid. - kind 1021 - Bid. Content is the bid amount. References the auction event via an
etag. Bids reference the event ID (not UUID) so editing an auction after a bid invalidates the bid.
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).
paid
Boolean indicating payment received (type 2).
shipped
Boolean indicating item shipped (type 2).