Skip to content

Commit

Permalink
mint - nut05 state changes
Browse files Browse the repository at this point in the history
  • Loading branch information
elnosh committed Jul 4, 2024
1 parent 915c83c commit 063d04b
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 32 deletions.
1 change: 1 addition & 0 deletions cashu/cashu.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ var (
InvalidProofErr = Error{Detail: "invalid proof", Code: ProofsErrCode}
InputsBelowOutputs = Error{Detail: "amount of input proofs is below amount of outputs", Code: ProofsErrCode}
QuoteNotExistErr = Error{Detail: "quote does not exist", Code: QuoteErrCode}
QuoteAlreadyPaid = Error{Detail: "quote already paid", Code: QuoteErrCode}
InsufficientProofsAmount = Error{Detail: "insufficient amount in proofs", Code: ProofsErrCode}
InvalidKeysetProof = Error{Detail: "proof from an invalid keyset", Code: ProofsErrCode}
InvalidSignatureRequest = Error{Detail: "requested signature from non-active keyset", Code: KeysetErrCode}
Expand Down
55 changes: 50 additions & 5 deletions cashu/nuts/nut05/nut05.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,33 @@
// [NUT-05]: https://github.com/cashubtc/nuts/blob/main/05.md
package nut05

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

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

type State int

const (
Unpaid State = iota
Pending
Paid
Unknown
)

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

type PostMeltQuoteBolt11Request struct {
Request string `json:"request"`
Expand All @@ -14,16 +40,35 @@ type PostMeltQuoteBolt11Response struct {
Quote string `json:"quote"`
Amount uint64 `json:"amount"`
FeeReserve uint64 `json:"fee_reserve"`
Paid bool `json:"paid"`
State State `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use state instead
Expiry int64 `json:"expiry"`
Preimage string `json:"payment_preimage,omitempty"`
}

type PostMeltBolt11Request struct {
Quote string `json:"quote"`
Inputs cashu.Proofs `json:"inputs"`
}

type PostMeltBolt11Response struct {
Paid bool `json:"paid"`
Preimage string `json:"payment_preimage"`
// Custom marshaler to display state as string
func (quoteResponse *PostMeltQuoteBolt11Response) MarshalJSON() ([]byte, error) {
var response = struct {
Quote string `json:"quote"`
Amount uint64 `json:"amount"`
FeeReserve uint64 `json:"fee_reserve"`
State string `json:"state"`
Paid bool `json:"paid"` // DEPRECATED: use state instead
Expiry int64 `json:"expiry"`
Preimage string `json:"payment_preimage,omitempty"`
}{
Quote: quoteResponse.Quote,
Amount: quoteResponse.Amount,
FeeReserve: quoteResponse.FeeReserve,
State: quoteResponse.State.String(),
Paid: quoteResponse.Paid,
Expiry: quoteResponse.Expiry,
Preimage: quoteResponse.Preimage,
}
return json.Marshal(response)
}
8 changes: 7 additions & 1 deletion mint/lightning/lnd.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,13 +111,19 @@ func (lnd *LndClient) InvoiceStatus(hash string) (Invoice, error) {
return Invoice{}, err
}

invoiceSettled := lookupInvoiceResponse.State == lnrpc.Invoice_SETTLED
invoice := Invoice{
PaymentRequest: lookupInvoiceResponse.PaymentRequest,
PaymentHash: hash,
Settled: lookupInvoiceResponse.State == lnrpc.Invoice_SETTLED,
Settled: invoiceSettled,
Amount: uint64(lookupInvoiceResponse.Value),
}

if invoiceSettled {
preimage := hex.EncodeToString(lookupInvoiceResponse.RPreimage)
invoice.Preimage = preimage
}

return invoice, nil
}

Expand Down
27 changes: 26 additions & 1 deletion mint/mint.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (
"github.com/decred/dcrd/dcrec/secp256k1/v4"
"github.com/elnosh/gonuts/cashu"
"github.com/elnosh/gonuts/cashu/nuts/nut04"
"github.com/elnosh/gonuts/cashu/nuts/nut05"
"github.com/elnosh/gonuts/cashu/nuts/nut06"
"github.com/elnosh/gonuts/crypto"
"github.com/elnosh/gonuts/mint/lightning"
Expand Down Expand Up @@ -248,9 +249,11 @@ func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages)
type MeltQuote struct {
Id string
InvoiceRequest string
PaymentHash string
Amount uint64
FeeReserve uint64
Paid bool
State nut05.State
Paid bool // DEPRECATED: use state instead
Expiry int64
Preimage string
}
Expand Down Expand Up @@ -288,8 +291,10 @@ func (m *Mint) MeltRequest(method, request, unit string) (MeltQuote, error) {
meltQuote := MeltQuote{
Id: hex.EncodeToString(hash[:]),
InvoiceRequest: request,
PaymentHash: bolt11.PaymentHash,
Amount: satAmount,
FeeReserve: fee,
State: nut05.Unpaid,
Paid: false,
Expiry: expiry,
}
Expand All @@ -310,6 +315,20 @@ func (m *Mint) GetMeltQuoteState(method, quoteId string) (MeltQuote, error) {
return MeltQuote{}, cashu.QuoteNotExistErr
}

// if quote not paid, check status of payment with backend
if meltQuote.State == nut05.Unpaid {
invoice, err := m.LightningClient.InvoiceStatus(meltQuote.PaymentHash)
if err != nil {
return MeltQuote{}, cashu.BuildCashuError(err.Error(), cashu.StandardErr.Code)
}
if invoice.Settled {
meltQuote.Paid = true
meltQuote.State = nut05.Paid
meltQuote.Preimage = invoice.Preimage
m.db.SaveMeltQuote(*meltQuote)
}
}

return *meltQuote, nil
}

Expand All @@ -324,6 +343,9 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (MeltQuot
if meltQuote == nil {
return MeltQuote{}, cashu.QuoteNotExistErr
}
if meltQuote.State == nut05.Paid {
return MeltQuote{}, cashu.QuoteAlreadyPaid
}

proofsAmount := proofs.Amount()

Expand All @@ -346,8 +368,11 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (MeltQuot

// if payment succeeded, mark melt quote as paid
// and invalidate proofs
meltQuote.State = nut05.Paid
// Deprecate Paid field in favor of State
meltQuote.Paid = true
meltQuote.Preimage = preimage
m.db.SaveMeltQuote(*meltQuote)
for _, proof := range proofs {
m.db.SaveProof(proof)
}
Expand Down
5 changes: 3 additions & 2 deletions mint/mint_integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,9 @@ func TestMelt(t *testing.T) {

// test already used proofs
_, err = testMint.MeltTokens(testutils.BOLT11_METHOD, meltQuote.Id, validProofs)
if !errors.Is(err, cashu.ProofAlreadyUsedErr) {
t.Fatalf("expected error '%v' but got '%v' instead", cashu.ProofAlreadyUsedErr, err)
//if !errors.Is(err, cashu.ProofAlreadyUsedErr) {
if !errors.Is(err, cashu.QuoteAlreadyPaid) {
t.Fatalf("expected error '%v' but got '%v' instead", cashu.QuoteAlreadyPaid, err)
}

}
25 changes: 18 additions & 7 deletions mint/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ func (ms *MintServer) writeResponse(
rw.Write(response)
}

// errResponse is the error that will be written in the response
// errLogMsg is the error to log
func (ms *MintServer) writeErr(rw http.ResponseWriter, req *http.Request, errResponse error, errLogMsg ...string) {
code := http.StatusBadRequest

Expand Down Expand Up @@ -326,10 +328,11 @@ func (ms *MintServer) meltQuoteRequest(rw http.ResponseWriter, req *http.Request
return
}

quoteResponse := nut05.PostMeltQuoteBolt11Response{
quoteResponse := &nut05.PostMeltQuoteBolt11Response{
Quote: meltQuote.Id,
Amount: meltQuote.Amount,
FeeReserve: meltQuote.FeeReserve,
State: meltQuote.State,
Paid: meltQuote.Paid,
Expiry: meltQuote.Expiry,
}
Expand All @@ -354,12 +357,14 @@ func (ms *MintServer) meltQuoteState(rw http.ResponseWriter, req *http.Request)
return
}

quoteState := nut05.PostMeltQuoteBolt11Response{
quoteState := &nut05.PostMeltQuoteBolt11Response{
Quote: meltQuote.Id,
Amount: meltQuote.Amount,
FeeReserve: meltQuote.FeeReserve,
State: meltQuote.State,
Paid: meltQuote.Paid,
Expiry: meltQuote.Expiry,
Preimage: meltQuote.Preimage,
}

jsonRes, err := json.Marshal(quoteState)
Expand All @@ -386,19 +391,25 @@ func (ms *MintServer) meltTokens(rw http.ResponseWriter, req *http.Request) {
if err != nil {
cashuErr, ok := err.(*cashu.Error)
if ok && cashuErr.Code == cashu.InvoiceErrCode {
ms.writeErr(rw, req, cashu.BuildCashuError("unable to send payment", cashu.InvoiceErrCode), cashuErr.Error())
responseError := cashu.BuildCashuError("unable to send payment", cashu.InvoiceErrCode)
ms.writeErr(rw, req, responseError, cashuErr.Error())
return
}
ms.writeErr(rw, req, err)
return
}

meltTokenResponse := nut05.PostMeltBolt11Response{
Paid: meltQuote.Paid,
Preimage: meltQuote.Preimage,
meltQuoteResponse := &nut05.PostMeltQuoteBolt11Response{
Quote: meltQuote.Id,
Amount: meltQuote.Amount,
FeeReserve: meltQuote.FeeReserve,
State: meltQuote.State,
Paid: meltQuote.Paid,
Expiry: meltQuote.Expiry,
Preimage: meltQuote.Preimage,
}

jsonRes, err := json.Marshal(meltTokenResponse)
jsonRes, err := json.Marshal(meltQuoteResponse)
if err != nil {
ms.writeErr(rw, req, cashu.StandardErr)
return
Expand Down
28 changes: 14 additions & 14 deletions wallet/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ func GetMintInfo(mintURL string) (*nut06.MintInfo, error) {

var mintInfo nut06.MintInfo
if err := json.Unmarshal(body, &mintInfo); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &mintInfo, nil
Expand All @@ -52,7 +52,7 @@ func GetActiveKeysets(mintURL string) (*nut01.GetKeysResponse, error) {

var keysetRes nut01.GetKeysResponse
if err := json.Unmarshal(body, &keysetRes); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &keysetRes, nil
Expand All @@ -72,7 +72,7 @@ func GetAllKeysets(mintURL string) (*nut02.GetKeysetsResponse, error) {

var keysetsRes nut02.GetKeysetsResponse
if err := json.Unmarshal(body, &keysetsRes); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &keysetsRes, nil
Expand All @@ -92,7 +92,7 @@ func GetKeysetById(mintURL, id string) (*nut01.GetKeysResponse, error) {

var keysetRes nut01.GetKeysResponse
if err := json.Unmarshal(body, &keysetRes); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &keysetRes, nil
Expand All @@ -118,7 +118,7 @@ func PostMintQuoteBolt11(mintURL string, mintQuoteRequest nut04.PostMintQuoteBol

var reqMintResponse nut04.PostMintQuoteBolt11Response
if err := json.Unmarshal(body, &reqMintResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &reqMintResponse, nil
Expand All @@ -138,7 +138,7 @@ func GetMintQuoteState(mintURL, quoteId string) (*nut04.PostMintQuoteBolt11Respo

var mintQuoteResponse nut04.PostMintQuoteBolt11Response
if err := json.Unmarshal(body, &mintQuoteResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &mintQuoteResponse, nil
Expand All @@ -164,7 +164,7 @@ func PostMintBolt11(mintURL string, mintRequest nut04.PostMintBolt11Request) (

var reqMintResponse nut04.PostMintBolt11Response
if err := json.Unmarshal(body, &reqMintResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &reqMintResponse, nil
Expand All @@ -189,7 +189,7 @@ func PostSwap(mintURL string, swapRequest nut03.PostSwapRequest) (*nut03.PostSwa

var swapResponse nut03.PostSwapResponse
if err := json.Unmarshal(body, &swapResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &swapResponse, nil
Expand All @@ -216,14 +216,14 @@ func PostMeltQuoteBolt11(mintURL string, meltQuoteRequest nut05.PostMeltQuoteBol

var meltQuoteResponse nut05.PostMeltQuoteBolt11Response
if err := json.Unmarshal(body, &meltQuoteResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &meltQuoteResponse, nil
}

func PostMeltBolt11(mintURL string, meltRequest nut05.PostMeltBolt11Request) (
*nut05.PostMeltBolt11Response, error) {
*nut05.PostMeltQuoteBolt11Response, error) {

requestBody, err := json.Marshal(meltRequest)
if err != nil {
Expand All @@ -241,9 +241,9 @@ func PostMeltBolt11(mintURL string, meltRequest nut05.PostMeltBolt11Request) (
return nil, err
}

var meltResponse nut05.PostMeltBolt11Response
var meltResponse nut05.PostMeltQuoteBolt11Response
if err := json.Unmarshal(body, &meltResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &meltResponse, nil
Expand All @@ -270,7 +270,7 @@ func PostCheckProofState(mintURL string, stateRequest nut07.PostCheckStateReques

var stateResponse nut07.PostCheckStateResponse
if err := json.Unmarshal(body, &stateResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &stateResponse, nil
Expand All @@ -297,7 +297,7 @@ func PostRestore(mintURL string, restoreRequest nut09.PostRestoreRequest) (

var restoreResponse nut09.PostRestoreResponse
if err := json.Unmarshal(body, &restoreResponse); err != nil {
return nil, fmt.Errorf("error reading response from mint: %v\n", err)
return nil, fmt.Errorf("error reading response from mint: %v", err)
}

return &restoreResponse, nil
Expand Down
Loading

0 comments on commit 063d04b

Please sign in to comment.