Introduction
The objective of this Nostr Specification is to enable two or more parties to transact material information, quote, and close orders in a standardized, structured way with their trading partners. Open Materials Availability (OMA) is an extension of the Nostr Protocol, leveraging Nostr’s federated data exchange model for companies to connect, and transact information in a trustless environment. This specification extends Nostr’s functionality with several key models embedded in Nostr events, enabling supply chain interactions. These models are:- Listings – Material supply or demand
- Quote – A bid or an ask on a published material listing
- Quote Response – A closure on a quote (accepted, or rejected)
Events
OMA events are the atomic units of information exchange in the network. Events are defined by Nostr NIP-01. Models used in the network are JSON object embedded into these Nostr events.Tags
Along with the content, events are tagged according to a convention. Following Nostr’s convention of indexing single-letter tags only, these are tags and their associated fields the spec uses for events. All tags SHOULD be lowercase, with no spaces. All tags except UserPublicKey are expected to have one value. Format:["<single-letter>", "<value>"]
- Address:
"a"
- Identifier:
"d"
- MaterialNo:
"n"
- Manufacturer:
"m"
- Thread:
"h"
- Type:
"y"
- Company:
"c"
- Batch:
"b"
- InternalNo:
"i"
- Generic:
"t"
- Reply Node:
"r"
- Recipient:
"p"
- Status:
"s"
- Finalized:
"z"
- Event:
"e"
- UserPublicKey:
"u"
Tag Explanations
Tags serve as queryable and additional identifieng information about an event.a
— Address
- Use: Referential address of an event for replacement, i.e. when a listing must be updated, it is done so on the address.
- Example:
["a","<kind integer>:<32-bytes lowercase hex of a pubkey>:<d tag value>"]
d
— Identifier
- Use: stable identifier for a given material, this is a concatination of material information that makes it physically unique among other components with the same part number.
- Example:
["d","MaterialNo + Supply + DateCode"]
n
— MaterialNo
- Use: material/part number from your system.
- Example:
["n","MAT-00042"]
m
— Manufacturer
- Use: manufacturer name.
- Example:
["m","Acme Corp"]
h
— Thread
- Use: groups related events.
- Value: a deterministic thread ID computed from the root
event_id
and two participant pubkeys.
y
— Type
- Use: material transaction type (e.g.,
supply
,demand
). - Example:
["y","supply"]
c
— Company
- Use: event publisher namespace.
- Example:
["c","acme"]
b
— Batch
- Use: groups listings created by a single publish action (e.g., one API call or one file import).
- Example:
["b","XYZ123"]
i
— InternalNo
- Use: internal ERP/DB tracking/reference.
- Example:
["i","INT-7788"]
t
— Generic
- Use: free-form topic tag (search/filter).
- Example:
["t","diodes"]
r
— Reply Node
- Use: websocket URL for reply events.
- Example:
["r","wss://domain.seminode.com/inbox"]
p
— Recipient
- Use: intended recipient’s Nostr pubkey.
- Example:
["p","<pubkey>"]
- Note: This tag is exclusive to thread events of kind Quote, or Quote Response
s
— Status
- Use: workflow state this event belongs to (e.g.,
accepted
,rejected
). - Example:
["s","accepted"]
- Note: This tag is exclusive to thread events of kind Quote Response
z
— Finalized
- Use: workflow state this event belongs to (e.g.,
pending
,complete
). - Example:
["z","pending"]
- Note: This tag is exclusive to thread events of kind Quote, or Quote Response
e
— Event
- Use: reference to another event (mention/link).
- Example:
["e","<event-id>"]
u
— UserPublicKey
- Use: user pubkey(s) associated with the event, allows fine grain filtering for company users.
- Example(s):
["u","<hex>"]
(repeat tag if multiple)
Query Modifying Tags
Query Modifying tags offer further specification for what events the client wants returned. These tags are not stored in events, rather they are passed in filters and modify the underlying query.-
Match:
"match"
<Optional["match", "fuzzy"]>
- Match tag currently supports
"fuzzy"
, for prefix or trigram matching on the above select tags.
- Match tag currently supports
-
Intent:
"intent"
<Optional["intent", "tail"]>
- The intent tag currently supports the
"tail"
option. "tail"
marks the latest event in a conversation between two parties for a given event.- This is determined by the most recent timestamp for a unique combination of:
- The
"e"
tag (event) - And an unordered pair of the event Author and Recipient tag
"p"
.
- The
- This is determined by the most recent timestamp for a unique combination of:
- The intent tag currently supports the
Listing
Listings convey specific information about a material. Kind:31001
Content:
Quote
Similar to a Listing, a quote is a JSON object embedded in an event content. Quotes’ content, however, are strings of NIP-44 encrypted JSON objects, where the conversation key is derived from your private key and the recipient’s public key. When publishing a quote in reference to a listing, CLIENTS MUST republish the Listing to the relay URL in the listing “r” tag. This preserves the threads integrity as Network Events are mutable. Kind:2014
Content:
content
of the event, whereupon retrieval it is decrypted.
Quote Tags
- Quotes SHOULD copy the listing tags over.
- Quotes MUST generate a thread_id
"h"
if not present SEE Thread ID Tag - Quotes MUST set the recipient
"p"
tag to the listing event’s Author pubkey. - Quotes MUST be published to the listing’s
"r"
tag inbox relay URL, as well as the sender’s inbox relay.
Response
Kind:2024
A response is an event that actions a quote. Responses must either have status "accepted"
or "rejected"
. Like quotes, responses are NIP-44 encrypted between the author’s private key and the recipient’s public key.
In OMA, a quoting thread can be closed in two ways: either through a rejection, or through mutual acceptance by both parties. Tagging is used to represent the state of the response. The following tags MUST be set for responses depending on the state.
- Rejected:
["s", "rejected"]
["z", "complete"]
- Accepted (First Party Acceptance):
["s", "accepted"]
["z", "pending"]
- Accepted (Second Party Acceptance / Final Closure):
["s", "accepted"]
["z", "complete"]
- Responses SHOULD copy the listing tags over.
- Responses MUST set the recipient
"p"
tag to the quote event’s Author pubkey. - Responses MUST set the status
"s"
tag to ‘accepted’ or ‘rejected’. - Responses MUST set the status
"z"
tag to ‘pending’ if they are the first to accept, or ‘compplete’ if they reject, or ar accepting another accepted response. - Responses MUST be published to the listing’s
"r"
tag inbox relay URL, as well as the sender’s inbox relay.
Threads
Threads are chains of quote and response events tied to an underlying listing. This is done via computed “h” tags. The purpose fo the thread id is to carry a unique identifier for an underlying listing, and exchange between two parties through the quoting process. The thread id tag is defined below. Stable definition Givenevent_id
, pubkey1
, pubkey2
(all 64-hex, lowercase), where event_id is the underlying root event ID, pubkey1 and pubkey2 are the conversation pubkeys:
- Validate
event_id
is non-empty (and 64-hex if enforcing format). - Canonicalize order:
pk_lo = min(pubkey1, pubkey2)
;pk_hi = max(pubkey1, pubkey2)
(lexicographic). - Concatenate (no delimiter):
raw = event_id + pk_lo + pk_hi
. - Hash:
sha256(raw)
; encode hex lowercase →thread_id
.