nip60: use more reasonable options instead of the crazy WithWhatever() pattern.
This commit is contained in:
@@ -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}
|
||||
|
||||
18
nip60/pay.go
18
nip60/pay.go
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user