mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 10:34:20 +01:00
Add get quote API to wallet + check proof states in batches (#637)
* add get quote api to wallet * wrong string * test before pushing * fix tests for deprecated api only * sigh
This commit is contained in:
@@ -381,8 +381,12 @@ async def invoice(
|
||||
while time.time() < check_until and not paid:
|
||||
await asyncio.sleep(5)
|
||||
try:
|
||||
mint_quote_resp = await wallet.get_mint_quote(invoice.id)
|
||||
if mint_quote_resp.state == MintQuoteState.paid.value:
|
||||
await wallet.mint(amount, split=optional_split, id=invoice.id)
|
||||
paid = True
|
||||
else:
|
||||
print(".", end="", flush=True)
|
||||
except Exception as e:
|
||||
# TODO: user error codes!
|
||||
if "not paid" in str(e):
|
||||
@@ -710,12 +714,7 @@ async def burn(ctx: Context, token: str, all: bool, force: bool, delete: str):
|
||||
if delete:
|
||||
await wallet.invalidate(proofs)
|
||||
else:
|
||||
# invalidate proofs in batches
|
||||
for _proofs in [
|
||||
proofs[i : i + settings.proofs_batch_size]
|
||||
for i in range(0, len(proofs), settings.proofs_batch_size)
|
||||
]:
|
||||
await wallet.invalidate(_proofs, check_spendable=True)
|
||||
await wallet.invalidate(proofs, check_spendable=True)
|
||||
await print_balance(ctx)
|
||||
|
||||
|
||||
@@ -1024,7 +1023,9 @@ async def info(ctx: Context, mint: bool, mnemonic: bool):
|
||||
if mint_info.get("time"):
|
||||
print(f" - Server time: {mint_info['time']}")
|
||||
if mint_info.get("nuts"):
|
||||
nuts_str = ', '.join([f"NUT-{k}" for k in mint_info['nuts'].keys()])
|
||||
nuts_str = ", ".join(
|
||||
[f"NUT-{k}" for k in mint_info["nuts"].keys()]
|
||||
)
|
||||
print(f" - Supported NUTS: {nuts_str}")
|
||||
print("")
|
||||
except Exception as e:
|
||||
|
||||
@@ -170,7 +170,7 @@ class LedgerAPI(LedgerAPIDeprecated):
|
||||
keys_dict: dict = resp.json()
|
||||
assert len(keys_dict), Exception("did not receive any keys")
|
||||
keys = KeysResponse.parse_obj(keys_dict)
|
||||
keysets_str = ' '.join([f"{k.id} ({k.unit})" for k in keys.keysets])
|
||||
keysets_str = " ".join([f"{k.id} ({k.unit})" for k in keys.keysets])
|
||||
logger.debug(f"Received {len(keys.keysets)} keysets from mint: {keysets_str}.")
|
||||
ret = [
|
||||
WalletKeyset(
|
||||
@@ -312,6 +312,24 @@ class LedgerAPI(LedgerAPIDeprecated):
|
||||
return_dict = resp.json()
|
||||
return PostMintQuoteResponse.parse_obj(return_dict)
|
||||
|
||||
@async_set_httpx_client
|
||||
@async_ensure_mint_loaded
|
||||
async def get_mint_quote(self, quote: str) -> PostMintQuoteResponse:
|
||||
"""Returns an existing mint quote from the server.
|
||||
|
||||
Args:
|
||||
quote (str): Quote ID
|
||||
|
||||
Returns:
|
||||
PostMintQuoteResponse: Mint Quote Response
|
||||
"""
|
||||
resp = await self.httpx.get(
|
||||
join(self.url, f"/v1/mint/quote/bolt11/{quote}"),
|
||||
)
|
||||
self.raise_on_error_request(resp)
|
||||
return_dict = resp.json()
|
||||
return PostMintQuoteResponse.parse_obj(return_dict)
|
||||
|
||||
@async_set_httpx_client
|
||||
@async_ensure_mint_loaded
|
||||
async def mint(
|
||||
@@ -400,6 +418,24 @@ class LedgerAPI(LedgerAPIDeprecated):
|
||||
return_dict = resp.json()
|
||||
return PostMeltQuoteResponse.parse_obj(return_dict)
|
||||
|
||||
@async_set_httpx_client
|
||||
@async_ensure_mint_loaded
|
||||
async def get_melt_quote(self, quote: str) -> PostMeltQuoteResponse:
|
||||
"""Returns an existing melt quote from the server.
|
||||
|
||||
Args:
|
||||
quote (str): Quote ID
|
||||
|
||||
Returns:
|
||||
PostMeltQuoteResponse: Melt Quote Response
|
||||
"""
|
||||
resp = await self.httpx.get(
|
||||
join(self.url, f"/v1/melt/quote/bolt11/{quote}"),
|
||||
)
|
||||
self.raise_on_error_request(resp)
|
||||
return_dict = resp.json()
|
||||
return PostMeltQuoteResponse.parse_obj(return_dict)
|
||||
|
||||
@async_set_httpx_client
|
||||
@async_ensure_mint_loaded
|
||||
async def melt(
|
||||
|
||||
@@ -162,7 +162,7 @@ class Wallet(
|
||||
self.keysets = {k.id: k for k in keysets_active_unit}
|
||||
else:
|
||||
self.keysets = {k.id: k for k in keysets_list}
|
||||
keysets_str = ' '.join([f"{i} {k.unit}" for i, k in self.keysets.items()])
|
||||
keysets_str = " ".join([f"{i} {k.unit}" for i, k in self.keysets.items()])
|
||||
logger.debug(f"Loaded keysets: {keysets_str}")
|
||||
return self
|
||||
|
||||
@@ -351,10 +351,9 @@ class Wallet(
|
||||
for keyset_id in self.keysets:
|
||||
proofs = await get_proofs(db=self.db, id=keyset_id, conn=conn)
|
||||
self.proofs.extend(proofs)
|
||||
keysets_str = ' '.join([f"{k.id} ({k.unit})" for k in self.keysets.values()])
|
||||
keysets_str = " ".join([f"{k.id} ({k.unit})" for k in self.keysets.values()])
|
||||
logger.trace(f"Proofs loaded for keysets: {keysets_str}")
|
||||
|
||||
|
||||
async def load_keysets_from_db(
|
||||
self, url: Union[str, None] = "", unit: Union[str, None] = ""
|
||||
):
|
||||
@@ -1020,6 +1019,11 @@ class Wallet(
|
||||
"""
|
||||
invalidated_proofs: List[Proof] = []
|
||||
if check_spendable:
|
||||
# checks proofs in batches
|
||||
for _proofs in [
|
||||
proofs[i : i + settings.proofs_batch_size]
|
||||
for i in range(0, len(proofs), settings.proofs_batch_size)
|
||||
]:
|
||||
proof_states = await self.check_proof_state(proofs)
|
||||
for i, state in enumerate(proof_states.states):
|
||||
if state.spent:
|
||||
@@ -1033,9 +1037,12 @@ class Wallet(
|
||||
f" {self.unit.str(sum_proofs(invalidated_proofs))}."
|
||||
)
|
||||
|
||||
async with self.db.connect() as conn:
|
||||
for p in invalidated_proofs:
|
||||
await invalidate_proof(p, db=self.db, conn=conn)
|
||||
try:
|
||||
# mark proof as spent
|
||||
await invalidate_proof(p, db=self.db)
|
||||
except Exception as e:
|
||||
logger.error(f"DB error while invalidating proof: {e}")
|
||||
|
||||
invalidate_secrets = [p.secret for p in invalidated_proofs]
|
||||
self.proofs = list(
|
||||
|
||||
@@ -4,6 +4,7 @@ import pytest_asyncio
|
||||
from cashu.core.base import MeltQuoteState
|
||||
from cashu.core.helpers import sum_proofs
|
||||
from cashu.core.models import PostMeltQuoteRequest, PostMintQuoteRequest
|
||||
from cashu.core.settings import settings
|
||||
from cashu.mint.ledger import Ledger
|
||||
from cashu.wallet.wallet import Wallet
|
||||
from cashu.wallet.wallet import Wallet as Wallet1
|
||||
@@ -55,6 +56,13 @@ async def test_melt_internal(wallet1: Wallet, ledger: Ledger):
|
||||
assert melt_quote.amount == 64
|
||||
assert melt_quote.fee_reserve == 0
|
||||
|
||||
if not settings.debug_mint_only_deprecated:
|
||||
melt_quote_response_pre_payment = await wallet1.get_melt_quote(melt_quote.quote)
|
||||
assert (
|
||||
not melt_quote_response_pre_payment.state == MeltQuoteState.paid.value
|
||||
), "melt quote should not be paid"
|
||||
assert melt_quote_response_pre_payment.amount == 64
|
||||
|
||||
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
||||
assert melt_quote_pre_payment.unpaid
|
||||
@@ -89,6 +97,13 @@ async def test_melt_external(wallet1: Wallet, ledger: Ledger):
|
||||
PostMeltQuoteRequest(request=invoice_payment_request, unit="sat")
|
||||
)
|
||||
|
||||
if not settings.debug_mint_only_deprecated:
|
||||
melt_quote_response_pre_payment = await wallet1.get_melt_quote(melt_quote.quote)
|
||||
assert (
|
||||
melt_quote_response_pre_payment.state == MeltQuoteState.unpaid.value
|
||||
), "melt quote should not be paid"
|
||||
assert melt_quote_response_pre_payment.amount == melt_quote.amount
|
||||
|
||||
melt_quote_pre_payment = await ledger.get_melt_quote(melt_quote.quote)
|
||||
assert not melt_quote_pre_payment.paid, "melt quote should not be paid"
|
||||
assert melt_quote_pre_payment.unpaid
|
||||
@@ -109,7 +124,12 @@ async def test_mint_internal(wallet1: Wallet, ledger: Ledger):
|
||||
mint_quote = await ledger.get_mint_quote(invoice.id)
|
||||
|
||||
assert mint_quote.paid, "mint quote should be paid"
|
||||
assert mint_quote.paid
|
||||
|
||||
if not settings.debug_mint_only_deprecated:
|
||||
mint_quote_resp = await wallet1.get_mint_quote(invoice.id)
|
||||
assert (
|
||||
mint_quote_resp.state == MeltQuoteState.paid.value
|
||||
), "mint quote should be paid"
|
||||
|
||||
output_amounts = [128]
|
||||
secrets, rs, derivation_paths = await wallet1.generate_n_secrets(
|
||||
@@ -139,6 +159,10 @@ async def test_mint_external(wallet1: Wallet, ledger: Ledger):
|
||||
assert not mint_quote.paid, "mint quote already paid"
|
||||
assert mint_quote.unpaid
|
||||
|
||||
if not settings.debug_mint_only_deprecated:
|
||||
mint_quote_resp = await wallet1.get_mint_quote(quote.quote)
|
||||
assert not mint_quote_resp.paid, "mint quote should not be paid"
|
||||
|
||||
await assert_err(
|
||||
wallet1.mint(128, id=quote.quote),
|
||||
"quote not paid",
|
||||
|
||||
@@ -4,7 +4,7 @@ from typing import List, Union
|
||||
import pytest
|
||||
import pytest_asyncio
|
||||
|
||||
from cashu.core.base import Proof
|
||||
from cashu.core.base import MintQuoteState, Proof
|
||||
from cashu.core.errors import CashuError, KeysetNotFoundError
|
||||
from cashu.core.helpers import sum_proofs
|
||||
from cashu.core.settings import settings
|
||||
@@ -168,6 +168,11 @@ async def test_request_mint(wallet1: Wallet):
|
||||
async def test_mint(wallet1: Wallet):
|
||||
invoice = await wallet1.request_mint(64)
|
||||
await pay_if_regtest(invoice.bolt11)
|
||||
if not settings.debug_mint_only_deprecated:
|
||||
quote_resp = await wallet1.get_mint_quote(invoice.id)
|
||||
assert quote_resp.request == invoice.bolt11
|
||||
assert quote_resp.state == MintQuoteState.paid.value
|
||||
|
||||
expected_proof_amounts = wallet1.split_wallet_state(64)
|
||||
await wallet1.mint(64, id=invoice.id)
|
||||
assert wallet1.balance == 64
|
||||
@@ -307,6 +312,10 @@ async def test_melt(wallet1: Wallet):
|
||||
assert total_amount == 64
|
||||
assert quote.fee_reserve == 0
|
||||
|
||||
if not settings.debug_mint_only_deprecated:
|
||||
quote_resp = await wallet1.get_melt_quote(quote.quote)
|
||||
assert quote_resp.amount == quote.amount
|
||||
|
||||
_, send_proofs = await wallet1.swap_to_send(wallet1.proofs, total_amount)
|
||||
|
||||
melt_response = await wallet1.melt(
|
||||
|
||||
@@ -110,6 +110,9 @@ def test_balance(cli_prefix):
|
||||
|
||||
@pytest.mark.skipif(is_regtest, reason="only works with FakeWallet")
|
||||
def test_invoice(mint, cli_prefix):
|
||||
if settings.debug_mint_only_deprecated:
|
||||
pytest.skip("only works with v1 API")
|
||||
|
||||
runner = CliRunner()
|
||||
result = runner.invoke(
|
||||
cli,
|
||||
|
||||
Reference in New Issue
Block a user