1 // Package musiquay implements the Musiquay protocol vocabulary: the event
2 // kinds, tag names, addresses and payload structures that the relay and the
3 // web app both encode into.
4 //
5 // Every structure here is a payload, not an event. A Track is the tag list and
6 // content of a kind 32210 event; the envelope (id, pubkey, created_at, sig) is
7 // the nostr layer's job. That split is what lets one vocabulary serve the
8 // native relay (pkg/event.E) and the wasm client (pkg/core.Event).
9 //
10 // The vocabulary comes from the spec in the musiquay repo's docs/:
11 // FEATURES_MUSIC.md, FEATURES_CREDITS.md, FEATURES_ROSTER.md,
12 // FEATURES_CALENDAR.md, FEATURES_ACCESS.md, FEATURES_MERCHANDISE.md,
13 // FEATURES_MARKETPLACE.md, PROTOCOL_FLOWS.md and the kind registry in
14 // ARCHITECTURE.md. Tag names and value tokens are spelled once, here, as
15 // constants: the docs are prose and examples, and drift between two
16 // independent spellings of "mastering-engineer" is a silent data bug.
17 //
18 // Where a doc leaves something open, the code says so at the point it matters
19 // instead of inventing a rule. Unknown tags survive a parse in Extra and are
20 // re-emitted, so a payload this version does not understand still round-trips.
21 package protocol
22 23 // Musiquay's own kinds. The addressable block is 32210-32217; 3221, 3222 and
24 // 32218-32220 are regular (immutable) because they are stored and counted.
25 const (
26 KindTrack uint16 = 32210
27 KindAlbum uint16 = 32211
28 KindPlaylist uint16 = 32212
29 KindSetlist uint16 = 32213
30 KindReleaseCredits uint16 = 32214
31 KindTrackCredits uint16 = 32215
32 KindEdition uint16 = 32216
33 KindAccessFilter uint16 = 32217
34 35 KindPlayReport uint16 = 3221
36 KindPurchaseOrder uint16 = 3222
37 KindDeliveryReceipt uint16 = 32218
38 KindHostAggregate uint16 = 32219
39 KindPublisherRollup uint16 = 32220
40 )
41 42 // Kinds the vocabulary reuses rather than defining.
43 const (
44 KindCreditLabel uint16 = 1985 // NIP-32 label: confirmation, attestation
45 KindComment uint16 = 1111 // NIP-22 comment: wiki contribution
46 KindCalendarDay uint16 = 31922 // NIP-52 date-based event
47 KindCalendar uint16 = 31923 // NIP-52 time-based event, and the roster
48 KindCalendarSet uint16 = 31924 // NIP-52 calendar collection
49 KindRSVP uint16 = 31925 // NIP-52 RSVP, and the waitlist entry
50 KindStall uint16 = 30017 // NIP-15 stall
51 KindProduct uint16 = 30018 // NIP-15 product
52 KindListing uint16 = 30402 // NIP-99 active classified listing
53 KindListingOff uint16 = 30403 // NIP-99 draft or closed listing
54 KindMusicStatus uint16 = 30315 // NIP-38 live now-playing, d = music
55 KindBlobAuth uint16 = 24242 // BUD-11 Blossom authorization token
56 KindBlobServers uint16 = 10063 // NIP-B7 Blossom server list
57 KindZapRequest uint16 = 9734 // NIP-57 zap request, carries the asset a tag
58 KindZapReceipt uint16 = 9735 // NIP-57 zap receipt, the Lightning proof
59 KindSeal uint16 = 13 // NIP-59 seal
60 KindGiftWrap uint16 = 1059 // NIP-59 gift wrap
61 )
62 63 // KindNames is the registry, in kind order. It is the single source of truth
64 // for the names used in logs, error messages and relay NIP-11 filtering.
65 func KindName(k uint16) (s string) {
66 switch k {
67 case KindTrack:
68 return "track"
69 case KindAlbum:
70 return "album"
71 case KindPlaylist:
72 return "playlist"
73 case KindSetlist:
74 return "setlist"
75 case KindReleaseCredits:
76 return "release-credits"
77 case KindTrackCredits:
78 return "track-credits"
79 case KindEdition:
80 return "edition"
81 case KindAccessFilter:
82 return "access-filter"
83 case KindPlayReport:
84 return "play-report"
85 case KindPurchaseOrder:
86 return "purchase-order"
87 case KindDeliveryReceipt:
88 return "delivery-receipt"
89 case KindHostAggregate:
90 return "host-aggregate"
91 case KindPublisherRollup:
92 return "publisher-rollup"
93 case KindCreditLabel:
94 return "credit-label"
95 case KindComment:
96 return "comment"
97 case KindCalendarDay:
98 return "calendar-day"
99 case KindCalendar:
100 return "calendar"
101 case KindCalendarSet:
102 return "calendar-set"
103 case KindRSVP:
104 return "rsvp"
105 case KindStall:
106 return "stall"
107 case KindProduct:
108 return "product"
109 case KindListing:
110 return "listing"
111 case KindListingOff:
112 return "listing-off"
113 case KindMusicStatus:
114 return "music-status"
115 case KindBlobAuth:
116 return "blob-auth"
117 case KindBlobServers:
118 return "blob-servers"
119 case KindZapRequest:
120 return "zap-request"
121 case KindZapReceipt:
122 return "zap-receipt"
123 case KindSeal:
124 return "seal"
125 case KindGiftWrap:
126 return "gift-wrap"
127 }
128 return "unknown"
129 }
130 131 // IsMusiquay reports whether k is defined by this protocol, as opposed to a
132 // kind it reuses from a NIP.
133 func IsMusiquay(k uint16) (ok bool) {
134 switch k {
135 case KindTrack, KindAlbum, KindPlaylist, KindSetlist,
136 KindReleaseCredits, KindTrackCredits, KindEdition, KindAccessFilter,
137 KindPlayReport, KindPurchaseOrder, KindDeliveryReceipt,
138 KindHostAggregate, KindPublisherRollup:
139 return true
140 }
141 return false
142 }
143 144 // IsAddressable reports whether a Musiquay kind is parameterized replaceable
145 // (NIP-01 30000-39999 with a d tag). References to these use a tags: the event
146 // id changes on every update, the address does not.
147 func IsAddressable(k uint16) (ok bool) {
148 switch k {
149 case KindTrack, KindAlbum, KindPlaylist, KindSetlist,
150 KindReleaseCredits, KindTrackCredits, KindEdition, KindAccessFilter:
151 return true
152 }
153 return false
154 }
155 156 // IsRegular reports whether a Musiquay kind is immutable. Play reports, orders,
157 // receipts and the aggregates are stored and counted, so they can never be
158 // replaceable: a play count is a count of events.
159 func IsRegular(k uint16) (ok bool) {
160 switch k {
161 case KindPlayReport, KindPurchaseOrder, KindDeliveryReceipt,
162 KindHostAggregate, KindPublisherRollup:
163 return true
164 }
165 return false
166 }
167 168 // IsPrivate reports whether a kind is never published to a relay as-is: the
169 // purchase order and the delivery receipt travel only inside NIP-59 wraps.
170 func IsPrivate(k uint16) (ok bool) {
171 switch k {
172 case KindPurchaseOrder, KindDeliveryReceipt:
173 return true
174 }
175 return false
176 }
177 178 // InReplaceableRange reports whether NIP-01's kind-range rule would treat k as
179 // parameterized replaceable (30000 <= kind < 40000), whatever the spec intends.
180 //
181 // This is a hazard the registry carries: the delivery receipt (32218), host
182 // aggregate (32219) and publisher rollup (32220) are regular, counted events
183 // that sit inside the replaceable range. A relay that applies the range rule
184 // without an exception keeps only one event per (kind, host, empty d) and
185 // silently drops every other receipt. Storage policy must ask IsRegular first,
186 // and the spec should move those three out of the range before shipping.
187 func InReplaceableRange(k uint16) (ok bool) {
188 return k >= 30000 && k < 40000
189 }
190 191 // RegularInReplaceableRange lists the Musiquay kinds that are regular yet sit
192 // in NIP-01's replaceable range. Storage policy has to special-case exactly
193 // these; nothing else in the vocabulary collides.
194 func RegularInReplaceableRange() (ks []uint16) {
195 ks = []uint16{:0:3}
196 ks = push(ks, KindDeliveryReceipt)
197 ks = push(ks, KindHostAggregate)
198 ks = push(ks, KindPublisherRollup)
199 return
200 }
201