This document specifies the implementation of separate Nostr event kinds for different Mostro event types. Currently, all Mostro application events (orders, ratings, info, disputes) use a single replaceable event kind (38383), differentiated only by the z tag. This change introduces dedicated kinds for each event type to improve client efficiency and relay filtering.
All Mostro events currently use NOSTR_ORDER_EVENT_KIND (kind 38383) from mostro_core:
| Event Type | Current Kind | Identifier (z tag) |
|---|---|---|
| Orders | 38383 | "order" |
| Ratings | 38383 | (embedded in order events) |
| Info | 38383 | "info" |
| Disputes | 38383 | "dispute" |
- Inefficient client subscriptions: Clients wanting only orders must download all event types
- Increased bandwidth usage: Unnecessary data transfer for specialized clients (e.g., orderbook displays)
- Relay filtering overhead: Relays cannot efficiently filter by event type at the protocol level
- Tag parsing requirement: Clients must parse
ztags to determine event type after download
| Event Type | New Kind | Change | NIP-33 Identifier (d tag) |
|---|---|---|---|
| Orders | 38383 | No change | order.id (UUID) |
| Ratings | 38384 | New (+1) | user_pubkey |
| Info | 38385 | New (+2) | mostro_pubkey |
| Disputes | 38386 | New (+3) | dispute.id (UUID) |
All kinds remain in the NIP-33 replaceable event range (30000-39999), ensuring events can be updated by publishing new events with the same d tag.
Note: All pubkeys in
dtags use hex encoding (not bech32/npub), consistent with how Nostr events are stored and processed internally. This ensures correct NIP-33 event replacement and relay filtering.
Location: mostro_core crate (external dependency)
Changes Required:
// Current constant (unchanged)
pub const NOSTR_ORDER_EVENT_KIND: u16 = 38383;
// New constants to add
pub const NOSTR_RATING_EVENT_KIND: u16 = 38384;
pub const NOSTR_INFO_EVENT_KIND: u16 = 38385;
pub const NOSTR_DISPUTE_EVENT_KIND: u16 = 38386;Rationale: Constants defined in mostro_core ensure consistency across all Mostro components (daemon, clients, libraries).
Current Implementation (src/nip33.rs:24-38):
pub fn new_event(
keys: &Keys,
content: &str,
identifier: String,
extra_tags: Tags,
) -> Result<Event, Error> {
// Uses NOSTR_ORDER_EVENT_KIND for all events
EventBuilder::new(nostr::Kind::Custom(NOSTR_ORDER_EVENT_KIND), content)
.tags(tags)
.sign_with_keys(keys)
}Proposed Changes:
Option A - Add kind parameter to existing function:
pub fn new_event(
keys: &Keys,
content: &str,
identifier: String,
extra_tags: Tags,
kind: u16, // New parameter
) -> Result<Event, Error> {
let mut tags: Vec<Tag> = Vec::with_capacity(1 + extra_tags.len());
tags.push(Tag::identifier(identifier));
tags.extend(extra_tags);
let tags = Tags::from_list(tags);
EventBuilder::new(nostr::Kind::Custom(kind), content)
.tags(tags)
.sign_with_keys(keys)
}Option B - Create separate functions for each event type (recommended for clarity):
/// Creates a new order event (kind 38383)
pub fn new_order_event(
keys: &Keys,
content: &str,
identifier: String,
extra_tags: Tags,
) -> Result<Event, Error> {
create_event(keys, content, identifier, extra_tags, NOSTR_ORDER_EVENT_KIND)
}
/// Creates a new rating event (kind 38384)
pub fn new_rating_event(
keys: &Keys,
content: &str,
identifier: String,
extra_tags: Tags,
) -> Result<Event, Error> {
create_event(keys, content, identifier, extra_tags, NOSTR_RATING_EVENT_KIND)
}
/// Creates a new info event (kind 38385)
pub fn new_info_event(
keys: &Keys,
content: &str,
identifier: String,
extra_tags: Tags,
) -> Result<Event, Error> {
create_event(keys, content, identifier, extra_tags, NOSTR_INFO_EVENT_KIND)
}
/// Creates a new dispute event (kind 38386)
pub fn new_dispute_event(
keys: &Keys,
content: &str,
identifier: String,
extra_tags: Tags,
) -> Result<Event, Error> {
create_event(keys, content, identifier, extra_tags, NOSTR_DISPUTE_EVENT_KIND)
}
// Internal helper function
fn create_event(
keys: &Keys,
content: &str,
identifier: String,
extra_tags: Tags,
kind: u16,
) -> Result<Event, Error> {
let mut tags: Vec<Tag> = Vec::with_capacity(1 + extra_tags.len());
tags.push(Tag::identifier(identifier));
tags.extend(extra_tags);
let tags = Tags::from_list(tags);
EventBuilder::new(nostr::Kind::Custom(kind), content)
.tags(tags)
.sign_with_keys(keys)
}Location: src/nip33.rs - order_to_tags() function and callers
Current Usage: Events created via new_event() with order tags
Change: Use new_order_event() (or new_event() with NOSTR_ORDER_EVENT_KIND)
Files to Update:
src/util.rs-send_new_order_msg()and related functions- Any location calling
new_event()for order publishing
Location: src/util.rs:606-633 - update_user_rating_event()
Current Code:
let event = new_event(keys, "", user.to_string(), tags)?;Updated Code:
let event = new_rating_event(keys, "", user.to_string(), tags)?;Location: src/scheduler.rs - job_info_event_send()
Current Flow:
info_to_tags()creates tags withz: "info"- Event created with
new_event()using mostro pubkey as identifier
Updated Flow:
info_to_tags()unchanged (tags still useful for filtering)- Event created with
new_info_event()
Location: src/app/dispute.rs:20-82 - publish_dispute_event()
Current Code (line 57):
let event = new_event(my_keys, "", dispute.id.to_string(), tags)
.map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?;Updated Code:
let event = new_dispute_event(my_keys, "", dispute.id.to_string(), tags)
.map_err(|_| MostroInternalErr(ServiceError::DisputeEventError))?;The z tag should be retained for backwards compatibility and additional context, but is no longer the primary identifier of event type.
| Event Type | Kind | z tag value |
|---|---|---|
| Orders | 38383 | "order" |
| Ratings | 38384 | "rating" |
| Info | 38385 | "info" |
| Disputes | 38386 | "dispute" |
Note: Rating events currently don't have a z tag. This implementation should add one for consistency.
| File | Function | Current Usage | New Usage |
|---|---|---|---|
src/nip33.rs |
new_event() |
Generic event creation | Keep for orders or deprecate |
src/util.rs |
update_user_rating_event() |
new_event() |
new_rating_event() |
src/util.rs |
Order publishing functions | new_event() |
new_order_event() |
src/scheduler.rs |
job_info_event_send() |
new_event() |
new_info_event() |
src/app/dispute.rs |
publish_dispute_event() |
new_event() |
new_dispute_event() |
To ensure smooth transition for existing clients:
- Transition Period: Publish events with both old and new kinds during migration
- Client Updates: Clients should update to query new kinds
- Deprecation Notice: Document that kind 38383 will only be used for orders after transition
During transition, publish duplicate events:
// Temporary: publish both old and new format
let legacy_event = new_event(keys, content, identifier.clone(), tags.clone())?;
let new_event = new_dispute_event(keys, content, identifier, tags)?;
client.send_event(&legacy_event).await?;
client.send_event(&new_event).await?;Note: This doubles event publishing and should only be used if backwards compatibility is critical. Recommended approach is a clean cutover with client coordination.
{
"kind": 38383,
"content": "",
"tags": [
["d", "550e8400-e29b-41d4-a716-446655440000"],
["k", "sell"],
["f", "USD"],
["s", "pending"],
["amt", "100000"],
["fa", "100"],
["pm", "bank_transfer,paypal"],
["premium", "5"],
["network", "mainnet"],
["layer", "lightning"],
["expires_at", "1704067200"],
["expiration", "1704153600"],
["y", "mostro"],
["z", "order"]
]
}{
"kind": 38384,
"content": "",
"tags": [
["d", "a1b2c3d4e5f6..."],
["total_reviews", "42"],
["total_rating", "4.8"],
["last_rating", "5"],
["min_rating", "3"],
["max_rating", "5"],
["days", "21"],
["y", "mostro"],
["z", "rating"]
]
}{
"kind": 38385,
"content": "",
"tags": [
["d", "a1b2c3d4e5f6..."],
["mostro_version", "0.15.6"],
["mostro_commit_hash", "abc123"],
["max_order_amount", "1000000"],
["min_order_amount", "1000"],
["fee", "0.01"],
["pow", "0"],
["y", "mostro"],
["z", "info"]
]
}{
"kind": 38386,
"content": "",
"tags": [
["d", "660e8400-e29b-41d4-a716-446655440001"],
["s", "pending"],
["initiator", "buyer"],
["y", "mostro"],
["z", "dispute"]
]
}// Get only orders - requires downloading all events
const filter = {
kinds: [38383],
"#y": ["mostro"],
"#z": ["order"] // Must filter after download
};// Get only orders - efficient relay-level filtering
const ordersFilter = { kinds: [38383], "#y": ["mostro"] };
// Get only ratings
const ratingsFilter = { kinds: [38384], "#y": ["mostro"] };
// Get only info
const infoFilter = { kinds: [38385], "#y": ["mostro"] };
// Get only disputes
const disputesFilter = { kinds: [38386], "#y": ["mostro"] };
// Get all Mostro events (if needed)
const allFilter = { kinds: [38383, 38384, 38385, 38386], "#y": ["mostro"] };-
Event Creation Tests:
- Verify
new_order_event()creates kind 38383 - Verify
new_rating_event()creates kind 38384 - Verify
new_info_event()creates kind 38385 - Verify
new_dispute_event()creates kind 38386
- Verify
-
Tag Preservation Tests:
- Verify all existing tags are preserved
- Verify
ztag is present and correct for each type
-
Event Publishing:
- Create and publish each event type
- Query relay by specific kind
- Verify only matching events returned
-
NIP-33 Replacement:
- Publish event with same
dtag - Verify old event is replaced
- Verify replacement works per-kind
- Publish event with same
- Create new order, verify kind 38383 on relay
- Complete order with rating, verify kind 38384 on relay
- Wait for info event, verify kind 38385 on relay
- Open dispute, verify kind 38386 on relay
- Query each kind separately, verify correct events returned
- Update existing order, verify NIP-33 replacement works
- mostro_core: Add new kind constants
NOSTR_RATING_EVENT_KIND = 38384NOSTR_INFO_EVENT_KIND = 38385NOSTR_DISPUTE_EVENT_KIND = 38386
- mostro-cli: Update subscription filters
- mostro-web: Update relay queries
- Third-party clients: Documentation for migration
- Add new constants
- Release new version
- Implement new event functions
- Update all publishing locations
- Test thoroughly
- Update mostro-cli queries
- Update mostro-web queries
- Publish migration guide
- Deploy updated mostrod
- Monitor relay events
- Verify correct kinds being published
- Remove backwards compatibility code if implemented
- Update documentation
| File | Changes |
|---|---|
mostro_core/src/lib.rs (external) |
Add 3 new kind constants |
src/nip33.rs |
Add kind-specific event builders |
src/util.rs |
Update rating event publishing |
src/scheduler.rs |
Update info event publishing |
src/app/dispute.rs |
Update dispute event publishing |
docs/ARCHITECTURE.md |
Document new kinds |
After implementation, verify correct operation with:
# Query orders only
nostr-cli req -k 38383 --tag y=mostro
# Query ratings only
nostr-cli req -k 38384 --tag y=mostro
# Query info only
nostr-cli req -k 38385 --tag y=mostro
# Query disputes only
nostr-cli req -k 38386 --tag y=mostro- NIP-01: Basic Protocol - Event kinds
- NIP-33: Parameterized Replaceable Events - Replaceable events
- NIP-69: Peer-to-peer Order Events - P2P marketplace specification
- Current implementation:
src/nip33.rs,src/app/dispute.rs,src/util.rs