Skip to content

Latest commit

 

History

History
497 lines (380 loc) · 13.6 KB

File metadata and controls

497 lines (380 loc) · 13.6 KB

Separate Event Kinds Technical Specification

Overview

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.

Problem Statement

Current Architecture

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"

Issues

  1. Inefficient client subscriptions: Clients wanting only orders must download all event types
  2. Increased bandwidth usage: Unnecessary data transfer for specialized clients (e.g., orderbook displays)
  3. Relay filtering overhead: Relays cannot efficiently filter by event type at the protocol level
  4. Tag parsing requirement: Clients must parse z tags to determine event type after download

Proposed Solution

New Kind Assignment

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 d tags 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.

Implementation Details

Phase 1: Define New Constants in mostro_core

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).

Phase 2: Update src/nip33.rs

2.1 Create Kind-Specific Event Builders

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)
}

Phase 3: Update Event Publishing Locations

3.1 Order Events

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

3.2 Rating Events

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)?;

3.3 Info Events

Location: src/scheduler.rs - job_info_event_send()

Current Flow:

  1. info_to_tags() creates tags with z: "info"
  2. Event created with new_event() using mostro pubkey as identifier

Updated Flow:

  1. info_to_tags() unchanged (tags still useful for filtering)
  2. Event created with new_info_event()

3.4 Dispute Events

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))?;

Phase 4: Update Tag Structure

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.

Phase 5: Update Callers

Files Requiring Updates

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()

Migration Strategy

Backwards Compatibility

To ensure smooth transition for existing clients:

  1. Transition Period: Publish events with both old and new kinds during migration
  2. Client Updates: Clients should update to query new kinds
  3. Deprecation Notice: Document that kind 38383 will only be used for orders after transition

Transition Implementation (Optional)

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.

Event Examples

Order Event (Kind 38383 - Unchanged)

{
  "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"]
  ]
}

Rating Event (Kind 38384 - New)

{
  "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"]
  ]
}

Info Event (Kind 38385 - New)

{
  "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"]
  ]
}

Dispute Event (Kind 38386 - New)

{
  "kind": 38386,
  "content": "",
  "tags": [
    ["d", "660e8400-e29b-41d4-a716-446655440001"],
    ["s", "pending"],
    ["initiator", "buyer"],
    ["y", "mostro"],
    ["z", "dispute"]
  ]
}

Client Query Examples

Before (Single Kind, Tag Filtering)

// Get only orders - requires downloading all events
const filter = {
  kinds: [38383],
  "#y": ["mostro"],
  "#z": ["order"]  // Must filter after download
};

After (Dedicated Kinds)

// 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"] };

Testing Plan

Unit Tests

  1. 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
  2. Tag Preservation Tests:

    • Verify all existing tags are preserved
    • Verify z tag is present and correct for each type

Integration Tests

  1. Event Publishing:

    • Create and publish each event type
    • Query relay by specific kind
    • Verify only matching events returned
  2. NIP-33 Replacement:

    • Publish event with same d tag
    • Verify old event is replaced
    • Verify replacement works per-kind

Manual Testing Checklist

  • 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

Dependencies

External Changes Required

  1. mostro_core: Add new kind constants
    • NOSTR_RATING_EVENT_KIND = 38384
    • NOSTR_INFO_EVENT_KIND = 38385
    • NOSTR_DISPUTE_EVENT_KIND = 38386

Client Updates Required

  1. mostro-cli: Update subscription filters
  2. mostro-web: Update relay queries
  3. Third-party clients: Documentation for migration

Rollout Plan

Step 1: Update mostro_core

  • Add new constants
  • Release new version

Step 2: Update mostrod

  • Implement new event functions
  • Update all publishing locations
  • Test thoroughly

Step 3: Update Clients

  • Update mostro-cli queries
  • Update mostro-web queries
  • Publish migration guide

Step 4: Deploy

  • Deploy updated mostrod
  • Monitor relay events
  • Verify correct kinds being published

Step 5: Cleanup (Optional)

  • Remove backwards compatibility code if implemented
  • Update documentation

Files to Modify Summary

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

Verification

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

References