Skip to content

Commit 3e19084

Browse files
authored
Merge pull request #1142 from KvngMikey/fix/npc-already-issued-error-code
fix(wallet): dispatch NPC already-issued check on 20002 not 11000
2 parents 11c9e30 + b30e931 commit 3e19084

2 files changed

Lines changed: 64 additions & 3 deletions

File tree

cashu/wallet/npc.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
import httpx
55

66
from cashu.core.base import MintQuoteState
7-
from cashu.core.errors import CashuError
7+
from cashu.core.errors import CashuError, QuoteAlreadyIssuedError
88
from cashu.core.nostr import create_nip98_header, derive_nostr_keypair, get_npub
99
from cashu.core.settings import settings
1010
from cashu.wallet.crud import get_bolt11_mint_quote, update_bolt11_mint_quote
@@ -188,7 +188,10 @@ async def mint_quotes(self) -> List[Any]:
188188
minted_proofs.extend(proofs)
189189
except Exception as e:
190190
# If the mint returns an error that the quote is already issued, we assume it is issued
191-
if "Code: 11000" in str(e) or (isinstance(e, CashuError) and e.code == 11000):
191+
code = QuoteAlreadyIssuedError.code
192+
if f"Code: {code}" in str(e) or (
193+
isinstance(e, CashuError) and e.code == code
194+
):
192195
print(f"Quote {quote_id} already issued (mint). Updating local state.")
193196
await update_bolt11_mint_quote(
194197
db=self.wallet.db,

tests/wallet/test_npc.py

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from cashu.core.base import Proof
5+
from cashu.core.base import MintQuoteState, Proof
66
from cashu.wallet.npc import NpubCash
77
from cashu.wallet.wallet import Wallet
88

@@ -125,3 +125,61 @@ async def test_mint_quotes(npc, mock_wallet):
125125

126126
assert len(proofs) == 1
127127
mock_wallet.mint.assert_called_once_with(100, quote_id="q1")
128+
129+
130+
@pytest.mark.asyncio
131+
@pytest.mark.parametrize(
132+
"error_code, expect_issued",
133+
[
134+
(20002, True), # QuoteAlreadyIssuedError: the quote really was issued
135+
(11000, False), # generic TransactionError: unrelated failure, keep retrying
136+
(20001, False), # QuoteNotPaidError
137+
(20005, False), # QuotePendingError
138+
],
139+
)
140+
async def test_mint_quotes_only_marks_issued_on_already_issued_code(
141+
npc, mock_wallet, error_code, expect_issued
142+
):
143+
"""Only the mint's "quote already issued" code may mark the local quote issued.
144+
145+
Marking it on any other failure strands a paid invoice: the quote is skipped
146+
on every later poll, so no ecash is ever issued for it.
147+
"""
148+
with patch("cashu.wallet.npc.httpx.AsyncClient") as mock_client, \
149+
patch("cashu.wallet.npc.get_bolt11_mint_quote", new_callable=AsyncMock) as mock_get_quote, \
150+
patch("cashu.wallet.npc.update_bolt11_mint_quote", new_callable=AsyncMock) as mock_update_quote:
151+
152+
mock_instance = mock_client.return_value.__aenter__.return_value
153+
154+
mock_resp = MagicMock()
155+
mock_resp.status_code = 200
156+
mock_resp.json.return_value = {
157+
"error": False,
158+
"data": {
159+
"quotes": [
160+
{"quoteId": "q1", "amount": 100, "state": "PAID", "mintUrl": "https://mint.example.com"},
161+
]
162+
}
163+
}
164+
mock_instance.get.return_value = mock_resp
165+
166+
mock_get_quote.return_value = None
167+
168+
mock_mint_quote = MagicMock()
169+
mock_mint_quote.state = "PAID"
170+
mock_wallet.get_mint_quote = AsyncMock(return_value=mock_mint_quote)
171+
172+
# the wallet flattens mint errors to "Mint Error: <detail> (Code: <n>)"
173+
mock_wallet.mint = AsyncMock(
174+
side_effect=Exception(f"Mint Error: something went wrong (Code: {error_code})")
175+
)
176+
177+
proofs = await npc.mint_quotes()
178+
179+
assert proofs == []
180+
if expect_issued:
181+
mock_update_quote.assert_awaited_once()
182+
assert mock_update_quote.await_args.kwargs["quote"] == "q1"
183+
assert mock_update_quote.await_args.kwargs["state"] == MintQuoteState.issued
184+
else:
185+
mock_update_quote.assert_not_awaited()

0 commit comments

Comments
 (0)