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])
|
id, err := nostr.IDFromHex(tag[1])
|
||||||
if err != nil {
|
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}
|
tf := TokenRef{EventID: id}
|
||||||
|
|||||||
18
nip60/pay.go
18
nip60/pay.go
@@ -5,22 +5,22 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/elnosh/gonuts/cashu"
|
|
||||||
"github.com/elnosh/gonuts/cashu/nuts/nut05"
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/nip60/client"
|
"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 {
|
if w.PublishUpdate == nil {
|
||||||
return "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
return "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||||
}
|
}
|
||||||
|
|
||||||
ss := &sendSettings{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(ss)
|
|
||||||
}
|
|
||||||
|
|
||||||
invoiceAmount, err := GetSatoshisAmountFromBolt11(invoice)
|
invoiceAmount, err := GetSatoshisAmountFromBolt11(invoice)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@@ -41,7 +41,7 @@ func (w *Wallet) PayBolt11(ctx context.Context, invoice string, opts ...SendOpti
|
|||||||
for range 5 {
|
for range 5 {
|
||||||
amount := invoiceAmount*(100+feeReservePct)/100 + feeReserveAbs
|
amount := invoiceAmount*(100+feeReservePct)/100 + feeReserveAbs
|
||||||
var fee uint64
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,42 +11,24 @@ import (
|
|||||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||||
)
|
)
|
||||||
|
|
||||||
type receiveSettings struct {
|
// ReceiveOptions contains options for receiving tokens
|
||||||
intoMint []string
|
type ReceiveOptions struct {
|
||||||
isNutzap bool
|
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(
|
func (w *Wallet) Receive(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
proofs cashu.Proofs,
|
proofs cashu.Proofs,
|
||||||
mint string,
|
mint string,
|
||||||
opts ...ReceiveOption,
|
opts ReceiveOptions,
|
||||||
) error {
|
) error {
|
||||||
if w.PublishUpdate == nil {
|
if w.PublishUpdate == nil {
|
||||||
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
return fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||||
}
|
}
|
||||||
|
|
||||||
rs := receiveSettings{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(&rs)
|
|
||||||
}
|
|
||||||
|
|
||||||
source, _ := nostr.NormalizeHTTPURL(mint)
|
source, _ := nostr.NormalizeHTTPURL(mint)
|
||||||
destination := rs.intoMint
|
destination := opts.IntoMint
|
||||||
|
|
||||||
swapSettings := swapSettings{}
|
swapSettings := swapSettings{}
|
||||||
|
|
||||||
@@ -138,7 +120,7 @@ saveproofs:
|
|||||||
{
|
{
|
||||||
EventID: newToken.event.ID,
|
EventID: newToken.event.ID,
|
||||||
Created: true,
|
Created: true,
|
||||||
IsNutzap: rs.isNutzap,
|
IsNutzap: opts.IsNutzap,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
createdAt: nostr.Now(),
|
createdAt: nostr.Now(),
|
||||||
|
|||||||
@@ -4,16 +4,16 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
|
"fiatjaf.com/nostr/nip60/client"
|
||||||
"github.com/elnosh/gonuts/cashu"
|
"github.com/elnosh/gonuts/cashu"
|
||||||
"github.com/elnosh/gonuts/cashu/nuts/nut04"
|
"github.com/elnosh/gonuts/cashu/nuts/nut04"
|
||||||
"fiatjaf.com/nostr/nip60/client"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (w *Wallet) SendExternal(
|
func (w *Wallet) SendExternal(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
mint string,
|
mint string,
|
||||||
targetAmount uint64,
|
targetAmount uint64,
|
||||||
opts ...SendOption,
|
opts SendOptions,
|
||||||
) (cashu.Proofs, error) {
|
) (cashu.Proofs, error) {
|
||||||
if w.PublishUpdate == nil {
|
if w.PublishUpdate == nil {
|
||||||
return nil, fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
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)
|
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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,30 +15,11 @@ import (
|
|||||||
"github.com/elnosh/gonuts/cashu/nuts/nut11"
|
"github.com/elnosh/gonuts/cashu/nuts/nut11"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SendOption func(opts *sendSettings)
|
// SendOptions contains options for sending tokens
|
||||||
|
type SendOptions struct {
|
||||||
type sendSettings struct {
|
SpecificSourceMint string
|
||||||
specificMint string
|
P2PK *btcec.PublicKey
|
||||||
p2pk *btcec.PublicKey
|
RefundTimelock nostr.Timestamp
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type chosenTokens struct {
|
type chosenTokens struct {
|
||||||
@@ -49,26 +30,21 @@ type chosenTokens struct {
|
|||||||
keysets []nut02.Keyset
|
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 {
|
if w.PublishUpdate == nil {
|
||||||
return nil, "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
return nil, "", fmt.Errorf("can't do write operations: missing PublishUpdate function")
|
||||||
}
|
}
|
||||||
|
|
||||||
ss := &sendSettings{}
|
|
||||||
for _, opt := range opts {
|
|
||||||
opt(ss)
|
|
||||||
}
|
|
||||||
|
|
||||||
w.tokensMu.Lock()
|
w.tokensMu.Lock()
|
||||||
defer w.tokensMu.Unlock()
|
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 {
|
if err != nil {
|
||||||
return nil, "", err
|
return nil, "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
swapSettings := swapSettings{}
|
swapSettings := swapSettings{}
|
||||||
if ss.p2pk != nil {
|
if opts.P2PK != nil {
|
||||||
if info, err := client.GetMintInfo(ctx, chosen.mint); err != nil || !info.Nuts.Nut11.Supported {
|
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)
|
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{
|
tags := nut11.P2PKTags{
|
||||||
NSigs: 1,
|
NSigs: 1,
|
||||||
Locktime: 0,
|
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.Refund = []*btcec.PublicKey{w.PublicKey}
|
||||||
tags.Locktime = ss.refundtimelock
|
tags.Locktime = int64(opts.RefundTimelock)
|
||||||
}
|
}
|
||||||
|
|
||||||
swapSettings.spendingCondition = &nut10.SpendingCondition{
|
swapSettings.spendingCondition = &nut10.SpendingCondition{
|
||||||
Kind: nut10.P2PK,
|
Kind: nut10.P2PK,
|
||||||
Data: hex.EncodeToString(ss.p2pk.SerializeCompressed()),
|
Data: hex.EncodeToString(opts.P2PK.SerializeCompressed()),
|
||||||
Tags: nut11.SerializeP2PKTags(tags),
|
Tags: nut11.SerializeP2PKTags(tags),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -188,12 +164,12 @@ func (w *Wallet) saveChangeAndDeleteUsedTokens(
|
|||||||
func (w *Wallet) getProofsForSending(
|
func (w *Wallet) getProofsForSending(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
amount uint64,
|
amount uint64,
|
||||||
specificMint string,
|
fromMint string,
|
||||||
excludeMints []string,
|
excludeMints []string,
|
||||||
) (chosenTokens, uint64, error) {
|
) (chosenTokens, uint64, error) {
|
||||||
byMint := make(map[string]chosenTokens)
|
byMint := make(map[string]chosenTokens)
|
||||||
for t, token := range w.Tokens {
|
for t, token := range w.Tokens {
|
||||||
if specificMint != "" && token.Mint != specificMint {
|
if fromMint != "" && token.Mint != fromMint {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if slices.Contains(excludeMints, token.Mint) {
|
if slices.Contains(excludeMints, token.Mint) {
|
||||||
|
|||||||
@@ -14,6 +14,11 @@ import (
|
|||||||
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
"github.com/decred/dcrd/dcrec/secp256k1/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// WalletOptions contains options for loading a wallet
|
||||||
|
type WalletOptions struct {
|
||||||
|
WithHistory bool
|
||||||
|
}
|
||||||
|
|
||||||
type Wallet struct {
|
type Wallet struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
tokensMu sync.Mutex
|
tokensMu sync.Mutex
|
||||||
@@ -52,17 +57,9 @@ func LoadWallet(
|
|||||||
kr nostr.Keyer,
|
kr nostr.Keyer,
|
||||||
pool *nostr.Pool,
|
pool *nostr.Pool,
|
||||||
relays []string,
|
relays []string,
|
||||||
|
opts WalletOptions,
|
||||||
) *Wallet {
|
) *Wallet {
|
||||||
return loadWalletFromPool(ctx, kr, pool, relays, false)
|
return loadWalletFromPool(ctx, kr, pool, relays, opts)
|
||||||
}
|
|
||||||
|
|
||||||
func LoadWalletWithHistory(
|
|
||||||
ctx context.Context,
|
|
||||||
kr nostr.Keyer,
|
|
||||||
pool *nostr.Pool,
|
|
||||||
relays []string,
|
|
||||||
) *Wallet {
|
|
||||||
return loadWalletFromPool(ctx, kr, pool, relays, true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func loadWalletFromPool(
|
func loadWalletFromPool(
|
||||||
@@ -70,7 +67,7 @@ func loadWalletFromPool(
|
|||||||
kr nostr.Keyer,
|
kr nostr.Keyer,
|
||||||
pool *nostr.Pool,
|
pool *nostr.Pool,
|
||||||
relays []string,
|
relays []string,
|
||||||
withHistory bool,
|
opts WalletOptions,
|
||||||
) *Wallet {
|
) *Wallet {
|
||||||
pk, err := kr.GetPublicKey(ctx)
|
pk, err := kr.GetPublicKey(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -78,7 +75,7 @@ func loadWalletFromPool(
|
|||||||
}
|
}
|
||||||
|
|
||||||
kinds := []nostr.Kind{17375, 7375}
|
kinds := []nostr.Kind{17375, 7375}
|
||||||
if withHistory {
|
if opts.WithHistory {
|
||||||
kinds = append(kinds, 7376)
|
kinds = append(kinds, 7376)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
|
|
||||||
"fiatjaf.com/nostr"
|
"fiatjaf.com/nostr"
|
||||||
"fiatjaf.com/nostr/nip60"
|
"fiatjaf.com/nostr/nip60"
|
||||||
|
"github.com/btcsuite/btcd/btcec/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps")
|
var NutzapsNotAccepted = errors.New("user doesn't accept nutzaps")
|
||||||
@@ -27,7 +28,11 @@ func SendNutzap(
|
|||||||
amount uint64,
|
amount uint64,
|
||||||
message string,
|
message string,
|
||||||
) (chan nostr.PublishResult, error) {
|
) (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 {
|
if ie == nil {
|
||||||
return nil, NutzapsNotAccepted
|
return nil, NutzapsNotAccepted
|
||||||
}
|
}
|
||||||
@@ -60,9 +65,17 @@ func SendNutzap(
|
|||||||
nutzap.Tags = append(nutzap.Tags, nostr.Tag{"e", eventId.Hex()})
|
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
|
// check if we have enough tokens in any of these mints
|
||||||
for mint := range getEligibleTokensWeHave(info.Mints, w.Tokens, amount) {
|
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 {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -83,7 +96,9 @@ func SendNutzap(
|
|||||||
|
|
||||||
// we don't have tokens at the desired target mint, so we first have to create some
|
// we don't have tokens at the desired target mint, so we first have to create some
|
||||||
for _, mint := range info.Mints {
|
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 err != nil {
|
||||||
if strings.Contains(err.Error(), "generate mint quote") {
|
if strings.Contains(err.Error(), "generate mint quote") {
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in New Issue
Block a user