From 33265f1272a2ca40d57d8d8e7860bdddcd953995 Mon Sep 17 00:00:00 2001 From: elnosh Date: Tue, 30 Jul 2024 11:58:13 -0500 Subject: [PATCH] mint - error codes from NUTs --- cashu/cashu.go | 68 +++++++++++++++----------- mint/mint.go | 92 +++++++++++++++++++---------------- mint/mint_integration_test.go | 32 +++++++----- mint/server.go | 18 +++---- 4 files changed, 121 insertions(+), 89 deletions(-) diff --git a/cashu/cashu.go b/cashu/cashu.go index d0c6d78..bfdeb11 100644 --- a/cashu/cashu.go +++ b/cashu/cashu.go @@ -213,41 +213,53 @@ func (e Error) Error() string { // Common error codes const ( - StandardErrCode CashuErrCode = 1000 + iota - KeysetErrCode - PaymentMethodErrCode - UnitErrCode - QuoteErrCode - InvoiceErrCode - ProofsErrCode - DBErrorCode + StandardErrCode CashuErrCode = 10000 + // These will never be returned in a response. + // Using them to identify internally where + // the error originated and log appropriately + DBErrCode CashuErrCode = 1 + LightningBackendErrCode CashuErrCode = 2 + + UnitErrCode CashuErrCode = 11005 + PaymentMethodErrCode CashuErrCode = 11006 + + InvalidProofErrCode CashuErrCode = 10003 + ProofAlreadyUsedErrCode CashuErrCode = 11001 + InsufficientProofAmountErrCode CashuErrCode = 11002 + + UnknownKeysetErrCode CashuErrCode = 12001 + InactiveKeysetErrCode CashuErrCode = 12002 + + MintQuoteRequestNotPaidErrCode CashuErrCode = 20001 + MintQuoteAlreadyIssuedErrCode CashuErrCode = 20002 + + MeltQuotePendingErrCode CashuErrCode = 20005 + MeltQuoteAlreadyPaidErrCode CashuErrCode = 20006 + + QuoteErrCode CashuErrCode = 20007 ) var ( StandardErr = Error{Detail: "unable to process request", Code: StandardErrCode} EmptyBodyErr = Error{Detail: "request body cannot be emtpy", Code: StandardErrCode} - KeysetNotExistErr = Error{Detail: "keyset does not exist", Code: KeysetErrCode} + UnknownKeysetErr = Error{Detail: "unknown keyset", Code: UnknownKeysetErrCode} PaymentMethodNotSupportedErr = Error{Detail: "payment method not supported", Code: PaymentMethodErrCode} UnitNotSupportedErr = Error{Detail: "unit not supported", Code: UnitErrCode} - InvalidBlindedMessageAmount = Error{Detail: "invalid amount in blinded message", Code: KeysetErrCode} - QuoteIdNotSpecifiedErr = Error{Detail: "quote id not specified", Code: QuoteErrCode} - InvoiceNotExistErr = Error{Detail: "invoice does not exist", Code: InvoiceErrCode} - InvoiceNotPaidErr = Error{Detail: "invoice has not been paid", Code: InvoiceErrCode} - OutputsOverInvoiceErr = Error{ - Detail: "sum of the output amounts is greater than amount of invoice paid", - Code: InvoiceErrCode} - InvoiceTokensIssuedErr = Error{Detail: "tokens already issued for invoice", Code: InvoiceErrCode} - ProofAlreadyUsedErr = Error{Detail: "proofs already used", Code: ProofsErrCode} - InvalidProofErr = Error{Detail: "invalid proof", Code: ProofsErrCode} - NoProofsProvided = Error{Detail: "no proofs provided", Code: ProofsErrCode} - DuplicateProofs = Error{Detail: "duplicate proofs", Code: ProofsErrCode} - InputsBelowOutputs = Error{Detail: "amount of input proofs is below amount of outputs", Code: ProofsErrCode} - EmptyInputsErr = Error{Detail: "inputs cannot be empty", Code: ProofsErrCode} - QuoteNotExistErr = Error{Detail: "quote does not exist", Code: QuoteErrCode} - QuoteAlreadyPaid = Error{Detail: "quote already paid", Code: QuoteErrCode} - InsufficientProofsAmount = Error{Detail: "amount of input proofs is below amount needed for transaction", Code: ProofsErrCode} - InvalidKeysetProof = Error{Detail: "proof from an invalid keyset", Code: ProofsErrCode} - InvalidSignatureRequest = Error{Detail: "requested signature from non-active keyset", Code: KeysetErrCode} + InvalidBlindedMessageAmount = Error{Detail: "invalid amount in blinded message", Code: StandardErrCode} + MintQuoteRequestNotPaid = Error{Detail: "quote request has not been paid", Code: MintQuoteRequestNotPaidErrCode} + MintQuoteAlreadyIssued = Error{Detail: "quote already issued", Code: MintQuoteAlreadyIssuedErrCode} + OutputsOverQuoteAmountErr = Error{Detail: "sum of the output amounts is greater than quote amount", Code: StandardErrCode} + ProofAlreadyUsedErr = Error{Detail: "proofs already used", Code: ProofAlreadyUsedErrCode} + InvalidProofErr = Error{Detail: "invalid proof", Code: InvalidProofErrCode} + NoProofsProvided = Error{Detail: "no proofs provided", Code: InvalidProofErrCode} + DuplicateProofs = Error{Detail: "duplicate proofs", Code: InvalidProofErrCode} + QuoteNotExistErr = Error{Detail: "quote does not exist", Code: QuoteErrCode} + MeltQuoteAlreadyPaid = Error{Detail: "quote already paid", Code: MeltQuoteAlreadyPaidErrCode} + InsufficientProofsAmount = Error{ + Detail: "amount of input proofs is below amount needed for transaction", + Code: InsufficientProofAmountErrCode, + } + InactiveKeysetSignatureRequest = Error{Detail: "requested signature from non-active keyset", Code: InactiveKeysetErrCode} ) // Given an amount, it returns list of amounts e.g 13 -> [1, 4, 8] diff --git a/mint/mint.go b/mint/mint.go index e107634..0617ad3 100644 --- a/mint/mint.go +++ b/mint/mint.go @@ -183,7 +183,7 @@ func (m *Mint) RequestMintQuote(method string, amount uint64, unit string) (stor invoice, err := m.requestInvoice(amount) if err != nil { msg := fmt.Sprintf("error generating payment request: %v", err) - return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.InvoiceErrCode) + return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.LightningBackendErrCode) } quoteId, err := cashu.GenerateRandomQuoteId() @@ -202,7 +202,7 @@ func (m *Mint) RequestMintQuote(method string, amount uint64, unit string) (stor err = m.db.SaveMintQuote(mintQuote) if err != nil { msg := fmt.Sprintf("error saving mint quote to db: %v", err) - return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.DBErrorCode) + return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode) } return mintQuote, nil @@ -224,7 +224,7 @@ func (m *Mint) GetMintQuoteState(method, quoteId string) (storage.MintQuote, err status, err := m.LightningClient.InvoiceStatus(mintQuote.PaymentHash) if err != nil { msg := fmt.Sprintf("error getting status of payment request: %v", err) - return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.InvoiceErrCode) + return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.LightningBackendErrCode) } if status.Settled && mintQuote.State == nut04.Unpaid { @@ -232,7 +232,7 @@ func (m *Mint) GetMintQuoteState(method, quoteId string) (storage.MintQuote, err err := m.db.UpdateMintQuoteState(mintQuote.Id, mintQuote.State) if err != nil { msg := fmt.Sprintf("error getting quote state: %v", err) - return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.DBErrorCode) + return storage.MintQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode) } } @@ -255,11 +255,11 @@ func (m *Mint) MintTokens(method, id string, blindedMessages cashu.BlindedMessag status, err := m.LightningClient.InvoiceStatus(mintQuote.PaymentHash) if err != nil { msg := fmt.Sprintf("error getting status of payment request: %v", err) - return nil, cashu.BuildCashuError(msg, cashu.InvoiceErrCode) + return nil, cashu.BuildCashuError(msg, cashu.LightningBackendErrCode) } if status.Settled { if mintQuote.State == nut04.Issued { - return nil, cashu.InvoiceTokensIssuedErr + return nil, cashu.MintQuoteAlreadyIssued } blindedMessagesAmount := blindedMessages.Amount() @@ -275,7 +275,7 @@ func (m *Mint) MintTokens(method, id string, blindedMessages cashu.BlindedMessag // verify that amount from blinded messages is less // than quote amount if blindedMessagesAmount > mintQuote.Amount { - return nil, cashu.OutputsOverInvoiceErr + return nil, cashu.OutputsOverQuoteAmountErr } var err error @@ -288,10 +288,10 @@ func (m *Mint) MintTokens(method, id string, blindedMessages cashu.BlindedMessag err = m.db.UpdateMintQuoteState(mintQuote.Id, nut04.Issued) if err != nil { msg := fmt.Sprintf("error getting quote state: %v", err) - return nil, cashu.BuildCashuError(msg, cashu.DBErrorCode) + return nil, cashu.BuildCashuError(msg, cashu.DBErrCode) } } else { - return nil, cashu.InvoiceNotPaidErr + return nil, cashu.MintQuoteRequestNotPaid } return blindedSignatures, nil @@ -303,13 +303,8 @@ func (m *Mint) MintTokens(method, id string, blindedMessages cashu.BlindedMessag // the proofs that were used as input. // It returns the BlindedSignatures. func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages) (cashu.BlindedSignatures, error) { - proofsLen := len(proofs) - if proofsLen == 0 { - return nil, cashu.NoProofsProvided - } - var proofsAmount uint64 - Ys := make([]string, proofsLen) + Ys := make([]string, len(proofs)) for i, proof := range proofs { proofsAmount += proof.Amount @@ -335,19 +330,7 @@ func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages) return nil, cashu.InsufficientProofsAmount } - // check if proofs were alredy used - usedProofs, err := m.db.GetProofsUsed(Ys) - if err != nil { - if !errors.Is(err, sql.ErrNoRows) { - msg := fmt.Sprintf("could not get used proofs from db: %v", err) - return nil, cashu.BuildCashuError(msg, cashu.DBErrorCode) - } - } - if len(usedProofs) != 0 { - return nil, cashu.ProofAlreadyUsedErr - } - - err = m.verifyProofs(proofs) + err := m.verifyProofs(proofs, Ys) if err != nil { return nil, err } @@ -362,7 +345,7 @@ func (m *Mint) Swap(proofs cashu.Proofs, blindedMessages cashu.BlindedMessages) err = m.db.SaveProofs(proofs) if err != nil { msg := fmt.Sprintf("error invalidating proofs. Could not save proofs to db: %v", err) - return nil, cashu.BuildCashuError(msg, cashu.DBErrorCode) + return nil, cashu.BuildCashuError(msg, cashu.DBErrCode) } return blindedSignatures, nil @@ -382,7 +365,7 @@ func (m *Mint) MeltRequest(method, request, unit string) (storage.MeltQuote, err bolt11, err := decodepay.Decodepay(request) if err != nil { msg := fmt.Sprintf("invalid invoice: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.InvoiceErrCode) + return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.StandardErrCode) } quoteId, err := cashu.GenerateRandomQuoteId() @@ -406,7 +389,7 @@ func (m *Mint) MeltRequest(method, request, unit string) (storage.MeltQuote, err } if err := m.db.SaveMeltQuote(meltQuote); err != nil { msg := fmt.Sprintf("error saving melt quote to db: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrorCode) + return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode) } return meltQuote, nil @@ -430,6 +413,19 @@ func (m *Mint) GetMeltQuoteState(method, quoteId string) (storage.MeltQuote, err // MeltTokens verifies whether proofs provided are valid // and proceeds to attempt payment. func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage.MeltQuote, error) { + var proofsAmount uint64 + Ys := make([]string, len(proofs)) + for i, proof := range proofs { + proofsAmount += proof.Amount + + Y, err := crypto.HashToCurve([]byte(proof.Secret)) + if err != nil { + return storage.MeltQuote{}, cashu.InvalidProofErr + } + Yhex := hex.EncodeToString(Y.SerializeCompressed()) + Ys[i] = Yhex + } + if method != BOLT11_METHOD { return storage.MeltQuote{}, cashu.PaymentMethodNotSupportedErr } @@ -439,15 +435,14 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage. return storage.MeltQuote{}, cashu.QuoteNotExistErr } if meltQuote.State == nut05.Paid { - return storage.MeltQuote{}, cashu.QuoteAlreadyPaid + return storage.MeltQuote{}, cashu.MeltQuoteAlreadyPaid } - err = m.verifyProofs(proofs) + err = m.verifyProofs(proofs, Ys) if err != nil { return storage.MeltQuote{}, err } - proofsAmount := proofs.Amount() fees := m.TransactionFees(proofs) // checks if amount in proofs is enough if proofsAmount < meltQuote.Amount+meltQuote.FeeReserve+uint64(fees) { @@ -458,7 +453,7 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage. // to make the payment preimage, err := m.LightningClient.SendPayment(meltQuote.InvoiceRequest, meltQuote.Amount) if err != nil { - return storage.MeltQuote{}, cashu.BuildCashuError(err.Error(), cashu.InvoiceErrCode) + return storage.MeltQuote{}, cashu.BuildCashuError(err.Error(), cashu.LightningBackendErrCode) } // if payment succeeded, mark melt quote as paid @@ -468,21 +463,33 @@ func (m *Mint) MeltTokens(method, quoteId string, proofs cashu.Proofs) (storage. err = m.db.UpdateMeltQuote(meltQuote.Id, meltQuote.Preimage, meltQuote.State) if err != nil { msg := fmt.Sprintf("error getting quote state: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrorCode) + return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode) } err = m.db.SaveProofs(proofs) if err != nil { msg := fmt.Sprintf("error invalidating proofs. Could not save proofs to db: %v", err) - return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrorCode) + return storage.MeltQuote{}, cashu.BuildCashuError(msg, cashu.DBErrCode) } return *meltQuote, nil } -func (m *Mint) verifyProofs(proofs cashu.Proofs) error { +func (m *Mint) verifyProofs(proofs cashu.Proofs, Ys []string) error { if len(proofs) == 0 { - return cashu.EmptyInputsErr + return cashu.NoProofsProvided + } + + // check if proofs were alredy used + usedProofs, err := m.db.GetProofsUsed(Ys) + if err != nil { + if !errors.Is(err, sql.ErrNoRows) { + msg := fmt.Sprintf("could not get used proofs from db: %v", err) + return cashu.BuildCashuError(msg, cashu.DBErrCode) + } + } + if len(usedProofs) != 0 { + return cashu.ProofAlreadyUsedErr } // check duplicte proofs @@ -495,7 +502,7 @@ func (m *Mint) verifyProofs(proofs cashu.Proofs) error { // of the mint's keyset var k *secp256k1.PrivateKey if keyset, ok := m.Keysets[proof.Id]; !ok { - return cashu.InvalidKeysetProof + return cashu.UnknownKeysetErr } else { if key, ok := keyset.Keys[proof.Amount]; ok { k = key.PrivateKey @@ -527,10 +534,13 @@ func (m *Mint) signBlindedMessages(blindedMessages cashu.BlindedMessages) (cashu blindedSignatures := make(cashu.BlindedSignatures, len(blindedMessages)) for i, msg := range blindedMessages { + if _, ok := m.Keysets[msg.Id]; !ok { + return nil, cashu.UnknownKeysetErr + } var k *secp256k1.PrivateKey keyset, ok := m.ActiveKeysets[msg.Id] if !ok { - return nil, cashu.InvalidSignatureRequest + return nil, cashu.InactiveKeysetSignatureRequest } else { if key, ok := keyset.Keys[msg.Amount]; ok { k = key.PrivateKey diff --git a/mint/mint_integration_test.go b/mint/mint_integration_test.go index c5e8bc5..8f55889 100644 --- a/mint/mint_integration_test.go +++ b/mint/mint_integration_test.go @@ -202,8 +202,8 @@ func TestMintTokens(t *testing.T) { // test without paying invoice _, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Id, blindedMessages) - if !errors.Is(err, cashu.InvoiceNotPaidErr) { - t.Fatalf("expected error '%v' but got '%v' instead", cashu.InvoiceNotPaidErr, err) + if !errors.Is(err, cashu.MintQuoteRequestNotPaid) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.MintQuoteRequestNotPaid, err) } // test invalid quote @@ -224,16 +224,16 @@ func TestMintTokens(t *testing.T) { // test with blinded messages over request mint amount overBlindedMessages, _, _, err := testutils.CreateBlindedMessages(mintAmount+100, keyset) _, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Id, overBlindedMessages) - if !errors.Is(err, cashu.OutputsOverInvoiceErr) { - t.Fatalf("expected error '%v' but got '%v' instead", cashu.OutputsOverInvoiceErr, err) + if !errors.Is(err, cashu.OutputsOverQuoteAmountErr) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.OutputsOverQuoteAmountErr, err) } // test with invalid keyset in blinded messages invalidKeyset := crypto.MintKeyset{Id: "0192384aa"} invalidKeysetMessages, _, _, err := testutils.CreateBlindedMessages(mintAmount, invalidKeyset) _, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Id, invalidKeysetMessages) - if !errors.Is(err, cashu.InvalidSignatureRequest) { - t.Fatalf("expected error '%v' but got '%v' instead", cashu.InvalidSignatureRequest, err) + if !errors.Is(err, cashu.UnknownKeysetErr) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.UnknownKeysetErr, err) } _, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Id, blindedMessages) @@ -243,8 +243,8 @@ func TestMintTokens(t *testing.T) { // test already minted tokens _, err = testMint.MintTokens(testutils.BOLT11_METHOD, mintQuoteResponse.Id, blindedMessages) - if !errors.Is(err, cashu.InvoiceTokensIssuedErr) { - t.Fatalf("expected error '%v' but got '%v' instead", cashu.InvoiceTokensIssuedErr, err) + if !errors.Is(err, cashu.MintQuoteAlreadyIssued) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.MintQuoteAlreadyIssued, err) } } @@ -459,10 +459,20 @@ func TestMelt(t *testing.T) { t.Fatal("got unexpected unpaid melt quote") } - // test already used proofs + // test quote already paid _, err = testMint.MeltTokens(testutils.BOLT11_METHOD, meltQuote.Id, validProofs) - if !errors.Is(err, cashu.QuoteAlreadyPaid) { - t.Fatalf("expected error '%v' but got '%v' instead", cashu.QuoteAlreadyPaid, err) + if !errors.Is(err, cashu.MeltQuoteAlreadyPaid) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.MeltQuoteAlreadyPaid, err) + } + + // test already used proofs + newQuote, err := testMint.MeltRequest(testutils.BOLT11_METHOD, addInvoiceResponse.PaymentRequest, testutils.SAT_UNIT) + if err != nil { + t.Fatalf("got unexpected error in melt request: %v", err) + } + _, err = testMint.MeltTokens(testutils.BOLT11_METHOD, newQuote.Id, validProofs) + if !errors.Is(err, cashu.ProofAlreadyUsedErr) { + t.Fatalf("expected error '%v' but got '%v' instead", cashu.ProofAlreadyUsedErr, err) } // mint with fees diff --git a/mint/server.go b/mint/server.go index a8e00cb..a59e078 100644 --- a/mint/server.go +++ b/mint/server.go @@ -181,7 +181,7 @@ func (ms *MintServer) getKeysetById(rw http.ResponseWriter, req *http.Request) { ks, ok := ms.mint.Keysets[id] if !ok { - ms.writeErr(rw, req, cashu.KeysetNotExistErr) + ms.writeErr(rw, req, cashu.UnknownKeysetErr) return } @@ -212,7 +212,7 @@ func (ms *MintServer) mintRequest(rw http.ResponseWriter, req *http.Request) { // note: if there was internal error from lightning backend generating invoice // or error from db, log that error but return generic response if ok { - if cashuErr.Code == cashu.InvoiceErrCode || cashuErr.Code == cashu.DBErrorCode { + if cashuErr.Code == cashu.LightningBackendErrCode || cashuErr.Code == cashu.DBErrCode { ms.writeErr(rw, req, cashu.StandardErr, cashuErr.Error()) return } @@ -250,7 +250,7 @@ func (ms *MintServer) mintQuoteState(rw http.ResponseWriter, req *http.Request) // note: if there was internal error from lightning backend // or error from db, log that error but return generic response if ok { - if cashuErr.Code == cashu.InvoiceErrCode || cashuErr.Code == cashu.DBErrorCode { + if cashuErr.Code == cashu.LightningBackendErrCode || cashuErr.Code == cashu.DBErrCode { ms.writeErr(rw, req, cashu.StandardErr, cashuErr.Error()) return } @@ -295,7 +295,7 @@ func (ms *MintServer) mintTokensRequest(rw http.ResponseWriter, req *http.Reques // note: if there was internal error from lightning backend // or error from db, log that error but return generic response if ok { - if cashuErr.Code == cashu.InvoiceErrCode || cashuErr.Code == cashu.DBErrorCode { + if cashuErr.Code == cashu.LightningBackendErrCode || cashuErr.Code == cashu.DBErrCode { ms.writeErr(rw, req, cashu.StandardErr, cashuErr.Error()) return } @@ -327,7 +327,7 @@ func (ms *MintServer) swapRequest(rw http.ResponseWriter, req *http.Request) { cashuErr, ok := err.(*cashu.Error) // note: if there was internal error from db // log that error but return generic response - if ok && cashuErr.Code == cashu.DBErrorCode { + if ok && cashuErr.Code == cashu.DBErrCode { ms.writeErr(rw, req, cashu.StandardErr, cashuErr.Error()) return } @@ -361,7 +361,7 @@ func (ms *MintServer) meltQuoteRequest(rw http.ResponseWriter, req *http.Request cashuErr, ok := err.(*cashu.Error) // note: if there was internal error from db // log that error but return generic response - if ok && cashuErr.Code == cashu.DBErrorCode { + if ok && cashuErr.Code == cashu.DBErrCode { ms.writeErr(rw, req, cashu.StandardErr, cashuErr.Error()) return } @@ -436,11 +436,11 @@ func (ms *MintServer) meltTokens(rw http.ResponseWriter, req *http.Request) { // note: if there was internal error from lightning backend // or error from db, log that error but return generic response if ok { - if cashuErr.Code == cashu.InvoiceErrCode { - responseError := cashu.BuildCashuError("unable to send payment", cashu.InvoiceErrCode) + if cashuErr.Code == cashu.LightningBackendErrCode { + responseError := cashu.BuildCashuError("unable to send payment", cashu.StandardErrCode) ms.writeErr(rw, req, responseError, cashuErr.Error()) return - } else if cashuErr.Code == cashu.DBErrorCode { + } else if cashuErr.Code == cashu.DBErrCode { ms.writeErr(rw, req, cashu.StandardErr, cashuErr.Error()) return }