Skip to content

Commit

Permalink
wallet and cli commands for pending proofs and quotes
Browse files Browse the repository at this point in the history
  • Loading branch information
elnosh committed Oct 19, 2024
1 parent 21f7e6d commit 98328a0
Show file tree
Hide file tree
Showing 7 changed files with 285 additions and 30 deletions.
58 changes: 58 additions & 0 deletions cmd/nutw/nutw.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ func main() {
sendCmd,
receiveCmd,
payCmd,
quotesCmd,
p2pkLockCmd,
mnemonicCmd,
restoreCmd,
Expand Down Expand Up @@ -144,6 +145,11 @@ func getBalance(ctx *cli.Context) error {
}

fmt.Printf("\nTotal balance: %v sats\n", totalBalance)

pendingBalance := nutw.PendingBalance()
if pendingBalance > 0 {
fmt.Printf("Pending balance: %v sats\n", pendingBalance)
}
return nil
}

Expand Down Expand Up @@ -416,6 +422,58 @@ func pay(ctx *cli.Context) error {
return nil
}

const (
checkFlag = "check"
)

var quotesCmd = &cli.Command{
Name: "quotes",
Usage: "list and check status of pending melt quotes",
Before: setupWallet,
Flags: []cli.Flag{
&cli.StringFlag{
Name: checkFlag,
Usage: "check state of quote",
},
},
Action: quotes,
}

func quotes(ctx *cli.Context) error {
pendingQuotes := nutw.GetPendingMeltQuotes()

if ctx.IsSet(checkFlag) {
quote := ctx.String(checkFlag)

quoteResponse, err := nutw.CheckMeltQuoteState(quote)
if err != nil {
printErr(err)
}

switch quoteResponse.State {
case nut05.Paid:
fmt.Printf("Invoice for quote '%v' was paid. Preimage: %v\n", quote, quoteResponse.Preimage)
case nut05.Pending:
fmt.Println("payment is still pending")
case nut05.Unpaid:
fmt.Println("quote was not paid")
}

return nil
}

if len(pendingQuotes) > 0 {
fmt.Println("Pending quotes: ")
for _, quote := range pendingQuotes {
fmt.Printf("ID: %v\n", quote)
}
} else {
fmt.Println("no pending quotes")
}

return nil
}

var p2pkLockCmd = &cli.Command{
Name: "p2pk-lock",
Usage: "Retrieves a public key to which ecash can be locked",
Expand Down
4 changes: 4 additions & 0 deletions mint/config.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package mint

import (
"time"

"github.com/elnosh/gonuts/cashu/nuts/nut06"
"github.com/elnosh/gonuts/mint/lightning"
)
Expand All @@ -23,6 +25,8 @@ type Config struct {
Limits MintLimits
LightningClient lightning.Client
LogLevel LogLevel
// NOTE: using this value for testing
MeltTimeout *time.Duration
}

type MintInfo struct {
Expand Down
10 changes: 8 additions & 2 deletions mint/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ import (
type MintServer struct {
httpServer *http.Server
mint *Mint
// NOTE: using this value for testing
meltTimeout *time.Duration
}

func (ms *MintServer) Start() error {
Expand All @@ -46,7 +48,7 @@ func SetupMintServer(config Config) (*MintServer, error) {
return nil, err
}

mintServer := &MintServer{mint: mint}
mintServer := &MintServer{mint: mint, meltTimeout: config.MeltTimeout}
err = mintServer.setupHttpServer(config.Port)
if err != nil {
return nil, err
Expand Down Expand Up @@ -449,7 +451,11 @@ func (ms *MintServer) meltTokens(rw http.ResponseWriter, req *http.Request) {
return
}

ctx, cancel := context.WithTimeout(context.Background(), time.Minute*1)
timeout := time.Minute * 1
if ms.meltTimeout != nil {
timeout = *ms.meltTimeout
}
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()

meltQuote, err := ms.mint.MeltTokens(ctx, method, meltTokensRequest.Quote, meltTokensRequest.Inputs)
Expand Down
2 changes: 2 additions & 0 deletions testutils/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ func mintConfig(
return nil, fmt.Errorf("error setting LND client: %v", err)
}

timeout := time.Second * 2
mintConfig := &mint.Config{
DerivationPathIdx: 0,
Port: port,
Expand All @@ -247,6 +248,7 @@ func mintConfig(
Limits: limits,
LightningClient: lndClient,
LogLevel: mint.Disable,
MeltTimeout: &timeout,
}

return mintConfig, nil
Expand Down
12 changes: 7 additions & 5 deletions wallet/storage/bolt.go
Original file line number Diff line number Diff line change
Expand Up @@ -265,11 +265,11 @@ func (db *BoltDB) DeletePendingProofsByQuoteId(quoteId string) error {
return err
}

y, err := hex.DecodeString(proof.Y)
if err != nil {
return err
}
if proof.MeltQuoteId == quoteId {
y, err := hex.DecodeString(proof.Y)
if err != nil {
return err
}
if err := pendingProofsb.Delete(y); err != nil {
return err
}
Expand All @@ -292,7 +292,9 @@ func (db *BoltDB) DeletePendingProofs(Ys []string) error {
if val == nil {
return ProofNotFound
}
return pendingProofsb.Delete(y)
if err := pendingProofsb.Delete(y); err != nil {
return err
}
}

return nil
Expand Down
71 changes: 48 additions & 23 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,18 @@ func (w *Wallet) GetBalanceByMints() map[string]uint64 {
return mintsBalances
}

func (w *Wallet) PendingBalance() uint64 {
return Amount(w.db.GetPendingProofs())
}

func Amount(proofs []storage.DBProof) uint64 {
var totalAmount uint64 = 0
for _, proof := range proofs {
totalAmount += proof.Amount
}
return totalAmount
}

// RequestMint requests a mint quote to the wallet's current mint
// for the specified amount
func (w *Wallet) RequestMint(amount uint64) (*nut04.PostMintQuoteBolt11Response, error) {
Expand All @@ -340,9 +352,8 @@ func (w *Wallet) RequestMint(amount uint64) (*nut04.PostMintQuoteBolt11Response,
QuoteExpiry: mintResponse.Expiry,
}

err = w.db.SaveInvoice(invoice)
if err != nil {
return nil, err
if err = w.db.SaveInvoice(invoice); err != nil {
return nil, fmt.Errorf("error saving invoice: %v", err)
}

return mintResponse, nil
Expand Down Expand Up @@ -731,7 +742,7 @@ func (w *Wallet) CheckMeltQuoteState(quoteId string) (*nut05.PostMeltQuoteBolt11
}
}

if quote.State != nut05.Unknown || quote.State == nut05.Unpaid {
if (quote.State == nut05.Unknown && !quote.Paid) || quote.State == nut05.Unpaid {
pendingProofs := w.db.GetPendingProofsByQuoteId(quoteId)
// if there were any pending proofs tied to this quote, remove them from pending
// and add them to available proofs for wallet to use
Expand All @@ -756,16 +767,15 @@ func (w *Wallet) CheckMeltQuoteState(quoteId string) (*nut05.PostMeltQuoteBolt11
return nil, fmt.Errorf("error storing proofs: %v", err)
}
}

}
}

return quote, nil
}

// Melt will request the mint to pay the given invoice
func (w *Wallet) Melt(invoice, mint string) (*nut05.PostMeltQuoteBolt11Response, error) {
selectedMint, ok := w.mints[mint]
func (w *Wallet) Melt(invoice, mintURL string) (*nut05.PostMeltQuoteBolt11Response, error) {
selectedMint, ok := w.mints[mintURL]
if !ok {
return nil, ErrMintNotExist
}
Expand All @@ -776,7 +786,7 @@ func (w *Wallet) Melt(invoice, mint string) (*nut05.PostMeltQuoteBolt11Response,
}

meltRequest := nut05.PostMeltQuoteBolt11Request{Request: invoice, Unit: "sat"}
meltQuoteResponse, err := PostMeltQuoteBolt11(mint, meltRequest)
meltQuoteResponse, err := PostMeltQuoteBolt11(mintURL, meltRequest)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -809,7 +819,7 @@ func (w *Wallet) Melt(invoice, mint string) (*nut05.PostMeltQuoteBolt11Response,
quoteInvoice := storage.Invoice{
TransactionType: storage.Melt,
Id: meltQuoteResponse.Quote,
Mint: mint,
Mint: mintURL,
QuoteAmount: amountNeeded,
InvoiceAmount: uint64(bolt11.MSatoshi / 1000),
PaymentRequest: invoice,
Expand All @@ -826,22 +836,15 @@ func (w *Wallet) Melt(invoice, mint string) (*nut05.PostMeltQuoteBolt11Response,
Inputs: proofs,
Outputs: outputs,
}
meltBolt11Response, err := PostMeltBolt11(mint, meltBolt11Request)
meltBolt11Response, err := PostMeltBolt11(mintURL, meltBolt11Request)
if err != nil {
cashuErr, ok := err.(cashu.Error)
if ok {
// if the error was a failed lightning payment
// remove proofs from pending and add them back to available proofs to wallet
if cashuErr.Code == cashu.LightningPaymentErrCode {
if err := w.db.SaveProofs(proofs); err != nil {
return nil, fmt.Errorf("error storing proofs: %v", err)
}
if err := w.db.DeletePendingProofsByQuoteId(meltQuoteResponse.Quote); err != nil {
return nil, fmt.Errorf("error removing pending proofs: %v", err)
}
}
// if there was error with melt, remove proofs from pending and save them for use
if err := w.db.SaveProofs(proofs); err != nil {
return nil, fmt.Errorf("error storing proofs: %v", err)
}
if err := w.db.DeletePendingProofsByQuoteId(meltQuoteResponse.Quote); err != nil {
return nil, fmt.Errorf("error removing pending proofs: %v", err)
}
// if some other error, leave proofs as pending
return nil, err
}

Expand Down Expand Up @@ -1716,6 +1719,9 @@ func Restore(walletPath, mnemonic string, mintsToRestore []string) (cashu.Proofs
// create batch of 100 blinded messages
for i := 0; i < 100; i++ {
secret, r, err := generateDeterministicSecret(keysetDerivationPath, counter)
if err != nil {
return nil, err
}
B_, r, err := crypto.BlindMessage(secret, r)
if err != nil {
return nil, err
Expand Down Expand Up @@ -1805,6 +1811,25 @@ func Restore(walletPath, mnemonic string, mintsToRestore []string) (cashu.Proofs
return proofsRestored, nil
}

func (w *Wallet) GetPendingProofs() []storage.DBProof {
return w.db.GetPendingProofs()
}

// GetPendingMeltQuotes return a list of pending quote ids
func (w *Wallet) GetPendingMeltQuotes() []string {
pendingProofs := w.db.GetPendingProofs()
pendingProofsMap := make(map[string][]storage.DBProof)
var pendingQuotes []string
for _, proof := range pendingProofs {
if _, ok := pendingProofsMap[proof.MeltQuoteId]; !ok {
pendingQuotes = append(pendingQuotes, proof.MeltQuoteId)
}
pendingProofsMap[proof.MeltQuoteId] = append(pendingProofsMap[proof.MeltQuoteId], proof)
}

return pendingQuotes
}

func (w *Wallet) GetInvoiceByPaymentRequest(pr string) (*storage.Invoice, error) {
bolt11, err := decodepay.Decodepay(pr)
if err != nil {
Expand Down
Loading

0 comments on commit 98328a0

Please sign in to comment.