mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-05-12 11:39:58 +02:00
nip61 and nip60 improvements and fixes.
This commit is contained in:
parent
48ce669a3d
commit
1e4848d84d
@ -11,17 +11,47 @@ import (
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
)
|
||||
|
||||
type receiveSettings struct {
|
||||
intoMint []string
|
||||
isNutzap bool
|
||||
}
|
||||
|
||||
type ReceiveOption func(*receiveSettings)
|
||||
|
||||
func WithMintDestination(url string) ReceiveOption {
|
||||
return func(rs *receiveSettings) {
|
||||
rs.intoMint = append(rs.intoMint, url)
|
||||
}
|
||||
}
|
||||
|
||||
func WithNutzap() ReceiveOption {
|
||||
return func(rs *receiveSettings) {
|
||||
rs.isNutzap = true
|
||||
}
|
||||
}
|
||||
|
||||
func (w *Wallet) Receive(
|
||||
ctx context.Context,
|
||||
proofs cashu.Proofs,
|
||||
mint string,
|
||||
opts ...ReceiveOption,
|
||||
) error {
|
||||
if w.PublishUpdate == nil {
|
||||
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||
}
|
||||
|
||||
source := "http" + nostr.NormalizeURL(mint)[2:]
|
||||
lightningSwap := slices.Contains(w.Mints, source)
|
||||
rs := receiveSettings{}
|
||||
for _, opt := range opts {
|
||||
opt(&rs)
|
||||
}
|
||||
|
||||
source, _ := nostr.NormalizeHTTPURL(mint)
|
||||
destination := rs.intoMint
|
||||
if len(destination) == 0 {
|
||||
destination = w.Mints
|
||||
}
|
||||
|
||||
lightningSwap := slices.Contains(destination, source)
|
||||
swapOpts := make([]SwapOption, 0, 1)
|
||||
|
||||
for i, proof := range proofs {
|
||||
@ -60,7 +90,7 @@ func (w *Wallet) Receive(
|
||||
// if we have to swap to our own mint we do it now by getting a bolt11 invoice from our mint
|
||||
// and telling the current mint to pay it
|
||||
if lightningSwap {
|
||||
for _, targetMint := range w.Mints {
|
||||
for _, targetMint := range destination {
|
||||
swappedProofs, err, status := lightningMeltMint(
|
||||
ctx,
|
||||
newProofs,
|
||||
@ -110,7 +140,7 @@ saveproofs:
|
||||
{
|
||||
EventID: newToken.event.ID,
|
||||
Created: true,
|
||||
IsNutzap: false,
|
||||
IsNutzap: rs.isNutzap,
|
||||
},
|
||||
},
|
||||
createdAt: nostr.Now(),
|
||||
|
@ -72,7 +72,7 @@ func (t *Token) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) err
|
||||
return fmt.Errorf("failed to parse token content: %w", err)
|
||||
}
|
||||
|
||||
t.Mint = "http" + nostr.NormalizeURL(t.Mint)[2:]
|
||||
t.Mint, _ = nostr.NormalizeHTTPURL(t.Mint)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
108
nip60/wallet.go
108
nip60/wallet.go
@ -24,6 +24,7 @@ type Wallet struct {
|
||||
kr nostr.Keyer
|
||||
|
||||
// PublishUpdate must be set to a function that publishes event to the user relays
|
||||
// (if all arguments are their zero values that means it is a wallet update event).
|
||||
PublishUpdate func(
|
||||
event nostr.Event,
|
||||
deleted *Token,
|
||||
@ -78,7 +79,7 @@ func loadWalletFromPool(
|
||||
|
||||
kinds := []int{17375, 7375}
|
||||
if withHistory {
|
||||
kinds = append(kinds, 7375)
|
||||
kinds = append(kinds, 7376)
|
||||
}
|
||||
|
||||
eoseChan := make(chan struct{})
|
||||
@ -229,6 +230,89 @@ func (w *Wallet) Balance() uint64 {
|
||||
return sum
|
||||
}
|
||||
|
||||
func (w *Wallet) AddMint(ctx context.Context, urls ...string) error {
|
||||
if w.PublishUpdate == nil {
|
||||
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
url, err := nostr.NormalizeHTTPURL(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !slices.Contains(w.Mints, url) {
|
||||
w.Mints = append(w.Mints, url)
|
||||
}
|
||||
}
|
||||
|
||||
evt := nostr.Event{}
|
||||
if err := w.toEvent(ctx, w.kr, &evt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
w.PublishUpdate(evt, nil, nil, nil, false)
|
||||
w.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) RemoveMint(ctx context.Context, urls ...string) error {
|
||||
if w.PublishUpdate == nil {
|
||||
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||
}
|
||||
|
||||
for _, url := range urls {
|
||||
url, err := nostr.NormalizeHTTPURL(url)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if idx := slices.Index(w.Mints, url); idx != -1 {
|
||||
w.Mints = slices.Delete(w.Mints, idx, idx+1)
|
||||
}
|
||||
}
|
||||
|
||||
evt := nostr.Event{}
|
||||
if err := w.toEvent(ctx, w.kr, &evt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
w.PublishUpdate(evt, nil, nil, nil, false)
|
||||
w.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) SetPrivateKey(ctx context.Context, privateKey string) error {
|
||||
if w.PublishUpdate == nil {
|
||||
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||
}
|
||||
|
||||
skb, err := hex.DecodeString(privateKey)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(skb) != 32 {
|
||||
return fmt.Errorf("private key must be 32 bytes, got %d", len(skb))
|
||||
}
|
||||
|
||||
w.PrivateKey, w.PublicKey = btcec.PrivKeyFromBytes(skb)
|
||||
|
||||
evt := nostr.Event{}
|
||||
if err := w.toEvent(ctx, w.kr, &evt); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
w.PublishUpdate(evt, nil, nil, nil, false)
|
||||
w.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
|
||||
evt.CreatedAt = nostr.Now()
|
||||
evt.Kind = 17375
|
||||
@ -239,12 +323,15 @@ func (w *Wallet) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event)
|
||||
return err
|
||||
}
|
||||
|
||||
tags := make(nostr.Tags, 0, 1+len(w.Mints))
|
||||
tags = append(tags, nostr.Tag{"privkey", hex.EncodeToString(w.PrivateKey.Serialize())})
|
||||
for _, mint := range w.Mints {
|
||||
tags = append(tags, nostr.Tag{"mint", mint})
|
||||
encryptedTags := make(nostr.Tags, 0, 1+len(w.Mints))
|
||||
if w.PrivateKey != nil {
|
||||
encryptedTags = append(encryptedTags, nostr.Tag{"privkey", hex.EncodeToString(w.PrivateKey.Serialize())})
|
||||
}
|
||||
jtags, _ := json.Marshal(tags)
|
||||
|
||||
for _, mint := range w.Mints {
|
||||
encryptedTags = append(encryptedTags, nostr.Tag{"mint", mint})
|
||||
}
|
||||
jtags, _ := json.Marshal(encryptedTags)
|
||||
evt.Content, err = kr.Encrypt(
|
||||
ctx,
|
||||
string(jtags),
|
||||
@ -301,15 +388,12 @@ func (w *Wallet) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) er
|
||||
}
|
||||
}
|
||||
|
||||
if privateKey == nil {
|
||||
return fmt.Errorf("missing wallet private key")
|
||||
if privateKey != nil {
|
||||
w.PrivateKey = privateKey
|
||||
w.PublicKey = w.PrivateKey.PubKey()
|
||||
}
|
||||
|
||||
// finally set these things when we know nothing will fail
|
||||
w.Mints = mints
|
||||
fmt.Println("mints", mints)
|
||||
w.PrivateKey = privateKey
|
||||
w.PublicKey = w.PrivateKey.PubKey()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ type Info struct {
|
||||
Relays []string
|
||||
}
|
||||
|
||||
func (zi *Info) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
|
||||
func (zi *Info) ToEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) error {
|
||||
evt.CreatedAt = nostr.Now()
|
||||
evt.Kind = 10019
|
||||
|
||||
@ -36,7 +36,7 @@ func (zi *Info) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) e
|
||||
return nil
|
||||
}
|
||||
|
||||
func (zi *Info) parse(evt *nostr.Event) error {
|
||||
func (zi *Info) ParseEvent(evt *nostr.Event) error {
|
||||
zi.Mints = make([]string, 0)
|
||||
for _, tag := range evt.Tags {
|
||||
if len(tag) < 2 {
|
||||
@ -46,7 +46,8 @@ func (zi *Info) parse(evt *nostr.Event) error {
|
||||
switch tag[0] {
|
||||
case "mint":
|
||||
if len(tag) == 2 || slices.Contains(tag[2:], cashu.Sat.String()) {
|
||||
zi.Mints = append(zi.Mints, "http"+nostr.NormalizeURL(tag[1])[2:])
|
||||
url, _ := nostr.NormalizeHTTPURL(tag[1])
|
||||
zi.Mints = append(zi.Mints, url)
|
||||
}
|
||||
case "relay":
|
||||
zi.Relays = append(zi.Relays, tag[1])
|
||||
|
@ -33,7 +33,7 @@ func SendNutzap(
|
||||
}
|
||||
|
||||
info := Info{}
|
||||
if err := info.parse(ie.Event); err != nil {
|
||||
if err := info.ParseEvent(ie.Event); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
44
normalize.go
44
normalize.go
@ -1,6 +1,7 @@
|
||||
package nostr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strings"
|
||||
)
|
||||
@ -36,6 +37,49 @@ func NormalizeURL(u string) string {
|
||||
return p.String()
|
||||
}
|
||||
|
||||
// NormalizeHTTPURL does normalization of http(s):// URLs according to rfc3986. Don't use for relay URLs.
|
||||
func NormalizeHTTPURL(s string) (string, error) {
|
||||
s = strings.TrimSpace(s)
|
||||
|
||||
if !strings.HasPrefix(s, "http") {
|
||||
s = "https://" + s
|
||||
}
|
||||
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
|
||||
if u.Scheme == "" {
|
||||
u, err = url.Parse("http://" + s)
|
||||
if err != nil {
|
||||
return s, err
|
||||
}
|
||||
}
|
||||
|
||||
if strings.HasPrefix(s, "//") {
|
||||
s = "http:" + s
|
||||
}
|
||||
|
||||
var p int
|
||||
switch u.Scheme {
|
||||
case "http":
|
||||
p = 80
|
||||
case "https":
|
||||
p = 443
|
||||
}
|
||||
u.Host = strings.TrimSuffix(u.Host, fmt.Sprintf(":%d", p))
|
||||
|
||||
v := u.Query()
|
||||
u.RawQuery = v.Encode()
|
||||
u.RawQuery, _ = url.QueryUnescape(u.RawQuery)
|
||||
|
||||
h := u.String()
|
||||
h = strings.TrimSuffix(h, "/")
|
||||
|
||||
return h, nil
|
||||
}
|
||||
|
||||
// NormalizeOKMessage takes a string message that is to be sent in an `OK` or `CLOSED` command
|
||||
// and prefixes it with "<prefix>: " if it doesn't already have an acceptable prefix.
|
||||
func NormalizeOKMessage(reason string, prefix string) string {
|
||||
|
Loading…
x
Reference in New Issue
Block a user