it never ends.
This commit is contained in:
@@ -20,7 +20,7 @@ type HistoryEntry struct {
|
||||
}
|
||||
|
||||
type TokenRef struct {
|
||||
EventID string
|
||||
EventID nostr.ID
|
||||
Created bool
|
||||
IsNutzap bool
|
||||
}
|
||||
@@ -47,7 +47,7 @@ func (h HistoryEntry) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Ev
|
||||
|
||||
for _, tf := range h.TokenReferences {
|
||||
if tf.IsNutzap {
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"e", tf.EventID, "", "redeemed"})
|
||||
evt.Tags = append(evt.Tags, nostr.Tag{"e", tf.EventID.Hex(), "", "redeemed"})
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ func (h HistoryEntry) toEvent(ctx context.Context, kr nostr.Keyer, evt *nostr.Ev
|
||||
marker = "created"
|
||||
}
|
||||
|
||||
encryptedTags = append(encryptedTags, nostr.Tag{"e", tf.EventID, "", marker})
|
||||
encryptedTags = append(encryptedTags, nostr.Tag{"e", tf.EventID.Hex(), "", marker})
|
||||
}
|
||||
|
||||
jsonb, _ := json.Marshal(encryptedTags)
|
||||
@@ -129,11 +129,12 @@ func (h *HistoryEntry) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Eve
|
||||
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])
|
||||
id, err := nostr.IDFromHex(tag[1])
|
||||
if err != nil {
|
||||
return fmt.Errorf("'e' tag has invalid event id %s: %w", tag[1])
|
||||
}
|
||||
|
||||
tf := TokenRef{EventID: tag[1]}
|
||||
tf := TokenRef{EventID: id}
|
||||
switch tag[3] {
|
||||
case "created":
|
||||
tf.Created = true
|
||||
|
||||
@@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
)
|
||||
|
||||
type receiveSettings struct {
|
||||
|
||||
@@ -6,13 +6,13 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut11"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
type SendOption func(opts *sendSettings)
|
||||
@@ -23,10 +23,9 @@ type sendSettings struct {
|
||||
refundtimelock int64
|
||||
}
|
||||
|
||||
func WithP2PK(pubkey string) SendOption {
|
||||
func WithP2PK(pubkey nostr.PubKey) SendOption {
|
||||
return func(opts *sendSettings) {
|
||||
pkb, _ := hex.DecodeString(pubkey)
|
||||
opts.p2pk, _ = btcec.ParsePubKey(pkb)
|
||||
opts.p2pk, _ = btcec.ParsePubKey(append([]byte{2}, pubkey[:]...))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,7 +131,7 @@ func (w *Wallet) saveChangeAndDeleteUsedTokens(
|
||||
mintedAt: nostr.Now(),
|
||||
Mint: mintURL,
|
||||
Proofs: changeProofs,
|
||||
Deleted: make([]string, 0, len(usedTokenIndexes)),
|
||||
Deleted: make([]nostr.ID, 0, len(usedTokenIndexes)),
|
||||
event: &nostr.Event{},
|
||||
}
|
||||
|
||||
@@ -144,7 +143,7 @@ func (w *Wallet) saveChangeAndDeleteUsedTokens(
|
||||
deleteEvent := nostr.Event{
|
||||
CreatedAt: nostr.Now(),
|
||||
Kind: 5,
|
||||
Tags: nostr.Tags{{"e", token.event.ID}, {"k", "7375"}},
|
||||
Tags: nostr.Tags{{"e", token.event.ID.Hex()}, {"k", "7375"}},
|
||||
}
|
||||
w.kr.SignEvent(ctx, &deleteEvent)
|
||||
|
||||
|
||||
@@ -5,14 +5,14 @@ import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
)
|
||||
|
||||
type Token struct {
|
||||
Mint string `json:"mint"`
|
||||
Proofs cashu.Proofs `json:"proofs"`
|
||||
Deleted []string `json:"del,omitempty"`
|
||||
Deleted []nostr.ID `json:"del,omitempty"`
|
||||
|
||||
mintedAt nostr.Timestamp
|
||||
event *nostr.Event
|
||||
@@ -20,7 +20,7 @@ type Token struct {
|
||||
|
||||
func (t Token) ID() string {
|
||||
if t.event != nil {
|
||||
return t.event.ID
|
||||
return t.event.ID.Hex()
|
||||
}
|
||||
|
||||
return "<not-published>"
|
||||
|
||||
@@ -9,9 +9,9 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||
"fiatjaf.com/nostr"
|
||||
)
|
||||
|
||||
type Wallet struct {
|
||||
@@ -19,7 +19,7 @@ type Wallet struct {
|
||||
tokensMu sync.Mutex
|
||||
event *nostr.Event
|
||||
|
||||
pendingDeletions []string // token events that should be deleted
|
||||
pendingDeletions []nostr.ID // token events that should be deleted
|
||||
|
||||
kr nostr.Keyer
|
||||
|
||||
@@ -34,7 +34,7 @@ type Wallet struct {
|
||||
)
|
||||
|
||||
// Processed, if not nil, is called every time a received event is processed
|
||||
Processed func(*nostr.Event, error)
|
||||
Processed func(nostr.Event, error)
|
||||
|
||||
// Stable is closed when we have gotten an EOSE from all relays
|
||||
Stable chan struct{}
|
||||
@@ -77,7 +77,7 @@ func loadWalletFromPool(
|
||||
return nil
|
||||
}
|
||||
|
||||
kinds := []int{17375, 7375}
|
||||
kinds := []uint16{17375, 7375}
|
||||
if withHistory {
|
||||
kinds = append(kinds, 7376)
|
||||
}
|
||||
@@ -86,16 +86,18 @@ func loadWalletFromPool(
|
||||
events := pool.SubscribeManyNotifyEOSE(
|
||||
ctx,
|
||||
relays,
|
||||
nostr.Filter{Kinds: kinds, Authors: []string{pk}},
|
||||
nostr.Filter{Kinds: kinds, Authors: []nostr.PubKey{pk}},
|
||||
eoseChanE,
|
||||
nostr.SubscriptionOptions{},
|
||||
)
|
||||
|
||||
eoseChanD := make(chan struct{})
|
||||
deletions := pool.SubscribeManyNotifyEOSE(
|
||||
ctx,
|
||||
relays,
|
||||
nostr.Filter{Kinds: []int{5}, Tags: nostr.TagMap{"k": []string{"7375"}}, Authors: []string{pk}},
|
||||
nostr.Filter{Kinds: []uint16{5}, Tags: nostr.TagMap{"k": []string{"7375"}}, Authors: []nostr.PubKey{pk}},
|
||||
eoseChanD,
|
||||
nostr.SubscriptionOptions{},
|
||||
)
|
||||
|
||||
eoseChan := make(chan struct{})
|
||||
@@ -116,7 +118,7 @@ func loadWallet(
|
||||
eoseChan chan struct{},
|
||||
) *Wallet {
|
||||
w := &Wallet{
|
||||
pendingDeletions: make([]string, 0, 128),
|
||||
pendingDeletions: make([]nostr.ID, 0, 128),
|
||||
kr: kr,
|
||||
Stable: make(chan struct{}),
|
||||
Tokens: make([]Token, 0, 128),
|
||||
@@ -143,11 +145,15 @@ func loadWallet(
|
||||
w.Lock()
|
||||
if !eosed {
|
||||
for tag := range ie.Event.Tags.FindAll("e") {
|
||||
w.pendingDeletions = append(w.pendingDeletions, tag[1])
|
||||
if id, err := nostr.IDFromHex(tag[1]); err == nil {
|
||||
w.pendingDeletions = append(w.pendingDeletions, id)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for tag := range ie.Event.Tags.FindAll("e") {
|
||||
w.removeDeletedToken(tag[1])
|
||||
if id, err := nostr.IDFromHex(tag[1]); err == nil {
|
||||
w.removeDeletedToken(id)
|
||||
}
|
||||
}
|
||||
}
|
||||
w.Unlock()
|
||||
@@ -159,7 +165,7 @@ func loadWallet(
|
||||
w.Lock()
|
||||
switch ie.Event.Kind {
|
||||
case 17375:
|
||||
if err := w.parse(ctx, kr, ie.Event); err != nil {
|
||||
if err := w.parse(ctx, kr, &ie.Event); err != nil {
|
||||
if w.Processed != nil {
|
||||
w.Processed(ie.Event, err)
|
||||
}
|
||||
@@ -169,11 +175,11 @@ func loadWallet(
|
||||
|
||||
// if this metadata is newer than what we had, update
|
||||
if w.event == nil || ie.Event.CreatedAt > w.event.CreatedAt {
|
||||
w.parse(ctx, kr, ie.Event) // this will either fail or set the new metadata
|
||||
w.parse(ctx, kr, &ie.Event) // this will either fail or set the new metadata
|
||||
}
|
||||
case 7375: // token
|
||||
token := Token{}
|
||||
if err := token.parse(ctx, kr, ie.Event); err != nil {
|
||||
if err := token.parse(ctx, kr, &ie.Event); err != nil {
|
||||
if w.Processed != nil {
|
||||
w.Processed(ie.Event, err)
|
||||
}
|
||||
@@ -200,7 +206,7 @@ func loadWallet(
|
||||
|
||||
case 7376: // history
|
||||
he := HistoryEntry{}
|
||||
if err := he.parse(ctx, kr, ie.Event); err != nil {
|
||||
if err := he.parse(ctx, kr, &ie.Event); err != nil {
|
||||
if w.Processed != nil {
|
||||
w.Processed(ie.Event, err)
|
||||
}
|
||||
@@ -230,7 +236,7 @@ func (w *Wallet) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *Wallet) removeDeletedToken(eventId string) {
|
||||
func (w *Wallet) removeDeletedToken(eventId nostr.ID) {
|
||||
for t := len(w.Tokens) - 1; t >= 0; t-- {
|
||||
token := w.Tokens[t]
|
||||
if token.event != nil && token.event.ID == eventId {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"context"
|
||||
"fmt"
|
||||
@@ -8,17 +9,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/keyer"
|
||||
"github.com/btcsuite/btcd/btcec/v2"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/exp/rand"
|
||||
)
|
||||
|
||||
func TestWallet(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
kr, err := keyer.NewPlainKeySigner("040cbf11f24b080ad9d8669d7514d9f3b7b1f58e5a6dcb75549352b041656537")
|
||||
kr, err := keyer.NewPlainKeySigner(nostr.MustSecretKeyFromHex("040cbf11f24b080ad9d8669d7514d9f3b7b1f58e5a6dcb75549352b041656537"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -53,7 +54,7 @@ func TestWallet(t *testing.T) {
|
||||
Amount: 100,
|
||||
createdAt: nostr.Timestamp(time.Now().Add(-3 * time.Hour).Unix()),
|
||||
TokenReferences: []TokenRef{
|
||||
{Created: true, EventID: "645babb9051f46ddc97d960e68f82934e627f136dde7b860bf87c9213d937b58"},
|
||||
{Created: true, EventID: nostr.MustIDFromHex("645babb9051f46ddc97d960e68f82934e627f136dde7b860bf87c9213d937b58")},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -61,8 +62,8 @@ func TestWallet(t *testing.T) {
|
||||
Amount: 200,
|
||||
createdAt: nostr.Timestamp(time.Now().Add(-2 * time.Hour).Unix()),
|
||||
TokenReferences: []TokenRef{
|
||||
{Created: false, EventID: "add072ae7d7a027748e03024267a1c073f3fbc26cca468ba8630d039a7f5df72"},
|
||||
{Created: true, EventID: "b8460b5589b68a0d9a017ac3784d17a0729046206aa631f7f4b763b738e36cf8"},
|
||||
{Created: false, EventID: nostr.MustIDFromHex("add072ae7d7a027748e03024267a1c073f3fbc26cca468ba8630d039a7f5df72")},
|
||||
{Created: true, EventID: nostr.MustIDFromHex("b8460b5589b68a0d9a017ac3784d17a0729046206aa631f7f4b763b738e36cf8")},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -70,52 +71,52 @@ func TestWallet(t *testing.T) {
|
||||
Amount: 300,
|
||||
createdAt: nostr.Timestamp(time.Now().Add(-1 * time.Hour).Unix()),
|
||||
TokenReferences: []TokenRef{
|
||||
{Created: false, EventID: "61f86031d0ab95e9134a3ab955e96104cb1f4d610172838d28aa7ae9dc1cc924"},
|
||||
{Created: true, EventID: "588b78e4af06e960434239e7367a0bedf84747d4c52ff943f5e8b7daa3e1b601", IsNutzap: true},
|
||||
{Created: false, EventID: "8f14c0a4ff1bf85ccc26bf0125b9a289552f9b59bbb310b163d6a88a7bbd4ebc"},
|
||||
{Created: true, EventID: "41a6f442b7c3c9e2f1e8c4835c00f17c56b3e3be4c9f7cf7bc4cdd705b1b61db", IsNutzap: true},
|
||||
{Created: false, EventID: nostr.MustIDFromHex("61f86031d0ab95e9134a3ab955e96104cb1f4d610172838d28aa7ae9dc1cc924")},
|
||||
{Created: true, EventID: nostr.MustIDFromHex("588b78e4af06e960434239e7367a0bedf84747d4c52ff943f5e8b7daa3e1b601"), IsNutzap: true},
|
||||
{Created: false, EventID: nostr.MustIDFromHex("8f14c0a4ff1bf85ccc26bf0125b9a289552f9b59bbb310b163d6a88a7bbd4ebc")},
|
||||
{Created: true, EventID: nostr.MustIDFromHex("41a6f442b7c3c9e2f1e8c4835c00f17c56b3e3be4c9f7cf7bc4cdd705b1b61db"), IsNutzap: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
// turn everything into events
|
||||
events := make([]*nostr.Event, 0, 7)
|
||||
events := make([]nostr.Event, 0, 7)
|
||||
|
||||
// wallet metadata event
|
||||
metaEvent := &nostr.Event{}
|
||||
err = w.toEvent(ctx, kr, metaEvent)
|
||||
metaEvent := nostr.Event{}
|
||||
err = w.toEvent(ctx, kr, &metaEvent)
|
||||
require.NoError(t, err)
|
||||
events = append(events, metaEvent)
|
||||
|
||||
// token events
|
||||
for i := range w.Tokens {
|
||||
evt := &nostr.Event{}
|
||||
evt := nostr.Event{}
|
||||
evt.Tags = nostr.Tags{}
|
||||
err := w.Tokens[i].toEvent(ctx, kr, evt)
|
||||
err := w.Tokens[i].toEvent(ctx, kr, &evt)
|
||||
require.NoError(t, err)
|
||||
w.Tokens[i].event = evt
|
||||
w.Tokens[i].event = &evt
|
||||
events = append(events, evt)
|
||||
}
|
||||
|
||||
// history events
|
||||
for i := range w.History {
|
||||
evt := &nostr.Event{}
|
||||
evt := nostr.Event{}
|
||||
evt.Tags = nostr.Tags{}
|
||||
err := w.History[i].toEvent(ctx, kr, evt)
|
||||
err := w.History[i].toEvent(ctx, kr, &evt)
|
||||
require.NoError(t, err)
|
||||
w.History[i].event = evt
|
||||
w.History[i].event = &evt
|
||||
events = append(events, evt)
|
||||
}
|
||||
|
||||
// test different orderings
|
||||
testCases := []struct {
|
||||
name string
|
||||
sort func([]*nostr.Event)
|
||||
sort func([]nostr.Event)
|
||||
}{
|
||||
{
|
||||
name: "random order",
|
||||
sort: func(evts []*nostr.Event) {
|
||||
sort: func(evts []nostr.Event) {
|
||||
r := rand.New(rand.NewSource(42)) // deterministic
|
||||
r.Shuffle(len(evts), func(i, j int) {
|
||||
evts[i], evts[j] = evts[j], evts[i]
|
||||
@@ -124,16 +125,16 @@ func TestWallet(t *testing.T) {
|
||||
},
|
||||
{
|
||||
name: "most recent first",
|
||||
sort: func(evts []*nostr.Event) {
|
||||
slices.SortFunc(evts, func(a, b *nostr.Event) int {
|
||||
sort: func(evts []nostr.Event) {
|
||||
slices.SortFunc(evts, func(a, b nostr.Event) int {
|
||||
return int(b.CreatedAt - a.CreatedAt)
|
||||
})
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "least recent first",
|
||||
sort: func(evts []*nostr.Event) {
|
||||
slices.SortFunc(evts, func(a, b *nostr.Event) int {
|
||||
sort: func(evts []nostr.Event) {
|
||||
slices.SortFunc(evts, func(a, b nostr.Event) int {
|
||||
return int(a.CreatedAt - b.CreatedAt)
|
||||
})
|
||||
},
|
||||
@@ -143,7 +144,7 @@ func TestWallet(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
// make a copy and sort it
|
||||
eventsCopy := make([]*nostr.Event, len(events))
|
||||
eventsCopy := make([]nostr.Event, len(events))
|
||||
copy(eventsCopy, events)
|
||||
tc.sort(eventsCopy)
|
||||
|
||||
@@ -162,7 +163,7 @@ func TestWallet(t *testing.T) {
|
||||
|
||||
// load wallet from events
|
||||
loaded := loadWallet(ctx, kr, evtChan, make(chan nostr.RelayEvent), eoseChan)
|
||||
loaded.Processed = func(evt *nostr.Event, err error) {
|
||||
loaded.Processed = func(evt nostr.Event, err error) {
|
||||
fmt.Println("processed", evt.Kind, err)
|
||||
}
|
||||
|
||||
@@ -174,8 +175,8 @@ func TestWallet(t *testing.T) {
|
||||
slices.SortFunc(loaded.History, func(a, b HistoryEntry) int { return cmp.Compare(a.createdAt, b.createdAt) })
|
||||
slices.SortFunc(w.History, func(a, b HistoryEntry) int { return cmp.Compare(a.createdAt, b.createdAt) })
|
||||
for i := range w.History {
|
||||
slices.SortFunc(loaded.History[i].TokenReferences, func(a, b TokenRef) int { return cmp.Compare(a.EventID, b.EventID) })
|
||||
slices.SortFunc(w.History[i].TokenReferences, func(a, b TokenRef) int { return cmp.Compare(a.EventID, b.EventID) })
|
||||
slices.SortFunc(loaded.History[i].TokenReferences, func(a, b TokenRef) int { return bytes.Compare(a.EventID[:], b.EventID[:]) })
|
||||
slices.SortFunc(w.History[i].TokenReferences, func(a, b TokenRef) int { return bytes.Compare(a.EventID[:], b.EventID[:]) })
|
||||
require.Equal(t, loaded.History[i], w.History[i])
|
||||
}
|
||||
require.ElementsMatch(t, loaded.Mints, w.Mints)
|
||||
|
||||
Reference in New Issue
Block a user