package nip60 import ( "context" "encoding/json" "fmt" "strconv" "github.com/nbd-wtf/go-nostr" ) type HistoryEntry struct { In bool // in = received, out = sent Amount uint64 tokenEventIDs []string nutZaps []bool createdAt nostr.Timestamp event *nostr.Event } func (h HistoryEntry) toEvent(ctx context.Context, kr nostr.Keyer, walletId string, evt *nostr.Event) error { pk, err := kr.GetPublicKey(ctx) if err != nil { return err } dir := "in" if !h.In { dir = "out" } evt.CreatedAt = h.createdAt evt.Kind = 7376 evt.Tags = nostr.Tags{{"a", fmt.Sprintf("37375:%s:%s", pk, walletId)}} encryptedTags := nostr.Tags{ nostr.Tag{"direction", dir}, nostr.Tag{"amount", strconv.FormatUint(uint64(h.Amount), 10), "sat"}, } for i, tid := range h.tokenEventIDs { isNutZap := h.nutZaps[i] if h.In && isNutZap { evt.Tags = append(evt.Tags, nostr.Tag{"e", tid, "", "redeemed"}) continue } marker := "created" if !h.In { marker = "destroyed" } encryptedTags = append(encryptedTags, nostr.Tag{"e", tid, "", marker}) } jsonb, _ := json.Marshal(encryptedTags) evt.Content, err = kr.Encrypt( ctx, string(jsonb), pk, ) if err != nil { return err } err = kr.SignEvent(ctx, evt) if err != nil { return err } return nil } func (h *HistoryEntry) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error { h.event = evt h.createdAt = evt.CreatedAt pk, err := kr.GetPublicKey(ctx) if err != nil { return err } // event tags and encrypted tags are mixed together jsonb, err := kr.Decrypt(ctx, evt.Content, pk) if err != nil { return err } var tags nostr.Tags if len(jsonb) > 0 { tags = make(nostr.Tags, 0, 7) if err := json.Unmarshal([]byte(jsonb), &tags); err != nil { return err } tags = append(tags, evt.Tags...) } essential := 0 for _, tag := range tags { if len(tag) < 2 { continue } switch tag[0] { case "direction": essential++ if tag[1] == "in" { h.In = true } else if tag[1] == "out" { h.In = false } else { return fmt.Errorf("unexpected 'direction' tag %s", tag[1]) } case "amount": essential++ if len(tag) < 2 { return fmt.Errorf("'amount' tag must have at least 2 items") } if len(tag) >= 3 && tag[2] != "sat" { return fmt.Errorf("only 'sat' wallets are supported") } v, err := strconv.ParseUint(tag[1], 10, 64) if err != nil { return fmt.Errorf("invalid 'amount' %s: %w", tag[1], err) } h.Amount = v case "e": essential++ if len(tag) < 4 { return fmt.Errorf("'e' tag must have at least 4 items") } if !nostr.IsValid32ByteHex(tag[1]) { return fmt.Errorf("'e' tag has invalid event id %s", tag[1]) } h.tokenEventIDs = append(h.tokenEventIDs, tag[1]) switch tag[3] { case "created": h.nutZaps = append(h.nutZaps, false) case "destroyed": h.nutZaps = append(h.nutZaps, false) case "redeemed": h.nutZaps = append(h.nutZaps, true) } } } if essential < 3 { return fmt.Errorf("missing essential tags") } return nil }