go-nostr/nip60/wallet.go

154 lines
3.0 KiB
Go

package nip60
import (
"context"
"encoding/hex"
"encoding/json"
"fmt"
"sync"
"github.com/btcsuite/btcd/btcec/v2"
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/nbd-wtf/go-nostr"
)
type Wallet struct {
wl *WalletStash
Identifier string
Description string
Name string
PrivateKey *btcec.PrivateKey
PublicKey *btcec.PublicKey
Relays []string
Mints []string
Tokens []Token
History []HistoryEntry
tokensMu sync.Mutex
event *nostr.Event
tokensPendingDeletion []string
}
func (w *Wallet) Balance() uint64 {
var sum uint64
for _, token := range w.Tokens {
sum += token.Proofs.Amount()
}
return sum
}
func (w *Wallet) DisplayName() string {
if w.Name != "" {
return fmt.Sprintf("%s (%s)", w.Name, w.Identifier)
}
return w.Identifier
}
func (w *Wallet) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
evt.CreatedAt = nostr.Now()
evt.Kind = 37375
evt.Tags = make(nostr.Tags, 0, 7)
pk, err := kr.GetPublicKey(ctx)
if err != nil {
return err
}
evt.Content, err = kr.Encrypt(
ctx,
fmt.Sprintf(`[["privkey","%x"]]`, w.PrivateKey.Serialize()),
pk,
)
if err != nil {
return 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})
}
for _, mint := range w.Mints {
evt.Tags = append(evt.Tags, nostr.Tag{"mint", mint})
}
err = kr.SignEvent(ctx, evt)
if err != nil {
return err
}
return 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)
w.event = evt
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":
essential++
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":
essential++
skb, err := hex.DecodeString(tag[1])
if err != nil {
return fmt.Errorf("failed to parse private key: %w", err)
}
w.PrivateKey = secp256k1.PrivKeyFromBytes(skb)
w.PublicKey = w.PrivateKey.PubKey()
}
}
if essential != 3 {
return fmt.Errorf("missing essential tags")
}
return nil
}