nip60: fix spending conditions over SendExternal() and SendInternal()
This commit is contained in:
@@ -5,11 +5,12 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut02"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut04"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut05"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
)
|
||||
|
||||
type lightningSwapStatus int
|
||||
@@ -95,7 +96,7 @@ inspectmeltstatusresponse:
|
||||
}
|
||||
}
|
||||
|
||||
proofs, err = redeemMinted(ctx, to, mintQuote, amount)
|
||||
proofs, err = redeemMinted(ctx, to, mintQuote, amount, nil)
|
||||
if err != nil {
|
||||
return nil,
|
||||
fmt.Errorf("failed to redeem minted proofs at %s (after successfully melting at %s): %w", to, from, err),
|
||||
@@ -105,11 +106,13 @@ inspectmeltstatusresponse:
|
||||
return proofs, nil, nothingCanBeDone
|
||||
}
|
||||
|
||||
// redeemMinted just downloads proofs from a lightning invoice paid at some mint.
|
||||
func redeemMinted(
|
||||
ctx context.Context,
|
||||
mint string,
|
||||
mintQuote string,
|
||||
mintAmount uint64,
|
||||
spendingCondition *nut10.SpendingCondition,
|
||||
) (cashu.Proofs, error) {
|
||||
// source mint says it has paid the invoice, now check it against the target mint
|
||||
// check if the _mint_ invoice was paid
|
||||
@@ -139,7 +142,7 @@ func redeemMinted(
|
||||
return nil, fmt.Errorf("target mint %s sent us an invalid keyset: %w", mint, err)
|
||||
}
|
||||
split := cashu.AmountSplit(mintAmount)
|
||||
blindedMessages, secrets, rs, err := createBlindedMessages(split, keyset.Id, nil)
|
||||
blindedMessages, secrets, rs, err := createBlindedMessages(split, keyset.Id, spendingCondition)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating blinded messages: %w", err)
|
||||
}
|
||||
|
||||
@@ -34,5 +34,5 @@ func (w *Wallet) SendExternal(
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return redeemMinted(ctx, mint, mintResp.Quote, targetAmount)
|
||||
return redeemMinted(ctx, mint, mintResp.Quote, targetAmount, opts.asSpendingCondition(w.PublicKey))
|
||||
}
|
||||
|
||||
64
nip60/send-internal.go
Normal file
64
nip60/send-internal.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"fiatjaf.com/nostr"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
"github.com/elnosh/gonuts/cashu"
|
||||
)
|
||||
|
||||
func (w *Wallet) SendInternal(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")
|
||||
}
|
||||
|
||||
w.tokensMu.Lock()
|
||||
defer w.tokensMu.Unlock()
|
||||
|
||||
chosen, _, err := w.getProofsForSending(ctx, amount, opts.SpecificSourceMint, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
if opts.Hashlock != [32]byte{} {
|
||||
if info, err := client.GetMintInfo(ctx, chosen.mint); err != nil || !info.Nuts.Nut14.Supported {
|
||||
return nil, chosen.mint, fmt.Errorf("mint doesn't support htlc: %w", err)
|
||||
}
|
||||
} else 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)
|
||||
}
|
||||
}
|
||||
|
||||
swapSettings := swapSettings{
|
||||
spendingCondition: opts.asSpendingCondition(w.PublicKey),
|
||||
}
|
||||
|
||||
// get new proofs
|
||||
proofsToSend, changeProofs, err := w.swapProofs(ctx, chosen.mint, chosen.proofs, amount, swapSettings)
|
||||
if err != nil {
|
||||
return nil, chosen.mint, err
|
||||
}
|
||||
|
||||
he := HistoryEntry{
|
||||
event: &nostr.Event{},
|
||||
TokenReferences: make([]TokenRef, 0, 5),
|
||||
createdAt: nostr.Now(),
|
||||
In: false,
|
||||
Amount: chosen.proofs.Amount() - changeProofs.Amount(),
|
||||
}
|
||||
|
||||
if err := w.saveChangeAndDeleteUsedTokens(ctx, chosen.mint, changeProofs, chosen.tokenIndexes, &he); err != nil {
|
||||
return nil, chosen.mint, err
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
if err := he.toEvent(ctx, w.kr, he.event); err == nil {
|
||||
w.PublishUpdate(*he.event, nil, nil, nil, true)
|
||||
}
|
||||
w.Unlock()
|
||||
|
||||
return proofsToSend, chosen.mint, nil
|
||||
}
|
||||
111
nip60/send.go
111
nip60/send.go
@@ -20,6 +20,54 @@ type SendOptions struct {
|
||||
SpecificSourceMint string
|
||||
P2PK *btcec.PublicKey
|
||||
RefundTimelock nostr.Timestamp
|
||||
Hashlock [32]byte
|
||||
}
|
||||
|
||||
func (opts SendOptions) asSpendingCondition(refund *btcec.PublicKey) *nut10.SpendingCondition {
|
||||
if opts.Hashlock != [32]byte{} {
|
||||
// when we have an HTLC condition:
|
||||
// (it can also include a P2PK and a timelock)
|
||||
tags := nut11.P2PKTags{
|
||||
NSigs: 1,
|
||||
Locktime: 0,
|
||||
Sigflag: nut11.SIGINPUTS,
|
||||
}
|
||||
if opts.P2PK != nil {
|
||||
tags.Pubkeys = []*btcec.PublicKey{opts.P2PK}
|
||||
}
|
||||
if opts.RefundTimelock != 0 {
|
||||
tags.Refund = []*btcec.PublicKey{refund}
|
||||
tags.Locktime = int64(opts.RefundTimelock)
|
||||
}
|
||||
|
||||
return &nut10.SpendingCondition{
|
||||
Kind: nut10.HTLC,
|
||||
Data: hex.EncodeToString(opts.Hashlock[:]),
|
||||
Tags: nut11.SerializeP2PKTags(tags),
|
||||
}
|
||||
} else if opts.P2PK != nil {
|
||||
// otherwise when it is just a P2PK condition with no hashlock
|
||||
// (may also have a timelock)
|
||||
|
||||
tags := nut11.P2PKTags{
|
||||
NSigs: 1,
|
||||
Locktime: 0,
|
||||
Pubkeys: []*btcec.PublicKey{opts.P2PK},
|
||||
Sigflag: nut11.SIGINPUTS,
|
||||
}
|
||||
if opts.RefundTimelock != 0 {
|
||||
tags.Refund = []*btcec.PublicKey{refund}
|
||||
tags.Locktime = int64(opts.RefundTimelock)
|
||||
}
|
||||
|
||||
return &nut10.SpendingCondition{
|
||||
Kind: nut10.P2PK,
|
||||
Data: hex.EncodeToString(opts.P2PK.SerializeCompressed()),
|
||||
Tags: nut11.SerializeP2PKTags(tags),
|
||||
}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type chosenTokens struct {
|
||||
@@ -30,69 +78,6 @@ type chosenTokens struct {
|
||||
keysets []nut02.Keyset
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
w.tokensMu.Lock()
|
||||
defer w.tokensMu.Unlock()
|
||||
|
||||
chosen, _, err := w.getProofsForSending(ctx, amount, opts.SpecificSourceMint, nil)
|
||||
if err != nil {
|
||||
return nil, "", err
|
||||
}
|
||||
|
||||
swapSettings := swapSettings{}
|
||||
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)
|
||||
}
|
||||
|
||||
tags := nut11.P2PKTags{
|
||||
NSigs: 1,
|
||||
Locktime: 0,
|
||||
Pubkeys: []*btcec.PublicKey{opts.P2PK},
|
||||
}
|
||||
if opts.RefundTimelock != 0 {
|
||||
tags.Refund = []*btcec.PublicKey{w.PublicKey}
|
||||
tags.Locktime = int64(opts.RefundTimelock)
|
||||
}
|
||||
|
||||
swapSettings.spendingCondition = &nut10.SpendingCondition{
|
||||
Kind: nut10.P2PK,
|
||||
Data: hex.EncodeToString(opts.P2PK.SerializeCompressed()),
|
||||
Tags: nut11.SerializeP2PKTags(tags),
|
||||
}
|
||||
}
|
||||
|
||||
// get new proofs
|
||||
proofsToSend, changeProofs, err := w.swapProofs(ctx, chosen.mint, chosen.proofs, amount, swapSettings)
|
||||
if err != nil {
|
||||
return nil, chosen.mint, err
|
||||
}
|
||||
|
||||
he := HistoryEntry{
|
||||
event: &nostr.Event{},
|
||||
TokenReferences: make([]TokenRef, 0, 5),
|
||||
createdAt: nostr.Now(),
|
||||
In: false,
|
||||
Amount: chosen.proofs.Amount() - changeProofs.Amount(),
|
||||
}
|
||||
|
||||
if err := w.saveChangeAndDeleteUsedTokens(ctx, chosen.mint, changeProofs, chosen.tokenIndexes, &he); err != nil {
|
||||
return nil, chosen.mint, err
|
||||
}
|
||||
|
||||
w.Lock()
|
||||
if err := he.toEvent(ctx, w.kr, he.event); err == nil {
|
||||
w.PublishUpdate(*he.event, nil, nil, nil, true)
|
||||
}
|
||||
w.Unlock()
|
||||
|
||||
return proofsToSend, chosen.mint, nil
|
||||
}
|
||||
|
||||
func (w *Wallet) saveChangeAndDeleteUsedTokens(
|
||||
ctx context.Context,
|
||||
mintURL string,
|
||||
|
||||
@@ -5,12 +5,12 @@ import (
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"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/nut03"
|
||||
"github.com/elnosh/gonuts/cashu/nuts/nut10"
|
||||
"fiatjaf.com/nostr/nip60/client"
|
||||
)
|
||||
|
||||
type swapSettings struct {
|
||||
|
||||
@@ -86,7 +86,7 @@ func SendNutzap(
|
||||
continue
|
||||
}
|
||||
|
||||
proofs, _, err := w.Send(ctx, amount, nip60.SendOptions{
|
||||
proofs, _, err := w.SendInternal(ctx, amount, nip60.SendOptions{
|
||||
P2PK: p2pk,
|
||||
SpecificSourceMint: mint,
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user