Skip to content

Commit

Permalink
Merge pull request #31 from elnosh/nut04-state
Browse files Browse the repository at this point in the history
nut04 - state field
  • Loading branch information
elnosh authored Jul 8, 2024
2 parents f1f35b6 + 05e6a82 commit abd55b0
Show file tree
Hide file tree
Showing 5 changed files with 176 additions and 18 deletions.
79 changes: 77 additions & 2 deletions cashu/nuts/nut04/nut04.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,45 @@
// [NUT-04]: https://github.com/cashubtc/nuts/blob/main/04.md
package nut04

import "github.com/elnosh/gonuts/cashu"
import (
"encoding/json"

"github.com/elnosh/gonuts/cashu"
)

type State int

const (
Unpaid State = iota
Paid
Issued
Unknown
)

func (state State) String() string {
switch state {
case Unpaid:
return "UNPAID"
case Paid:
return "PAID"
case Issued:
return "ISSUED"
default:
return "unknown"
}
}

func StringToState(state string) State {
switch state {
case "UNPAID":
return Unpaid
case "PAID":
return Paid
case "ISSUED":
return Issued
}
return Unknown
}

type PostMintQuoteBolt11Request struct {
Amount uint64 `json:"amount"`
Expand All @@ -13,7 +51,8 @@ type PostMintQuoteBolt11Request struct {
type PostMintQuoteBolt11Response struct {
Quote string `json:"quote"`
Request string `json:"request"`
Paid bool `json:"paid"`
State State `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use State instead
Expiry int64 `json:"expiry"`
}

Expand All @@ -25,3 +64,39 @@ type PostMintBolt11Request struct {
type PostMintBolt11Response struct {
Signatures cashu.BlindedSignatures `json:"signatures"`
}

type TempQuote struct {
Quote string `json:"quote"`
Request string `json:"request"`
State string `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use State instead
Expiry int64 `json:"expiry"`
}

func (quoteResponse *PostMintQuoteBolt11Response) MarshalJSON() ([]byte, error) {
var tempQuote = TempQuote{
Quote: quoteResponse.Quote,
Request: quoteResponse.Request,
State: quoteResponse.State.String(),
Paid: quoteResponse.Paid,
Expiry: quoteResponse.Expiry,
}
return json.Marshal(tempQuote)
}

func (quoteResponse *PostMintQuoteBolt11Response) UnmarshalJSON(data []byte) error {
tempQuote := &TempQuote{}

if err := json.Unmarshal(data, tempQuote); err != nil {
return err
}

quoteResponse.Quote = tempQuote.Quote
quoteResponse.Request = tempQuote.Request
state := StringToState(tempQuote.State)
quoteResponse.State = state
quoteResponse.Paid = tempQuote.Paid
quoteResponse.Expiry = tempQuote.Expiry

return nil
}
14 changes: 11 additions & 3 deletions mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ func (m *Mint) RequestMintQuote(method string, amount uint64, unit string) (nut0
reqMintQuoteResponse := nut04.PostMintQuoteBolt11Response{
Quote: invoice.Id,
Request: invoice.PaymentRequest,
Paid: invoice.Settled,
State: nut04.Unpaid,
Paid: invoice.Settled, // DEPRECATED: remove after wallets have upgraded
Expiry: invoice.Expiry,
}

Expand All @@ -144,15 +145,22 @@ func (m *Mint) GetMintQuoteState(method, quoteId string) (nut04.PostMintQuoteBol
msg := fmt.Sprintf("error getting invoice status: %v", err)
return nut04.PostMintQuoteBolt11Response{}, cashu.BuildCashuError(msg, cashu.InvoiceErrCode)
}
if status.Settled && status.Settled != invoice.Settled {

state := nut04.Unpaid
if status.Settled {
invoice.Settled = status.Settled
state = nut04.Paid
if invoice.Redeemed {
state = nut04.Issued
}
m.db.SaveInvoice(*invoice)
}

quoteState := nut04.PostMintQuoteBolt11Response{
Quote: invoice.Id,
Request: invoice.PaymentRequest,
Paid: invoice.Settled,
State: state,
Paid: invoice.Settled, // DEPRECATED: remove after wallets have upgraded
Expiry: invoice.Expiry,
}
return quoteState, nil
Expand Down
81 changes: 81 additions & 0 deletions mint/mint_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

btcdocker "github.com/elnosh/btc-docker-test"
"github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut04"
"github.com/elnosh/gonuts/cashu/nuts/nut05"
"github.com/elnosh/gonuts/crypto"
"github.com/elnosh/gonuts/mint"
Expand Down Expand Up @@ -112,6 +113,86 @@ func TestRequestMintQuote(t *testing.T) {
}
}

func TestMintQuoteState(t *testing.T) {
var mintAmount uint64 = 42000
mintQuoteResponse, err := testMint.RequestMintQuote(testutils.BOLT11_METHOD, mintAmount, testutils.SAT_UNIT)
if err != nil {
t.Fatalf("error requesting mint quote: %v", err)
}

var keyset crypto.Keyset
for _, k := range testMint.ActiveKeysets {
keyset = k
break
}

// test invalid method
_, err = testMint.GetMintQuoteState("strike", mintQuoteResponse.Quote)
if !errors.Is(err, cashu.PaymentMethodNotSupportedErr) {
t.Fatalf("expected error '%v' but got '%v' instead", cashu.PaymentMethodNotSupportedErr, err)
}

// test invalid quote
_, err = testMint.GetMintQuoteState(testutils.BOLT11_METHOD, "mintquote1234")
if !errors.Is(err, cashu.QuoteNotExistErr) {
t.Fatalf("expected error '%v' but got '%v' instead", cashu.QuoteNotExistErr, err)
}

// test quote state before paying invoice
quoteStateResponse, err := testMint.GetMintQuoteState(testutils.BOLT11_METHOD, mintQuoteResponse.Quote)
if err != nil {
t.Fatalf("unexpected error getting quote state: %v", err)
}
if quoteStateResponse.Paid {
t.Fatalf("expected quote.Paid '%v' but got '%v' instead", false, quoteStateResponse.Paid)
}
if quoteStateResponse.State != nut04.Unpaid {
t.Fatalf("expected quote state '%v' but got '%v' instead", nut04.Unpaid.String(), quoteStateResponse.State.String())
}

//pay invoice
sendPaymentRequest := lnrpc.SendRequest{
PaymentRequest: mintQuoteResponse.Request,
}
response, _ := lnd2.Client.SendPaymentSync(ctx, &sendPaymentRequest)
if len(response.PaymentError) > 0 {
t.Fatalf("error paying invoice: %v", response.PaymentError)
}

// test quote state after paying invoice
quoteStateResponse, err = testMint.GetMintQuoteState(testutils.BOLT11_METHOD, mintQuoteResponse.Quote)
if err != nil {
t.Fatalf("unexpected error getting quote state: %v", err)
}
if !quoteStateResponse.Paid {
t.Fatalf("expected quote.Paid '%v' but got '%v' instead", true, quoteStateResponse.Paid)
}
if quoteStateResponse.State != nut04.Paid {
t.Fatalf("expected quote state '%v' but got '%v' instead", nut04.Paid.String(), quoteStateResponse.State.String())
}

blindedMessages, _, _, err := testutils.CreateBlindedMessages(mintAmount, keyset)

// mint tokens
_, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Quote, blindedMessages)
if err != nil {
t.Fatalf("got unexpected error minting tokens: %v", err)
}

// test quote state after minting tokens
quoteStateResponse, err = testMint.GetMintQuoteState(testutils.BOLT11_METHOD, mintQuoteResponse.Quote)
if err != nil {
t.Fatalf("unexpected error getting quote state: %v", err)
}
if !quoteStateResponse.Paid {
t.Fatalf("expected quote.Paid '%v' but got '%v' instead", true, quoteStateResponse.Paid)
}
if quoteStateResponse.State != nut04.Issued {
t.Fatalf("expected quote state '%v' but got '%v' instead", nut04.Issued.String(), quoteStateResponse.State.String())
}

}

func TestMintTokens(t *testing.T) {
var mintAmount uint64 = 42000
mintQuoteResponse, err := testMint.RequestMintQuote(testutils.BOLT11_METHOD, mintAmount, testutils.SAT_UNIT)
Expand Down
4 changes: 2 additions & 2 deletions mint/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (ms *MintServer) mintRequest(rw http.ResponseWriter, req *http.Request) {
return
}

jsonRes, err := json.Marshal(reqMintResponse)
jsonRes, err := json.Marshal(&reqMintResponse)
if err != nil {
ms.writeErr(rw, req, cashu.StandardErr)
return
Expand All @@ -246,7 +246,7 @@ func (ms *MintServer) mintQuoteState(rw http.ResponseWriter, req *http.Request)
ms.writeErr(rw, req, err)
return
}
jsonRes, err := json.Marshal(mintQuoteStateResponse)
jsonRes, err := json.Marshal(&mintQuoteStateResponse)
if err != nil {
ms.writeErr(rw, req, cashu.StandardErr)
return
Expand Down
16 changes: 5 additions & 11 deletions wallet/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,16 +310,6 @@ func (w *Wallet) RequestMint(amount uint64) (*nut04.PostMintQuoteBolt11Response,
return mintResponse, nil
}

// CheckQuotePaid reports whether the mint quote has been paid
func (w *Wallet) CheckQuotePaid(quoteId string) bool {
mintQuote, err := GetMintQuoteState(w.currentMint.mintURL, quoteId)
if err != nil {
return false
}

return mintQuote.Paid
}

// MintTokens will check whether if the mint quote has been paid.
// If yes, it will create blinded messages that will send to the mint
// to get the blinded signatures.
Expand All @@ -330,9 +320,13 @@ func (w *Wallet) MintTokens(quoteId string) (cashu.Proofs, error) {
if err != nil {
return nil, err
}
if !mintQuote.Paid {
// TODO: remove usage of 'Paid' field after mints have upgraded
if !mintQuote.Paid || mintQuote.State == nut04.Unpaid {
return nil, errors.New("invoice not paid")
}
if mintQuote.State == nut04.Issued {
return nil, errors.New("quote has already been issued")
}

invoice, err := w.GetInvoiceByPaymentRequest(mintQuote.Request)
if err != nil {
Expand Down

0 comments on commit abd55b0

Please sign in to comment.