mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-03-17 21:32:56 +01:00
nip60: Changes channel is a PublishUpdate hook now that must be set, to prevent unpublished updates -- and also now updates have more metadata so the client can display more info about them.
This commit is contained in:
parent
f0054af4d8
commit
b86d5d52bb
@ -11,6 +11,10 @@ import (
|
||||
)
|
||||
|
||||
func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOption) (string, error) {
|
||||
if w.wl.PublishUpdate == nil {
|
||||
return "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||
}
|
||||
|
||||
ss := &sendSettings{}
|
||||
for _, opt := range opts {
|
||||
opt(ss)
|
||||
|
@ -12,6 +12,10 @@ import (
|
||||
)
|
||||
|
||||
func (w *Wallet) ReceiveToken(ctx context.Context, serializedToken string) error {
|
||||
if w.wl.PublishUpdate == nil {
|
||||
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||
}
|
||||
|
||||
token, err := cashu.DecodeToken(serializedToken)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -48,7 +52,7 @@ func (w *Wallet) ReceiveToken(ctx context.Context, serializedToken string) error
|
||||
}
|
||||
|
||||
// get new proofs
|
||||
newProofs, _, err := w.SwapProofs(ctx, source, proofs, proofs.Amount(), swapOpts...)
|
||||
newProofs, _, err := w.swapProofs(ctx, source, proofs, proofs.Amount(), swapOpts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -102,15 +106,13 @@ saveproofs:
|
||||
return fmt.Errorf("failed to make new token: %w", err)
|
||||
}
|
||||
|
||||
w.wl.Changes <- *newToken.event
|
||||
w.wl.Lock()
|
||||
w.wl.PublishUpdate(*newToken.event, nil, &newToken, nil, false)
|
||||
w.wl.Unlock()
|
||||
|
||||
w.tokensMu.Lock()
|
||||
w.Tokens = append(w.Tokens, newToken)
|
||||
w.tokensMu.Unlock()
|
||||
|
||||
wevt := nostr.Event{}
|
||||
w.toEvent(ctx, w.wl.kr, &wevt)
|
||||
w.wl.Changes <- wevt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -51,6 +51,10 @@ type chosenTokens struct {
|
||||
}
|
||||
|
||||
func (w *Wallet) SendToken(ctx context.Context, amount uint64, opts ...SendOption) (string, error) {
|
||||
if w.wl.PublishUpdate == nil {
|
||||
return "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||
}
|
||||
|
||||
ss := &sendSettings{}
|
||||
for _, opt := range opts {
|
||||
opt(ss)
|
||||
@ -91,7 +95,7 @@ func (w *Wallet) SendToken(ctx context.Context, amount uint64, opts ...SendOptio
|
||||
}
|
||||
|
||||
// get new proofs
|
||||
proofsToSend, changeProofs, err := w.SwapProofs(ctx, chosen.mint, chosen.proofs, amount, swapOpts...)
|
||||
proofsToSend, changeProofs, err := w.swapProofs(ctx, chosen.mint, chosen.proofs, amount, swapOpts...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -106,10 +110,6 @@ func (w *Wallet) SendToken(ctx context.Context, amount uint64, opts ...SendOptio
|
||||
return "", err
|
||||
}
|
||||
|
||||
wevt := nostr.Event{}
|
||||
w.toEvent(ctx, w.wl.kr, &wevt)
|
||||
w.wl.Changes <- wevt
|
||||
|
||||
return token.Serialize()
|
||||
}
|
||||
|
||||
@ -138,10 +138,13 @@ func (w *Wallet) saveChangeAndDeleteUsedTokens(
|
||||
deleteEvent := nostr.Event{
|
||||
CreatedAt: nostr.Now(),
|
||||
Kind: 5,
|
||||
Tags: nostr.Tags{{"e", token.event.ID}, {"k", "7375"}, {"alt", "deleting"}},
|
||||
Tags: nostr.Tags{{"e", token.event.ID}, {"k", "7375"}},
|
||||
}
|
||||
w.wl.kr.SignEvent(ctx, &deleteEvent)
|
||||
w.wl.Changes <- deleteEvent
|
||||
|
||||
w.wl.Lock()
|
||||
w.wl.PublishUpdate(deleteEvent, &token, nil, nil, false)
|
||||
w.wl.Unlock()
|
||||
}
|
||||
continue
|
||||
}
|
||||
@ -152,8 +155,13 @@ func (w *Wallet) saveChangeAndDeleteUsedTokens(
|
||||
if err := changeToken.toEvent(ctx, w.wl.kr, w.Identifier, changeToken.event); err != nil {
|
||||
return fmt.Errorf("failed to make change token: %w", err)
|
||||
}
|
||||
w.wl.Changes <- *changeToken.event
|
||||
w.wl.Lock()
|
||||
w.wl.PublishUpdate(*changeToken.event, nil, nil, &changeToken, false)
|
||||
w.wl.Unlock()
|
||||
|
||||
w.tokensMu.Lock()
|
||||
w.Tokens = append(updatedTokens, changeToken)
|
||||
w.tokensMu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -23,8 +23,14 @@ type WalletStash struct {
|
||||
|
||||
kr nostr.Keyer
|
||||
|
||||
// Changes emits a stream of events that must be published whenever something changes
|
||||
Changes chan nostr.Event
|
||||
// PublishUpdate must be set to a function that publishes event to the user relays
|
||||
PublishUpdate func(
|
||||
event nostr.Event,
|
||||
deleted *Token,
|
||||
received *Token,
|
||||
change *Token,
|
||||
isHistory bool,
|
||||
)
|
||||
|
||||
// Processed emits an error or nil every time an event is processed
|
||||
Processed chan error
|
||||
@ -94,7 +100,6 @@ func loadStash(
|
||||
pendingHistory: make(map[string][]HistoryEntry),
|
||||
pendingDeletions: make([]string, 0, 128),
|
||||
kr: kr,
|
||||
Changes: make(chan nostr.Event),
|
||||
Processed: make(chan error),
|
||||
Stable: make(chan struct{}),
|
||||
}
|
||||
@ -258,19 +263,6 @@ func loadStash(
|
||||
return wl
|
||||
}
|
||||
|
||||
func (wl *WalletStash) removeDeletedToken(eventId string) {
|
||||
for _, w := range wl.wallets {
|
||||
for t := len(w.Tokens) - 1; t >= 0; t-- {
|
||||
token := w.Tokens[t]
|
||||
if token.event != nil && token.event.ID == eventId {
|
||||
// swap delete
|
||||
w.Tokens[t] = w.Tokens[len(w.Tokens)-1]
|
||||
w.Tokens = w.Tokens[0 : len(w.Tokens)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (wl *WalletStash) EnsureWallet(id string) *Wallet {
|
||||
wl.Lock()
|
||||
defer wl.Unlock()
|
||||
@ -305,3 +297,23 @@ func (wl *WalletStash) Wallets() iter.Seq[*Wallet] {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Close waits for pending operations to end
|
||||
func (wl *WalletStash) Close() error {
|
||||
wl.Lock()
|
||||
defer wl.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (wl *WalletStash) removeDeletedToken(eventId string) {
|
||||
for _, w := range wl.wallets {
|
||||
for t := len(w.Tokens) - 1; t >= 0; t-- {
|
||||
token := w.Tokens[t]
|
||||
if token.event != nil && token.event.ID == eventId {
|
||||
// swap delete
|
||||
w.Tokens[t] = w.Tokens[len(w.Tokens)-1]
|
||||
w.Tokens = w.Tokens[0 : len(w.Tokens)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,7 +32,7 @@ type swapSettings struct {
|
||||
mustSignOutputs bool
|
||||
}
|
||||
|
||||
func (w *Wallet) SwapProofs(
|
||||
func (w *Wallet) swapProofs(
|
||||
ctx context.Context,
|
||||
mint string,
|
||||
proofs cashu.Proofs,
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
@ -26,8 +25,7 @@ type Wallet struct {
|
||||
Tokens []Token
|
||||
History []HistoryEntry
|
||||
|
||||
temporaryBalance uint64
|
||||
tokensMu sync.Mutex
|
||||
tokensMu sync.Mutex
|
||||
|
||||
event *nostr.Event
|
||||
tokensPendingDeletion []string
|
||||
@ -60,7 +58,7 @@ func (w *Wallet) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Event)
|
||||
|
||||
evt.Content, err = kr.Encrypt(
|
||||
ctx,
|
||||
fmt.Sprintf(`[["balance","%d","sat"],["privkey","%x"]]`, w.Balance(), w.PrivateKey.Serialize()),
|
||||
fmt.Sprintf(`[["privkey","%x"]]`, w.PrivateKey.Serialize()),
|
||||
pk,
|
||||
)
|
||||
if err != nil {
|
||||
@ -144,18 +142,6 @@ func (w *Wallet) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Event) er
|
||||
}
|
||||
w.PrivateKey = secp256k1.PrivKeyFromBytes(skb)
|
||||
w.PublicKey = w.PrivateKey.PubKey()
|
||||
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, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid 'balance' %s: %w", tag[1], err)
|
||||
}
|
||||
w.temporaryBalance = v
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,9 @@ func TestWalletTransfer(t *testing.T) {
|
||||
if stash1 == nil {
|
||||
t.Fatal("failed to load stash 1")
|
||||
}
|
||||
stash1.PublishUpdate = func(event nostr.Event, deleted, received, change *Token, isHistory bool) {
|
||||
pool.PublishMany(ctx, testRelays, event)
|
||||
}
|
||||
|
||||
// setup second wallet
|
||||
sk2 := os.Getenv("NIP60_SECRET_KEY_2")
|
||||
@ -50,15 +53,14 @@ func TestWalletTransfer(t *testing.T) {
|
||||
if stash2 == nil {
|
||||
t.Fatal("failed to load stash 2")
|
||||
}
|
||||
stash2.PublishUpdate = func(event nostr.Event, deleted, received, change *Token, isHistory bool) {
|
||||
pool.PublishMany(ctx, testRelays, event)
|
||||
}
|
||||
|
||||
// handle events from both stashes
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case evt := <-stash1.Changes:
|
||||
pool.PublishMany(ctx, testRelays, evt)
|
||||
case evt := <-stash2.Changes:
|
||||
pool.PublishMany(ctx, testRelays, evt)
|
||||
case err := <-stash1.Processed:
|
||||
if err != nil {
|
||||
t.Errorf("stash1 processing error: %v", err)
|
||||
|
Loading…
x
Reference in New Issue
Block a user