builder.go raw
1 package find
2
3 import (
4 "fmt"
5 "strconv"
6 "time"
7
8 "next.orly.dev/pkg/nostr/encoders/event"
9 "next.orly.dev/pkg/nostr/encoders/tag"
10 "next.orly.dev/pkg/nostr/encoders/timestamp"
11 "next.orly.dev/pkg/nostr/interfaces/signer"
12 )
13
14 // NewRegistrationProposal creates a new registration proposal event (kind 30100)
15 func NewRegistrationProposal(name, action string, signer signer.I) (*event.E, error) {
16 // Validate and normalize name
17 name = NormalizeName(name)
18 if err := ValidateName(name); err != nil {
19 return nil, fmt.Errorf("invalid name: %w", err)
20 }
21
22 // Validate action
23 if action != ActionRegister && action != ActionTransfer {
24 return nil, fmt.Errorf("invalid action: must be %s or %s", ActionRegister, ActionTransfer)
25 }
26
27 // Create event
28 ev := event.New()
29 ev.Kind = KindRegistrationProposal
30 ev.CreatedAt = timestamp.Now().V
31 ev.Pubkey = signer.Pub()
32
33 // Build tags
34 tags := tag.NewS()
35 tags.Append(tag.NewFromAny("d", name))
36 tags.Append(tag.NewFromAny("action", action))
37
38 // Add expiration tag (5 minutes from now)
39 expiration := time.Now().Add(ProposalExpiry).Unix()
40 tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
41
42 ev.Tags = tags
43 ev.Content = []byte{}
44
45 // Sign the event
46 if err := ev.Sign(signer); err != nil {
47 return nil, fmt.Errorf("failed to sign event: %w", err)
48 }
49
50 return ev, nil
51 }
52
53 // NewRegistrationProposalWithTransfer creates a transfer proposal with previous owner signature
54 func NewRegistrationProposalWithTransfer(name, prevOwner, prevSig string, signer signer.I) (*event.E, error) {
55 // Create base proposal
56 ev, err := NewRegistrationProposal(name, ActionTransfer, signer)
57 if err != nil {
58 return nil, err
59 }
60
61 // Add transfer-specific tags
62 ev.Tags.Append(tag.NewFromAny("prev_owner", prevOwner))
63 ev.Tags.Append(tag.NewFromAny("prev_sig", prevSig))
64
65 // Re-sign after adding tags
66 if err := ev.Sign(signer); err != nil {
67 return nil, fmt.Errorf("failed to sign transfer event: %w", err)
68 }
69
70 return ev, nil
71 }
72
73 // NewAttestation creates a new attestation event (kind 20100)
74 func NewAttestation(proposalID, decision string, weight int, reason, serviceURL string, signer signer.I) (*event.E, error) {
75 // Validate decision
76 if decision != DecisionApprove && decision != DecisionReject && decision != DecisionAbstain {
77 return nil, fmt.Errorf("invalid decision: must be approve, reject, or abstain")
78 }
79
80 // Create event
81 ev := event.New()
82 ev.Kind = KindAttestation
83 ev.CreatedAt = timestamp.Now().V
84 ev.Pubkey = signer.Pub()
85
86 // Build tags
87 tags := tag.NewS()
88 tags.Append(tag.NewFromAny("e", proposalID))
89 tags.Append(tag.NewFromAny("decision", decision))
90
91 if weight > 0 {
92 tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight)))
93 }
94
95 if reason != "" {
96 tags.Append(tag.NewFromAny("reason", reason))
97 }
98
99 if serviceURL != "" {
100 tags.Append(tag.NewFromAny("service", serviceURL))
101 }
102
103 // Add expiration tag (3 minutes from now)
104 expiration := time.Now().Add(AttestationExpiry).Unix()
105 tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
106
107 ev.Tags = tags
108 ev.Content = []byte{}
109
110 // Sign the event
111 if err := ev.Sign(signer); err != nil {
112 return nil, fmt.Errorf("failed to sign attestation: %w", err)
113 }
114
115 return ev, nil
116 }
117
118 // NewTrustGraphEvent creates a new trust graph event (kind 30101)
119 func NewTrustGraphEvent(entries []TrustEntry, signer signer.I) (*event.E, error) {
120 // Validate trust entries
121 for i, entry := range entries {
122 if err := ValidateTrustScore(entry.TrustScore); err != nil {
123 return nil, fmt.Errorf("invalid trust score at index %d: %w", i, err)
124 }
125 }
126
127 // Create event
128 ev := event.New()
129 ev.Kind = KindTrustGraph
130 ev.CreatedAt = timestamp.Now().V
131 ev.Pubkey = signer.Pub()
132
133 // Build tags
134 tags := tag.NewS()
135 tags.Append(tag.NewFromAny("d", "trust-graph"))
136
137 // Add trust entries as p tags
138 for _, entry := range entries {
139 tags.Append(tag.NewFromAny("p", entry.Pubkey, entry.ServiceURL,
140 strconv.FormatFloat(entry.TrustScore, 'f', 2, 64)))
141 }
142
143 // Add expiration tag (30 days from now)
144 expiration := time.Now().Add(TrustGraphExpiry).Unix()
145 tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
146
147 ev.Tags = tags
148 ev.Content = []byte{}
149
150 // Sign the event
151 if err := ev.Sign(signer); err != nil {
152 return nil, fmt.Errorf("failed to sign trust graph: %w", err)
153 }
154
155 return ev, nil
156 }
157
158 // NewNameState creates a new name state event (kind 30102)
159 func NewNameState(name, owner string, registeredAt time.Time, proposalID string,
160 attestations int, confidence float64, signer signer.I) (*event.E, error) {
161
162 // Validate name
163 name = NormalizeName(name)
164 if err := ValidateName(name); err != nil {
165 return nil, fmt.Errorf("invalid name: %w", err)
166 }
167
168 // Create event
169 ev := event.New()
170 ev.Kind = KindNameState
171 ev.CreatedAt = timestamp.Now().V
172 ev.Pubkey = signer.Pub()
173
174 // Build tags
175 tags := tag.NewS()
176 tags.Append(tag.NewFromAny("d", name))
177 tags.Append(tag.NewFromAny("owner", owner))
178 tags.Append(tag.NewFromAny("registered_at", strconv.FormatInt(registeredAt.Unix(), 10)))
179 tags.Append(tag.NewFromAny("proposal", proposalID))
180 tags.Append(tag.NewFromAny("attestations", strconv.Itoa(attestations)))
181 tags.Append(tag.NewFromAny("confidence", strconv.FormatFloat(confidence, 'f', 2, 64)))
182
183 // Add expiration tag (1 year from registration)
184 expiration := registeredAt.Add(NameRegistrationPeriod).Unix()
185 tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
186
187 ev.Tags = tags
188 ev.Content = []byte{}
189
190 // Sign the event
191 if err := ev.Sign(signer); err != nil {
192 return nil, fmt.Errorf("failed to sign name state: %w", err)
193 }
194
195 return ev, nil
196 }
197
198 // NewNameRecord creates a new name record event (kind 30103)
199 func NewNameRecord(name, recordType, value string, ttl int, signer signer.I) (*event.E, error) {
200 // Validate name
201 name = NormalizeName(name)
202 if err := ValidateName(name); err != nil {
203 return nil, fmt.Errorf("invalid name: %w", err)
204 }
205
206 // Validate record value
207 if err := ValidateRecordValue(recordType, value); err != nil {
208 return nil, err
209 }
210
211 // Create event
212 ev := event.New()
213 ev.Kind = KindNameRecords
214 ev.CreatedAt = timestamp.Now().V
215 ev.Pubkey = signer.Pub()
216
217 // Build tags
218 tags := tag.NewS()
219 tags.Append(tag.NewFromAny("d", fmt.Sprintf("%s:%s", name, recordType)))
220 tags.Append(tag.NewFromAny("name", name))
221 tags.Append(tag.NewFromAny("type", recordType))
222 tags.Append(tag.NewFromAny("value", value))
223
224 if ttl > 0 {
225 tags.Append(tag.NewFromAny("ttl", strconv.Itoa(ttl)))
226 }
227
228 ev.Tags = tags
229 ev.Content = []byte{}
230
231 // Sign the event
232 if err := ev.Sign(signer); err != nil {
233 return nil, fmt.Errorf("failed to sign name record: %w", err)
234 }
235
236 return ev, nil
237 }
238
239 // NewNameRecordWithPriority creates a name record with priority (for MX, SRV)
240 func NewNameRecordWithPriority(name, recordType, value string, ttl, priority int, signer signer.I) (*event.E, error) {
241 // Validate priority
242 if err := ValidatePriority(priority); err != nil {
243 return nil, err
244 }
245
246 // Create base record
247 ev, err := NewNameRecord(name, recordType, value, ttl, signer)
248 if err != nil {
249 return nil, err
250 }
251
252 // Add priority tag
253 ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority)))
254
255 // Re-sign
256 if err := ev.Sign(signer); err != nil {
257 return nil, fmt.Errorf("failed to sign record with priority: %w", err)
258 }
259
260 return ev, nil
261 }
262
263 // NewSRVRecord creates an SRV record with all required fields
264 func NewSRVRecord(name, value string, ttl, priority, weight, port int, signer signer.I) (*event.E, error) {
265 // Validate SRV-specific fields
266 if err := ValidatePriority(priority); err != nil {
267 return nil, err
268 }
269 if err := ValidateWeight(weight); err != nil {
270 return nil, err
271 }
272 if err := ValidatePort(port); err != nil {
273 return nil, err
274 }
275
276 // Create base record
277 ev, err := NewNameRecord(name, RecordTypeSRV, value, ttl, signer)
278 if err != nil {
279 return nil, err
280 }
281
282 // Add SRV-specific tags
283 ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority)))
284 ev.Tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight)))
285 ev.Tags.Append(tag.NewFromAny("port", strconv.Itoa(port)))
286
287 // Re-sign
288 if err := ev.Sign(signer); err != nil {
289 return nil, fmt.Errorf("failed to sign SRV record: %w", err)
290 }
291
292 return ev, nil
293 }
294
295 // NewCertificate creates a new certificate event (kind 30104)
296 func NewCertificate(name, certPubkey string, validFrom, validUntil time.Time,
297 challenge, challengeProof string, witnesses []WitnessSignature,
298 algorithm, usage string, signer signer.I) (*event.E, error) {
299
300 // Validate name
301 name = NormalizeName(name)
302 if err := ValidateName(name); err != nil {
303 return nil, fmt.Errorf("invalid name: %w", err)
304 }
305
306 // Create event
307 ev := event.New()
308 ev.Kind = KindCertificate
309 ev.CreatedAt = timestamp.Now().V
310 ev.Pubkey = signer.Pub()
311
312 // Build tags
313 tags := tag.NewS()
314 tags.Append(tag.NewFromAny("d", name))
315 tags.Append(tag.NewFromAny("name", name))
316 tags.Append(tag.NewFromAny("cert_pubkey", certPubkey))
317 tags.Append(tag.NewFromAny("valid_from", strconv.FormatInt(validFrom.Unix(), 10)))
318 tags.Append(tag.NewFromAny("valid_until", strconv.FormatInt(validUntil.Unix(), 10)))
319 tags.Append(tag.NewFromAny("challenge", challenge))
320 tags.Append(tag.NewFromAny("challenge_proof", challengeProof))
321
322 // Add witness signatures
323 for _, w := range witnesses {
324 tags.Append(tag.NewFromAny("witness", w.Pubkey, w.Signature))
325 }
326
327 ev.Tags = tags
328
329 // Add metadata to content
330 content := fmt.Sprintf(`{"algorithm":"%s","usage":"%s"}`, algorithm, usage)
331 ev.Content = []byte(content)
332
333 // Sign the event
334 if err := ev.Sign(signer); err != nil {
335 return nil, fmt.Errorf("failed to sign certificate: %w", err)
336 }
337
338 return ev, nil
339 }
340
341 // NewWitnessService creates a new witness service info event (kind 30105)
342 func NewWitnessService(endpoint string, challenges []string, maxValidity, fee int,
343 reputationID, description, contact string, signer signer.I) (*event.E, error) {
344
345 // Create event
346 ev := event.New()
347 ev.Kind = KindWitnessService
348 ev.CreatedAt = timestamp.Now().V
349 ev.Pubkey = signer.Pub()
350
351 // Build tags
352 tags := tag.NewS()
353 tags.Append(tag.NewFromAny("d", "witness-service"))
354 tags.Append(tag.NewFromAny("endpoint", endpoint))
355
356 for _, ch := range challenges {
357 tags.Append(tag.NewFromAny("challenges", ch))
358 }
359
360 if maxValidity > 0 {
361 tags.Append(tag.NewFromAny("max_validity", strconv.Itoa(maxValidity)))
362 }
363
364 if fee > 0 {
365 tags.Append(tag.NewFromAny("fee", strconv.Itoa(fee)))
366 }
367
368 if reputationID != "" {
369 tags.Append(tag.NewFromAny("reputation", reputationID))
370 }
371
372 // Add expiration tag (180 days from now)
373 expiration := time.Now().Add(WitnessServiceExpiry).Unix()
374 tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10)))
375
376 ev.Tags = tags
377
378 // Add metadata to content
379 content := fmt.Sprintf(`{"description":"%s","contact":"%s"}`, description, contact)
380 ev.Content = []byte(content)
381
382 // Sign the event
383 if err := ev.Sign(signer); err != nil {
384 return nil, fmt.Errorf("failed to sign witness service: %w", err)
385 }
386
387 return ev, nil
388 }
389