package find import ( "fmt" "strconv" "time" "git.mleku.dev/mleku/nostr/encoders/event" "git.mleku.dev/mleku/nostr/encoders/tag" "git.mleku.dev/mleku/nostr/encoders/timestamp" "git.mleku.dev/mleku/nostr/interfaces/signer" ) // NewRegistrationProposal creates a new registration proposal event (kind 30100) func NewRegistrationProposal(name, action string, signer signer.I) (*event.E, error) { // Validate and normalize name name = NormalizeName(name) if err := ValidateName(name); err != nil { return nil, fmt.Errorf("invalid name: %w", err) } // Validate action if action != ActionRegister && action != ActionTransfer { return nil, fmt.Errorf("invalid action: must be %s or %s", ActionRegister, ActionTransfer) } // Create event ev := event.New() ev.Kind = KindRegistrationProposal ev.CreatedAt = timestamp.Now().V ev.Pubkey = signer.Pub() // Build tags tags := tag.NewS() tags.Append(tag.NewFromAny("d", name)) tags.Append(tag.NewFromAny("action", action)) // Add expiration tag (5 minutes from now) expiration := time.Now().Add(ProposalExpiry).Unix() tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10))) ev.Tags = tags ev.Content = []byte{} // Sign the event if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign event: %w", err) } return ev, nil } // NewRegistrationProposalWithTransfer creates a transfer proposal with previous owner signature func NewRegistrationProposalWithTransfer(name, prevOwner, prevSig string, signer signer.I) (*event.E, error) { // Create base proposal ev, err := NewRegistrationProposal(name, ActionTransfer, signer) if err != nil { return nil, err } // Add transfer-specific tags ev.Tags.Append(tag.NewFromAny("prev_owner", prevOwner)) ev.Tags.Append(tag.NewFromAny("prev_sig", prevSig)) // Re-sign after adding tags if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign transfer event: %w", err) } return ev, nil } // NewAttestation creates a new attestation event (kind 20100) func NewAttestation(proposalID, decision string, weight int, reason, serviceURL string, signer signer.I) (*event.E, error) { // Validate decision if decision != DecisionApprove && decision != DecisionReject && decision != DecisionAbstain { return nil, fmt.Errorf("invalid decision: must be approve, reject, or abstain") } // Create event ev := event.New() ev.Kind = KindAttestation ev.CreatedAt = timestamp.Now().V ev.Pubkey = signer.Pub() // Build tags tags := tag.NewS() tags.Append(tag.NewFromAny("e", proposalID)) tags.Append(tag.NewFromAny("decision", decision)) if weight > 0 { tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight))) } if reason != "" { tags.Append(tag.NewFromAny("reason", reason)) } if serviceURL != "" { tags.Append(tag.NewFromAny("service", serviceURL)) } // Add expiration tag (3 minutes from now) expiration := time.Now().Add(AttestationExpiry).Unix() tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10))) ev.Tags = tags ev.Content = []byte{} // Sign the event if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign attestation: %w", err) } return ev, nil } // NewTrustGraphEvent creates a new trust graph event (kind 30101) func NewTrustGraphEvent(entries []TrustEntry, signer signer.I) (*event.E, error) { // Validate trust entries for i, entry := range entries { if err := ValidateTrustScore(entry.TrustScore); err != nil { return nil, fmt.Errorf("invalid trust score at index %d: %w", i, err) } } // Create event ev := event.New() ev.Kind = KindTrustGraph ev.CreatedAt = timestamp.Now().V ev.Pubkey = signer.Pub() // Build tags tags := tag.NewS() tags.Append(tag.NewFromAny("d", "trust-graph")) // Add trust entries as p tags for _, entry := range entries { tags.Append(tag.NewFromAny("p", entry.Pubkey, entry.ServiceURL, strconv.FormatFloat(entry.TrustScore, 'f', 2, 64))) } // Add expiration tag (30 days from now) expiration := time.Now().Add(TrustGraphExpiry).Unix() tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10))) ev.Tags = tags ev.Content = []byte{} // Sign the event if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign trust graph: %w", err) } return ev, nil } // NewNameState creates a new name state event (kind 30102) func NewNameState(name, owner string, registeredAt time.Time, proposalID string, attestations int, confidence float64, signer signer.I) (*event.E, error) { // Validate name name = NormalizeName(name) if err := ValidateName(name); err != nil { return nil, fmt.Errorf("invalid name: %w", err) } // Create event ev := event.New() ev.Kind = KindNameState ev.CreatedAt = timestamp.Now().V ev.Pubkey = signer.Pub() // Build tags tags := tag.NewS() tags.Append(tag.NewFromAny("d", name)) tags.Append(tag.NewFromAny("owner", owner)) tags.Append(tag.NewFromAny("registered_at", strconv.FormatInt(registeredAt.Unix(), 10))) tags.Append(tag.NewFromAny("proposal", proposalID)) tags.Append(tag.NewFromAny("attestations", strconv.Itoa(attestations))) tags.Append(tag.NewFromAny("confidence", strconv.FormatFloat(confidence, 'f', 2, 64))) // Add expiration tag (1 year from registration) expiration := registeredAt.Add(NameRegistrationPeriod).Unix() tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10))) ev.Tags = tags ev.Content = []byte{} // Sign the event if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign name state: %w", err) } return ev, nil } // NewNameRecord creates a new name record event (kind 30103) func NewNameRecord(name, recordType, value string, ttl int, signer signer.I) (*event.E, error) { // Validate name name = NormalizeName(name) if err := ValidateName(name); err != nil { return nil, fmt.Errorf("invalid name: %w", err) } // Validate record value if err := ValidateRecordValue(recordType, value); err != nil { return nil, err } // Create event ev := event.New() ev.Kind = KindNameRecords ev.CreatedAt = timestamp.Now().V ev.Pubkey = signer.Pub() // Build tags tags := tag.NewS() tags.Append(tag.NewFromAny("d", fmt.Sprintf("%s:%s", name, recordType))) tags.Append(tag.NewFromAny("name", name)) tags.Append(tag.NewFromAny("type", recordType)) tags.Append(tag.NewFromAny("value", value)) if ttl > 0 { tags.Append(tag.NewFromAny("ttl", strconv.Itoa(ttl))) } ev.Tags = tags ev.Content = []byte{} // Sign the event if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign name record: %w", err) } return ev, nil } // NewNameRecordWithPriority creates a name record with priority (for MX, SRV) func NewNameRecordWithPriority(name, recordType, value string, ttl, priority int, signer signer.I) (*event.E, error) { // Validate priority if err := ValidatePriority(priority); err != nil { return nil, err } // Create base record ev, err := NewNameRecord(name, recordType, value, ttl, signer) if err != nil { return nil, err } // Add priority tag ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority))) // Re-sign if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign record with priority: %w", err) } return ev, nil } // NewSRVRecord creates an SRV record with all required fields func NewSRVRecord(name, value string, ttl, priority, weight, port int, signer signer.I) (*event.E, error) { // Validate SRV-specific fields if err := ValidatePriority(priority); err != nil { return nil, err } if err := ValidateWeight(weight); err != nil { return nil, err } if err := ValidatePort(port); err != nil { return nil, err } // Create base record ev, err := NewNameRecord(name, RecordTypeSRV, value, ttl, signer) if err != nil { return nil, err } // Add SRV-specific tags ev.Tags.Append(tag.NewFromAny("priority", strconv.Itoa(priority))) ev.Tags.Append(tag.NewFromAny("weight", strconv.Itoa(weight))) ev.Tags.Append(tag.NewFromAny("port", strconv.Itoa(port))) // Re-sign if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign SRV record: %w", err) } return ev, nil } // NewCertificate creates a new certificate event (kind 30104) func NewCertificate(name, certPubkey string, validFrom, validUntil time.Time, challenge, challengeProof string, witnesses []WitnessSignature, algorithm, usage string, signer signer.I) (*event.E, error) { // Validate name name = NormalizeName(name) if err := ValidateName(name); err != nil { return nil, fmt.Errorf("invalid name: %w", err) } // Create event ev := event.New() ev.Kind = KindCertificate ev.CreatedAt = timestamp.Now().V ev.Pubkey = signer.Pub() // Build tags tags := tag.NewS() tags.Append(tag.NewFromAny("d", name)) tags.Append(tag.NewFromAny("name", name)) tags.Append(tag.NewFromAny("cert_pubkey", certPubkey)) tags.Append(tag.NewFromAny("valid_from", strconv.FormatInt(validFrom.Unix(), 10))) tags.Append(tag.NewFromAny("valid_until", strconv.FormatInt(validUntil.Unix(), 10))) tags.Append(tag.NewFromAny("challenge", challenge)) tags.Append(tag.NewFromAny("challenge_proof", challengeProof)) // Add witness signatures for _, w := range witnesses { tags.Append(tag.NewFromAny("witness", w.Pubkey, w.Signature)) } ev.Tags = tags // Add metadata to content content := fmt.Sprintf(`{"algorithm":"%s","usage":"%s"}`, algorithm, usage) ev.Content = []byte(content) // Sign the event if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign certificate: %w", err) } return ev, nil } // NewWitnessService creates a new witness service info event (kind 30105) func NewWitnessService(endpoint string, challenges []string, maxValidity, fee int, reputationID, description, contact string, signer signer.I) (*event.E, error) { // Create event ev := event.New() ev.Kind = KindWitnessService ev.CreatedAt = timestamp.Now().V ev.Pubkey = signer.Pub() // Build tags tags := tag.NewS() tags.Append(tag.NewFromAny("d", "witness-service")) tags.Append(tag.NewFromAny("endpoint", endpoint)) for _, ch := range challenges { tags.Append(tag.NewFromAny("challenges", ch)) } if maxValidity > 0 { tags.Append(tag.NewFromAny("max_validity", strconv.Itoa(maxValidity))) } if fee > 0 { tags.Append(tag.NewFromAny("fee", strconv.Itoa(fee))) } if reputationID != "" { tags.Append(tag.NewFromAny("reputation", reputationID)) } // Add expiration tag (180 days from now) expiration := time.Now().Add(WitnessServiceExpiry).Unix() tags.Append(tag.NewFromAny("expiration", strconv.FormatInt(expiration, 10))) ev.Tags = tags // Add metadata to content content := fmt.Sprintf(`{"description":"%s","contact":"%s"}`, description, contact) ev.Content = []byte(content) // Sign the event if err := ev.Sign(signer); err != nil { return nil, fmt.Errorf("failed to sign witness service: %w", err) } return ev, nil }