nip60: use more reasonable options instead of the crazy WithWhatever() pattern.

This commit is contained in:
fiatjaf
2025-05-05 16:36:44 -03:00
parent fcea4f1b15
commit b711548b03
7 changed files with 63 additions and 91 deletions

View File

@@ -131,7 +131,7 @@ func (h *HistoryEntry) parse(ctx context.Context, kr nostr.Keyer, evt *nostr.Eve
}
id, err := nostr.IDFromHex(tag[1])
if err != nil {
return fmt.Errorf("'e' tag has invalid event id %s: %w", tag[1])
return fmt.Errorf("'e' tag has invalid event id %s: %w", tag[1], err)
}
tf := TokenRef{EventID: id}

View File

@@ -5,22 +5,22 @@ import (
"fmt"
"time"
"github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut05"
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip60/client"
"github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut05"
)
func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOption) (string, error) {
// PayOptions contains options for paying a bolt11 invoice
type PayOptions struct {
FromMint string
}
func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts PayOptions) (string, error) {
if w.PublishUpdate == nil {
return "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
}
ss := &sendSettings{}
for _, opt := range opts {
opt(ss)
}
invoiceAmount, err := GetSatoshisAmountFromBolt11(invoice)
if err != nil {
return "", err
@@ -41,7 +41,7 @@ func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOpti
for range 5 {
amount := invoiceAmount*(100+feeReservePct)/100 + feeReserveAbs
var fee uint64
chosen, fee, err = w.getProofsForSending(ctx, amount, ss.specificMint, excludeMints)
chosen, fee, err = w.getProofsForSending(ctx, amount, opts.FromMint, excludeMints)
if err != nil {
return "", err
}

View File

@@ -11,42 +11,24 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut10"
)
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
}
// ReceiveOptions contains options for receiving tokens
type ReceiveOptions struct {
IntoMint []string
IsNutzap bool
}
func (w *Wallet) Receive(
ctx context.Context,
proofs cashu.Proofs,
mint string,
opts ...ReceiveOption,
opts ReceiveOptions,
) error {
if w.PublishUpdate == nil {
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
}
rs := receiveSettings{}
for _, opt := range opts {
opt(&rs)
}
source, _ := nostr.NormalizeHTTPURL(mint)
destination := rs.intoMint
destination := opts.IntoMint
swapSettings := swapSettings{}
@@ -138,7 +120,7 @@ saveproofs:
{
EventID: newToken.event.ID,
Created: true,
IsNutzap: rs.isNutzap,
IsNutzap: opts.IsNutzap,
},
},
createdAt: nostr.Now(),

View File

@@ -4,16 +4,16 @@ import (
"context"
"fmt"
"fiatjaf.com/nostr/nip60/client"
"github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut04"
"fiatjaf.com/nostr/nip60/client"
)
func (w *Wallet) SendExternal(
ctx context.Context,
mint string,
targetAmount uint64,
opts ...SendOption,
opts SendOptions,
) (cashu.Proofs, error) {
if w.PublishUpdate == nil {
return nil, fmt.Errorf("can't do write operations: missing PublishUpdate function")
@@ -28,7 +28,9 @@ func (w *Wallet) SendExternal(
return nil, fmt.Errorf("failed to generate mint quote: %w", err)
}
if _, err := w.PayBolt11(ctx, mintResp.Request, opts...); err != nil {
if _, err := w.PayBolt11(ctx, mintResp.Request, PayOptions{
FromMint: opts.SpecificSourceMint,
}); err != nil {
return nil, err
}

View File

@@ -15,30 +15,11 @@ import (
"github.com/elnosh/gonuts/cashu/nuts/nut11"
)
type SendOption func(opts *sendSettings)
type sendSettings struct {
specificMint string
p2pk *btcec.PublicKey
refundtimelock int64
}
func WithP2PK(pubkey nostr.PubKey) SendOption {
return func(opts *sendSettings) {
opts.p2pk, _ = btcec.ParsePubKey(append([]byte{2}, pubkey[:]...))
}
}
func WithRefundable(timelock nostr.Timestamp) SendOption {
return func(opts *sendSettings) {
opts.refundtimelock = int64(timelock)
}
}
func WithMint(url string) SendOption {
return func(opts *sendSettings) {
opts.specificMint = url
}
// SendOptions contains options for sending tokens
type SendOptions struct {
SpecificSourceMint string
P2PK *btcec.PublicKey
RefundTimelock nostr.Timestamp
}
type chosenTokens struct {
@@ -49,26 +30,21 @@ type chosenTokens struct {
keysets []nut02.Keyset
}
func (w *Wallet) Send(ctx context.Context, amount uint64, opts ...SendOption) (cashu.Proofs, string, error) {
func (w *Wallet) Send(ctx context.Context, amount uint64, opts SendOptions) (cashu.Proofs, string, error) {
if w.PublishUpdate == nil {
return nil, "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
}
ss := &sendSettings{}
for _, opt := range opts {
opt(ss)
}
w.tokensMu.Lock()
defer w.tokensMu.Unlock()
chosen, _, err := w.getProofsForSending(ctx, amount, ss.specificMint, nil)
chosen, _, err := w.getProofsForSending(ctx, amount, opts.SpecificSourceMint, nil)
if err != nil {
return nil, "", err
}
swapSettings := swapSettings{}
if ss.p2pk != nil {
if opts.P2PK != nil {
if info, err := client.GetMintInfo(ctx, chosen.mint); err != nil || !info.Nuts.Nut11.Supported {
return nil, chosen.mint, fmt.Errorf("mint doesn't support p2pk: %w", err)
}
@@ -76,16 +52,16 @@ func (w *Wallet) Send(ctx context.Context, amount uint64, opts ...SendOption) (c
tags := nut11.P2PKTags{
NSigs: 1,
Locktime: 0,
Pubkeys: []*btcec.PublicKey{ss.p2pk},
Pubkeys: []*btcec.PublicKey{opts.P2PK},
}
if ss.refundtimelock != 0 {
if opts.RefundTimelock != 0 {
tags.Refund = []*btcec.PublicKey{w.PublicKey}
tags.Locktime = ss.refundtimelock
tags.Locktime = int64(opts.RefundTimelock)
}
swapSettings.spendingCondition = &nut10.SpendingCondition{
Kind: nut10.P2PK,
Data: hex.EncodeToString(ss.p2pk.SerializeCompressed()),
Data: hex.EncodeToString(opts.P2PK.SerializeCompressed()),
Tags: nut11.SerializeP2PKTags(tags),
}
}
@@ -188,12 +164,12 @@ func (w *Wallet) saveChangeAndDeleteUsedTokens(
func (w *Wallet) getProofsForSending(
ctx context.Context,
amount uint64,
specificMint string,
fromMint string,
excludeMints []string,
) (chosenTokens, uint64, error) {
byMint := make(map[string]chosenTokens)
for t, token := range w.Tokens {
if specificMint != "" && token.Mint != specificMint {
if fromMint != "" && token.Mint != fromMint {
continue
}
if slices.Contains(excludeMints, token.Mint) {

View File

@@ -14,6 +14,11 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
)
// WalletOptions contains options for loading a wallet
type WalletOptions struct {
WithHistory bool
}
type Wallet struct {
sync.Mutex
tokensMu sync.Mutex
@@ -52,17 +57,9 @@ func LoadWallet(
kr nostr.Keyer,
pool *nostr.Pool,
relays []string,
opts WalletOptions,
) *Wallet {
return loadWalletFromPool(ctx, kr, pool, relays, false)
}
func LoadWalletWithHistory(
ctx context.Context,
kr nostr.Keyer,
pool *nostr.Pool,
relays []string,
) *Wallet {
return loadWalletFromPool(ctx, kr, pool, relays, true)
return loadWalletFromPool(ctx, kr, pool, relays, opts)
}
func loadWalletFromPool(
@@ -70,7 +67,7 @@ func loadWalletFromPool(
kr nostr.Keyer,
pool *nostr.Pool,
relays []string,
withHistory bool,
opts WalletOptions,
) *Wallet {
pk, err := kr.GetPublicKey(ctx)
if err != nil {
@@ -78,7 +75,7 @@ func loadWalletFromPool(
}
kinds := []nostr.Kind{17375, 7375}
if withHistory {
if opts.WithHistory {
kinds = append(kinds, 7376)
}

View File

@@ -11,6 +11,7 @@ import (
"fiatjaf.com/nostr"
"fiatjaf.com/nostr/nip60"
"github.com/btcsuite/btcd/btcec/v2"
)
var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps")
@@ -27,7 +28,11 @@ func SendNutzap(
amount uint64,
message string,
) (chan nostr.PublishResult, error) {
ie := pool.QuerySingle(ctx, relays, nostr.Filter{Kinds: []nostr.Kind{10019}, Authors: []nostr.PubKey{targetUserPublickey}}, nostr.SubscriptionOptions{})
ie := pool.QuerySingle(ctx, relays, nostr.Filter{
Kinds: []nostr.Kind{10019},
Authors: []nostr.PubKey{targetUserPublickey},
},
nostr.SubscriptionOptions{Label: "pre-nutzap"})
if ie == nil {
return nil, NutzapsNotAccepted
}
@@ -60,9 +65,17 @@ func SendNutzap(
nutzap.Tags = append(nutzap.Tags, nostr.Tag{"e", eventId.Hex()})
}
p2pk, err := btcec.ParsePubKey(append([]byte{2}, info.PublicKey[:]...))
if err != nil {
return nil, fmt.Errorf("invalid p2pk target '%s': %w", info.PublicKey.Hex(), err)
}
// check if we have enough tokens in any of these mints
for mint := range getEligibleTokensWeHave(info.Mints, w.Tokens, amount) {
proofs, _, err := w.Send(ctx, amount, nip60.WithP2PK(info.PublicKey), nip60.WithMint(mint))
proofs, _, err := w.Send(ctx, amount, nip60.SendOptions{
P2PK: p2pk,
SpecificSourceMint: mint,
})
if err != nil {
continue
}
@@ -83,7 +96,9 @@ func SendNutzap(
// we don't have tokens at the desired target mint, so we first have to create some
for _, mint := range info.Mints {
proofs, err := w.SendExternal(ctx, mint, amount)
proofs, err := w.SendExternal(ctx, mint, amount, nip60.SendOptions{
P2PK: p2pk,
})
if err != nil {
if strings.Contains(err.Error(), "generate mint quote") {
continue