mirror of
https://github.com/nbd-wtf/go-nostr.git
synced 2025-11-18 18:16:50 +01:00
nip60: fixes, actual Cashu stuff and a wallet.Receive() method.
This commit is contained in:
134
nip60/swap.go
Normal file
134
nip60/swap.go
Normal file
@@ -0,0 +1,134 @@
|
||||
package nip60
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"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"
|
||||
"github.com/nbd-wtf/go-nostr/nip60/client"
|
||||
)
|
||||
|
||||
// lightningMeltMint does the lightning dance of moving funds between mints
|
||||
func lightningMeltMint(
|
||||
ctx context.Context,
|
||||
proofs cashu.Proofs,
|
||||
from string,
|
||||
fromKeysets []nut02.Keyset,
|
||||
to string,
|
||||
) (newProofs cashu.Proofs, err error, canTryWithAnotherTargetMint bool, manualActionRequired bool) {
|
||||
// get active keyset of target mint
|
||||
keyset, err := client.GetActiveKeyset(ctx, to)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get keyset keys for %s: %w", to, err), true, false
|
||||
}
|
||||
|
||||
// unblind the signatures from the promises and build the proofs
|
||||
keysetKeys, err := parseKeysetKeys(keyset.Keys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("target mint %s sent us an invalid keyset: %w", to, err), true, false
|
||||
}
|
||||
|
||||
// now we start the melt-mint process in multiple attempts
|
||||
invoicePct := 0.99
|
||||
proofsAmount := proofs.Amount()
|
||||
amount := float64(proofsAmount) * invoicePct
|
||||
fee := uint64(calculateFee(proofs, fromKeysets))
|
||||
var meltQuote string
|
||||
var mintQuote string
|
||||
for range 10 {
|
||||
// request _mint_ quote to the 'to' mint -- this will generate an invoice
|
||||
mintResp, err := client.PostMintQuoteBolt11(ctx, to, nut04.PostMintQuoteBolt11Request{
|
||||
Amount: uint64(amount) - fee,
|
||||
Unit: cashu.Sat.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error requesting mint quote from %s: %w", to, err), true, false
|
||||
}
|
||||
|
||||
// request _melt_ quote from the 'from' mint
|
||||
// this melt will pay the invoice generated from the previous mint quote request
|
||||
meltResp, err := client.PostMeltQuoteBolt11(ctx, from, nut05.PostMeltQuoteBolt11Request{
|
||||
Request: mintResp.Request,
|
||||
Unit: cashu.Sat.String(),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error requesting melt quote from %s: %w", from, err), false, false
|
||||
}
|
||||
|
||||
// if amount in proofs is less than amount asked from mint in melt request,
|
||||
// lower the amount for mint request (because of lighting fees?)
|
||||
if meltResp.Amount+meltResp.FeeReserve+fee > proofsAmount {
|
||||
invoicePct -= 0.01
|
||||
amount *= invoicePct
|
||||
} else {
|
||||
meltQuote = meltResp.Quote
|
||||
mintQuote = mintResp.Quote
|
||||
goto meltworked
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("stop trying to do the melt because the mint part is too expensive"), true, false
|
||||
|
||||
meltworked:
|
||||
// request from mint to pay invoice from the mint quote request
|
||||
_, err = client.PostMeltBolt11(ctx, from, nut05.PostMeltBolt11Request{
|
||||
Quote: meltQuote,
|
||||
Inputs: proofs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error melting token: %v", err), false, true
|
||||
}
|
||||
|
||||
sleepTime := time.Millisecond * 200
|
||||
failures := 0
|
||||
for range 12 {
|
||||
sleepTime *= 2
|
||||
time.Sleep(sleepTime)
|
||||
|
||||
// check if the _mint_ invoice was paid
|
||||
mintQuoteStatusResp, err := client.GetMintQuoteState(ctx, to, mintQuote)
|
||||
if err != nil {
|
||||
failures++
|
||||
if failures > 10 {
|
||||
return nil, fmt.Errorf(
|
||||
"target mint %s failed to answer to our mint quote checks (%s): %w; a manual fix is needed",
|
||||
to, meltQuote, err,
|
||||
), false, true
|
||||
}
|
||||
}
|
||||
|
||||
// if it wasn't paid try again
|
||||
if mintQuoteStatusResp.State != nut04.Paid {
|
||||
continue
|
||||
}
|
||||
|
||||
// if it got paid make proceed to get proofs
|
||||
split := []uint64{1, 2, 3, 4}
|
||||
blindedMessages, secrets, rs, err := createBlindedMessages(split, keyset.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error creating blinded messages: %v", err), false, true
|
||||
}
|
||||
|
||||
// request mint to sign the blinded messages
|
||||
mintResponse, err := client.PostMintBolt11(ctx, to, nut04.PostMintBolt11Request{
|
||||
Quote: mintQuote,
|
||||
Outputs: blindedMessages,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("mint request to %s failed (%s): %w", to, mintQuote, err), false, true
|
||||
}
|
||||
|
||||
proofs, err := constructProofs(mintResponse.Signatures, blindedMessages, secrets, rs, keysetKeys)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error constructing proofs: %w", err), false, true
|
||||
}
|
||||
|
||||
return proofs, nil, false, false
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("we gave up waiting for the invoice at %s to be paid: %s", to, meltQuote), false, true
|
||||
}
|
||||
Reference in New Issue
Block a user