mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-11-15 16:50:16 +01:00
implement nip60 events.
This commit is contained in:
153
nip60/history.go
Normal file
153
nip60/history.go
Normal file
@@ -0,0 +1,153 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type HistoryEntry struct {
|
||||
In bool // in = received, out = sent
|
||||
Amount uint32
|
||||
|
||||
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) < 3 {
|
||||
return fmt.Errorf("'amount' tag must have at least 3 items")
|
||||
}
|
||||
if tag[2] != "sat" {
|
||||
return fmt.Errorf("only 'sat' wallets are supported")
|
||||
}
|
||||
v, err := strconv.ParseUint(tag[1], 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid 'amount' %s: %w", tag[1], err)
|
||||
}
|
||||
h.Amount = uint32(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
|
||||
}
|
||||
127
nip60/lib.go
Normal file
127
nip60/lib.go
Normal file
@@ -0,0 +1,127 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type WalletStash struct {
|
||||
sync.Mutex
|
||||
wallets map[string]*Wallet
|
||||
|
||||
pendingTokens map[string][]Token // tokens not yet assigned to a wallet
|
||||
pendingHistory map[string][]HistoryEntry // history entries not yet assigned to a wallet
|
||||
}
|
||||
|
||||
func LoadWallets(
|
||||
ctx context.Context,
|
||||
kr nostr.Keyer,
|
||||
events <-chan *nostr.Event,
|
||||
errors chan<- error,
|
||||
) *WalletStash {
|
||||
wl := &WalletStash{
|
||||
wallets: make(map[string]*Wallet, 1),
|
||||
pendingTokens: make(map[string][]Token),
|
||||
pendingHistory: make(map[string][]HistoryEntry),
|
||||
}
|
||||
|
||||
go func() {
|
||||
for evt := range events {
|
||||
wl.Lock()
|
||||
switch evt.Kind {
|
||||
case 37375:
|
||||
wallet := &Wallet{}
|
||||
if err := wallet.parse(ctx, kr, evt); err != nil {
|
||||
wl.Unlock()
|
||||
if errors != nil {
|
||||
errors <- fmt.Errorf("event %s failed: %w", evt, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
for _, he := range wl.pendingHistory[wallet.Identifier] {
|
||||
wallet.History = append(wallet.History, he)
|
||||
}
|
||||
for _, token := range wl.pendingTokens[wallet.Identifier] {
|
||||
wallet.Tokens = append(wallet.Tokens, token)
|
||||
}
|
||||
|
||||
wl.wallets[wallet.Identifier] = wallet
|
||||
|
||||
case 7375: // token
|
||||
ref := evt.Tags.GetFirst([]string{"a", ""})
|
||||
if ref == nil {
|
||||
wl.Unlock()
|
||||
if errors != nil {
|
||||
errors <- fmt.Errorf("event %s missing 'a' tag", evt)
|
||||
}
|
||||
continue
|
||||
}
|
||||
spl := strings.SplitN((*ref)[1], ":", 3)
|
||||
if len(spl) < 3 {
|
||||
wl.Unlock()
|
||||
if errors != nil {
|
||||
errors <- fmt.Errorf("event %s invalid 'a' tag", evt)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
token := Token{}
|
||||
if err := token.parse(ctx, kr, evt); err != nil {
|
||||
wl.Unlock()
|
||||
if errors != nil {
|
||||
errors <- fmt.Errorf("event %s failed: %w", evt, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if wallet, ok := wl.wallets[spl[2]]; ok {
|
||||
wallet.Tokens = append(wallet.Tokens, token)
|
||||
} else {
|
||||
wl.pendingTokens[spl[2]] = append(wl.pendingTokens[spl[2]], token)
|
||||
}
|
||||
|
||||
case 7376: // history
|
||||
ref := evt.Tags.GetFirst([]string{"a", ""})
|
||||
if ref == nil {
|
||||
wl.Unlock()
|
||||
if errors != nil {
|
||||
errors <- fmt.Errorf("event %s missing 'a' tag", evt)
|
||||
}
|
||||
continue
|
||||
}
|
||||
spl := strings.SplitN((*ref)[1], ":", 3)
|
||||
if len(spl) < 3 {
|
||||
wl.Unlock()
|
||||
if errors != nil {
|
||||
errors <- fmt.Errorf("event %s invalid 'a' tag", evt)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
he := HistoryEntry{}
|
||||
if err := he.parse(ctx, kr, evt); err != nil {
|
||||
wl.Unlock()
|
||||
if errors != nil {
|
||||
errors <- fmt.Errorf("event %s failed: %w", evt, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if wallet, ok := wl.wallets[spl[2]]; ok {
|
||||
wallet.History = append(wallet.History, he)
|
||||
} else {
|
||||
wl.pendingHistory[spl[2]] = append(wl.pendingHistory[spl[2]], he)
|
||||
}
|
||||
}
|
||||
|
||||
wl.Unlock()
|
||||
}
|
||||
}()
|
||||
|
||||
return wl
|
||||
}
|
||||
161
nip60/nip60_test.go
Normal file
161
nip60/nip60_test.go
Normal file
@@ -0,0 +1,161 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
"github.com/nbd-wtf/go-nostr/keyer"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestWalletRoundtrip(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
kr, err := keyer.NewPlainKeySigner("94b46586f475bbc92746cb8f14d59b083047ac3ab747774b066d17673c1cc527")
|
||||
require.NoError(t, err)
|
||||
|
||||
// create initial wallets with arbitrary data
|
||||
wallet1 := Wallet{
|
||||
Identifier: "wallet1",
|
||||
Name: "My First Wallet",
|
||||
Description: "Test wallet number one",
|
||||
PrivateKey: "secret123",
|
||||
Relays: []string{"wss://relay1.example.com", "wss://relay2.example.com"},
|
||||
Mints: []string{"https://mint1.example.com"},
|
||||
Tokens: []Token{
|
||||
{
|
||||
Mint: "https://mint1.example.com",
|
||||
Proofs: []Proof{
|
||||
{ID: "proof1", Amount: 100, Secret: "secret1", C: "c1"},
|
||||
{ID: "proof2", Amount: 200, Secret: "secret2", C: "c2"},
|
||||
},
|
||||
mintedAt: nostr.Now(),
|
||||
},
|
||||
{
|
||||
Mint: "https://mint2.example.com",
|
||||
Proofs: []Proof{
|
||||
{ID: "proof3", Amount: 500, Secret: "secret3", C: "c3"},
|
||||
},
|
||||
mintedAt: nostr.Now(),
|
||||
},
|
||||
},
|
||||
History: []HistoryEntry{
|
||||
{
|
||||
In: true,
|
||||
Amount: 300,
|
||||
tokenEventIDs: []string{
|
||||
"559cecf5aba6ab825347bedfd56ff603a2c6aa7c8d88790ca1e232759699bbc7",
|
||||
"8f2c40b064e3e601d070362f53ace6fe124992da8a7322357c0868f22f6c2350",
|
||||
},
|
||||
nutZaps: []bool{false, false},
|
||||
createdAt: nostr.Now(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
wallet2 := Wallet{
|
||||
Identifier: "wallet2",
|
||||
Name: "Second Wallet",
|
||||
Description: "Test wallet number two",
|
||||
PrivateKey: "secret456",
|
||||
Relays: []string{"wss://relay3.example.com"},
|
||||
Mints: []string{"https://mint2.example.com"},
|
||||
Tokens: []Token{
|
||||
{
|
||||
Mint: "https://mint2.example.com",
|
||||
Proofs: []Proof{
|
||||
{ID: "proof3", Amount: 500, Secret: "secret3", C: "c3"},
|
||||
},
|
||||
mintedAt: nostr.Now(),
|
||||
},
|
||||
},
|
||||
History: []HistoryEntry{
|
||||
{
|
||||
In: false,
|
||||
Amount: 200,
|
||||
tokenEventIDs: []string{
|
||||
"cc9dd6298ae7e1ae0866448f11fed1c3a818b7db837caf8d5c48e496200477fe",
|
||||
},
|
||||
nutZaps: []bool{false},
|
||||
createdAt: nostr.Now(),
|
||||
},
|
||||
{
|
||||
In: true,
|
||||
Amount: 300,
|
||||
tokenEventIDs: []string{
|
||||
"63e8ff4ca4f16d6edc0c93dd1659cc8029178560aef2c9a00ca323738ed680e3",
|
||||
"3898e1c01fd6043dd46b819ce6a940867ccc116bc7c733124d2c0658fb1d569e",
|
||||
},
|
||||
nutZaps: []bool{false, false},
|
||||
createdAt: nostr.Now(),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// convert wallets to events
|
||||
events1, err := wallet1.ToPublishableEvents(ctx, kr, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
events2, err := wallet2.ToPublishableEvents(ctx, kr, false)
|
||||
require.NoError(t, err)
|
||||
|
||||
// combine all events
|
||||
allEvents := append(events1, events2...)
|
||||
require.Len(t, allEvents, 8)
|
||||
|
||||
// make a derived shuffled version
|
||||
reversedAllEvents := make([]nostr.Event, len(allEvents))
|
||||
for i, evt := range allEvents {
|
||||
reversedAllEvents[len(allEvents)-1-i] = evt
|
||||
}
|
||||
|
||||
for _, allEvents := range [][]nostr.Event{allEvents, reversedAllEvents} {
|
||||
// create channel and feed events into it
|
||||
eventChan := make(chan *nostr.Event)
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
for _, evt := range allEvents {
|
||||
eventChan <- &evt
|
||||
}
|
||||
close(eventChan)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
// load wallets from events
|
||||
errorChan := make(chan error)
|
||||
walletStash := LoadWallets(ctx, kr, eventChan, errorChan)
|
||||
|
||||
var errorChanErr error
|
||||
go func() {
|
||||
for {
|
||||
errorChanErr = <-errorChan
|
||||
fmt.Println(errorChanErr)
|
||||
}
|
||||
}()
|
||||
|
||||
<-done
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
require.NoError(t, errorChanErr, "errorChan shouldn't have received any errors: %w", errorChanErr)
|
||||
|
||||
// compare loaded wallets with original ones
|
||||
loadedWallet1 := walletStash.wallets[wallet1.Identifier]
|
||||
require.Equal(t, wallet1.Name, loadedWallet1.Name)
|
||||
require.Equal(t, wallet1.Description, loadedWallet1.Description)
|
||||
require.Equal(t, wallet1.PrivateKey, loadedWallet1.PrivateKey)
|
||||
require.Len(t, loadedWallet1.Tokens, len(wallet1.Tokens))
|
||||
require.Len(t, loadedWallet1.History, len(wallet1.History))
|
||||
|
||||
loadedWallet2 := walletStash.wallets[wallet2.Identifier]
|
||||
require.Equal(t, wallet2.Name, loadedWallet2.Name)
|
||||
require.Equal(t, wallet2.Description, loadedWallet2.Description)
|
||||
require.Equal(t, wallet2.PrivateKey, loadedWallet2.PrivateKey)
|
||||
require.Len(t, loadedWallet2.Tokens, len(wallet2.Tokens))
|
||||
require.Len(t, loadedWallet2.History, len(wallet2.History))
|
||||
|
||||
// check token amounts
|
||||
require.Equal(t, wallet1.Balance(), loadedWallet1.Balance())
|
||||
require.Equal(t, wallet2.Balance(), loadedWallet2.Balance())
|
||||
}
|
||||
}
|
||||
81
nip60/token.go
Normal file
81
nip60/token.go
Normal file
@@ -0,0 +1,81 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Mint string `json:"mint"`
|
||||
Proofs []Proof `json:"proofs"`
|
||||
|
||||
mintedAt nostr.Timestamp
|
||||
event *nostr.Event
|
||||
}
|
||||
|
||||
type Proof struct {
|
||||
ID string `json:"id"`
|
||||
Amount uint32 `json:"amount"`
|
||||
Secret string `json:"secret"`
|
||||
C string `json:"C"`
|
||||
}
|
||||
|
||||
func (t Token) Amount() uint32 {
|
||||
var sum uint32
|
||||
for _, p := range t.Proofs {
|
||||
sum += p.Amount
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (t Token) toEvent(ctx context.Context, kr nostr.Keyer, walletId string, evt *nostr.Event) error {
|
||||
pk, err := kr.GetPublicKey(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
evt.CreatedAt = t.mintedAt
|
||||
evt.Kind = 7375
|
||||
evt.Tags = nostr.Tags{{"a", fmt.Sprintf("37375:%s:%s", pk, walletId)}}
|
||||
|
||||
content, _ := json.Marshal(t)
|
||||
evt.Content, err = kr.Encrypt(
|
||||
ctx,
|
||||
string(content),
|
||||
pk,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = kr.SignEvent(ctx, evt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *Token) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
|
||||
t.event = evt
|
||||
t.mintedAt = evt.CreatedAt
|
||||
|
||||
pk, err := kr.GetPublicKey(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
content, err := kr.Decrypt(ctx, evt.Content, pk)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(content), t); err != nil {
|
||||
return fmt.Errorf("failed to parse token content: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
176
nip60/wallet.go
Normal file
176
nip60/wallet.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/nbd-wtf/go-nostr"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
Identifier string
|
||||
Description string
|
||||
Name string
|
||||
PrivateKey string
|
||||
Relays []string
|
||||
Mints []string
|
||||
Tokens []Token
|
||||
History []HistoryEntry
|
||||
|
||||
temporaryBalance uint32
|
||||
}
|
||||
|
||||
func (w Wallet) Balance() uint32 {
|
||||
var sum uint32
|
||||
for _, token := range w.Tokens {
|
||||
sum += token.Amount()
|
||||
}
|
||||
return sum
|
||||
}
|
||||
|
||||
func (w Wallet) ToPublishableEvents(ctx context.Context, kr nostr.Keyer, skipExisting bool) ([]nostr.Event, error) {
|
||||
evt := nostr.Event{
|
||||
CreatedAt: nostr.Now(),
|
||||
Kind: 37375,
|
||||
Tags: make(nostr.Tags, 0, 7),
|
||||
}
|
||||
|
||||
pk, err := kr.GetPublicKey(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
evt.Content, err = kr.Encrypt(
|
||||
ctx,
|
||||
fmt.Sprintf(`[["balance","%d","sat"],["privkey","%s"]]`, w.Balance(), w.PrivateKey),
|
||||
pk,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
evt.Tags = append(evt.Tags,
|
||||
nostr.Tag{"d", w.Identifier},
|
||||
nostr.Tag{"unit", "sat"},
|
||||
)
|
||||
if w.Name != "" {
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"name", w.Name})
|
||||
}
|
||||
if w.Description != "" {
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"description", w.Description})
|
||||
}
|
||||
for _, relay := range w.Relays {
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"relay", relay})
|
||||
}
|
||||
|
||||
err = kr.SignEvent(ctx, &evt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
events := make([]nostr.Event, 0, 1+len(w.Tokens))
|
||||
events = append(events, evt)
|
||||
|
||||
for _, t := range w.Tokens {
|
||||
var evt nostr.Event
|
||||
|
||||
if t.event != nil {
|
||||
if skipExisting {
|
||||
continue
|
||||
}
|
||||
evt = *t.event
|
||||
} else {
|
||||
err := t.toEvent(ctx, kr, w.Identifier, &evt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
events = append(events, evt)
|
||||
}
|
||||
|
||||
for _, h := range w.History {
|
||||
var evt nostr.Event
|
||||
|
||||
if h.event != nil {
|
||||
if skipExisting {
|
||||
continue
|
||||
}
|
||||
evt = *h.event
|
||||
} else {
|
||||
err := h.toEvent(ctx, kr, w.Identifier, &evt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
events = append(events, evt)
|
||||
}
|
||||
|
||||
return events, nil
|
||||
}
|
||||
|
||||
func (w *Wallet) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
|
||||
w.Tokens = make([]Token, 0, 128)
|
||||
w.History = make([]HistoryEntry, 0, 128)
|
||||
|
||||
pk, err := kr.GetPublicKey(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
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 "d":
|
||||
w.Identifier = tag[1]
|
||||
case "name":
|
||||
w.Name = tag[1]
|
||||
case "description":
|
||||
w.Description = tag[1]
|
||||
case "unit":
|
||||
essential++
|
||||
if tag[1] != "sat" {
|
||||
return fmt.Errorf("only 'sat' wallets are supported")
|
||||
}
|
||||
case "relay":
|
||||
w.Relays = append(w.Relays, tag[1])
|
||||
case "mint":
|
||||
w.Mints = append(w.Mints, tag[1])
|
||||
case "privkey":
|
||||
w.PrivateKey = tag[1]
|
||||
case "balance":
|
||||
if len(tag) < 3 {
|
||||
return fmt.Errorf("'balance' tag must have at least 3 items")
|
||||
}
|
||||
if tag[2] != "sat" {
|
||||
return fmt.Errorf("only 'sat' wallets are supported")
|
||||
}
|
||||
v, err := strconv.ParseUint(tag[1], 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid 'balance' %s: %w", tag[1], err)
|
||||
}
|
||||
w.temporaryBalance = uint32(v)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user