diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4819a0f8..141d2d7a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,9 @@ jobs: os: [ubuntu-latest] python-version: ["3.10"] poetry-version: ["1.8.5"] - mint-only-deprecated: ["false", "true"] + # Wallet v0 / deprecated mint API support was removed in https://github.com/cashubtc/nutshell/pull/814; deprecated-only CI runs are no longer valid. + # mint-only-deprecated: ["false", "true"] + mint-only-deprecated: ["false"] mint-database: ["./test_data/test_mint", "postgres://cashu:cashu@localhost:5432/cashu"] backend-wallet-class: ["FakeWallet"] uses: ./.github/workflows/tests.yml diff --git a/cashu/wallet/v1_api.py b/cashu/wallet/v1_api.py index 3e944e63..55bdba6a 100644 --- a/cashu/wallet/v1_api.py +++ b/cashu/wallet/v1_api.py @@ -1,5 +1,4 @@ import json -import uuid from posixpath import join from typing import List, Optional, Tuple, Union @@ -9,8 +8,6 @@ from loguru import logger from pydantic import ValidationError -from cashu.wallet.crud import get_bolt11_melt_quote - from ..core.base import ( AuthProof, BlindedMessage, @@ -25,7 +22,6 @@ from ..core.crypto.secp import PublicKey from ..core.db import Database from ..core.models import ( - CheckFeesResponse_deprecated, GetInfoResponse, KeysetsResponse, KeysetsResponseKeyset, @@ -55,7 +51,6 @@ invalidate_proof, ) from .protocols import SupportsAuth -from .wallet_deprecated import LedgerAPIDeprecated GET = "GET" POST = "POST" @@ -110,7 +105,7 @@ async def wrapper(self, *args, **kwargs): return wrapper -class LedgerAPI(LedgerAPIDeprecated, SupportsAuth): +class LedgerAPI(SupportsAuth): tor: TorProxy httpx: httpx.AsyncClient api_prefix = "v1" @@ -149,6 +144,19 @@ def raise_on_error_request( raise Exception(error_message) resp.raise_for_status() + def raise_on_unsupported_version(self, resp: Response, endpoint:str): + """ + Helper that handles unsupported endpoints (pre-v1 mints). + If mint returns 404 (endpoint not present), raise a clear exception. + Otherwise delegate to raise_on_error_request for other status codes. + """ + + if resp.status_code == 404: + raise Exception(f"The mint at {self.url} does not support endpoint {endpoint}.") + + #For other non-200 statuses, raise using existing logic + self.raise_on_error_request(resp) + async def _request(self, method: str, path: str, noprefix=False, **kwargs): if not noprefix: path = join(self.api_prefix, path) @@ -227,13 +235,10 @@ async def _get_keys(self) -> List[WalletKeyset]: Exception: If no keys are received from the mint """ resp = await self._request(GET, "keys") - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self._get_keys_deprecated(self.url) - return [ret] - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + + #if mint doesn't support v1 keys endpoint, fail explicitly + self.raise_on_unsupported_version(resp, "Get /v1/keys") + keys_dict: dict = resp.json() assert len(keys_dict), Exception("did not receive any keys") keys = KeysResponse.parse_obj(keys_dict) @@ -269,13 +274,9 @@ async def _get_keyset(self, keyset_id: str) -> WalletKeyset: """ keyset_id_urlsafe = keyset_id.replace("+", "-").replace("/", "_") resp = await self._request(GET, f"keys/{keyset_id_urlsafe}") - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self._get_keyset_deprecated(self.url, keyset_id) - return ret - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + + #if mint doesn't support v1 keyset endpoint, fail explicitly + self.raise_on_unsupported_version(resp, f"GET /v1/keys/{keyset_id_urlsafe}") keys_dict = resp.json() assert len(keys_dict), Exception("did not receive any keys") @@ -304,13 +305,7 @@ async def _get_keysets(self) -> List[KeysetsResponseKeyset]: Exception: If no keysets are received from the mint """ resp = await self._request(GET, "keysets") - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self._get_keysets_deprecated(self.url) - return ret - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + self.raise_on_unsupported_version(resp, "Get /v1/keysets") keysets_dict = resp.json() keysets = KeysetsResponse.parse_obj(keysets_dict).keysets @@ -329,13 +324,8 @@ async def _get_info(self) -> GetInfoResponse: Exception: If the mint info request fails """ resp = await self._request(GET, "/v1/info", noprefix=True) - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self._get_info_deprecated() - return ret - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + self.raise_on_unsupported_version(resp, "Get /v1/info") + data: dict = resp.json() mint_info: GetInfoResponse = GetInfoResponse.parse_obj(data) return mint_info @@ -372,13 +362,9 @@ async def mint_quote( json=payload.dict(), ) - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self.request_mint_deprecated(amount) - return ret - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + #if mint doesn't support v1 endpoint, fail explicitly + self.raise_on_unsupported_version(resp, "POST /v1/mint/quote/bolt11") + return_dict = resp.json() return PostMintQuoteResponse.parse_obj(return_dict) @@ -394,7 +380,7 @@ async def get_mint_quote(self, quote: str) -> PostMintQuoteResponse: PostMintQuoteResponse: Mint Quote Response """ resp = await self._request(GET, f"mint/quote/bolt11/{quote}") - self.raise_on_error_request(resp) + self.raise_on_unsupported_version(resp, f"GET /v1/mint/quote/bolt11/{quote}") return_dict = resp.json() return PostMintQuoteResponse.parse_obj(return_dict) @@ -438,13 +424,9 @@ def _mintrequest_include_fields(outputs: List[BlindedMessage]): "mint/bolt11", json=payload, # type: ignore ) - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self.mint_deprecated(outputs, quote) - return ret - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + + # fail explicitly if mint doesn't support v1 mint endpoint + self.raise_on_unsupported_version(resp, f"POST /v1/mint/{quote}") response_dict = resp.json() logger.trace(f"Lightning invoice checked. POST {self.api_prefix}/mint/bolt11") promises = PostMintResponse.parse_obj(response_dict).signatures @@ -475,28 +457,9 @@ async def melt_quote( "melt/quote/bolt11", json=payload.dict(), ) - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret: CheckFeesResponse_deprecated = await self.check_fees_deprecated( - payment_request - ) - quote_id = f"deprecated_{uuid.uuid4()}" - amount_sat = ( - amount_msat // 1000 if amount_msat else invoice_obj.amount_msat // 1000 - ) - return PostMeltQuoteResponse( - quote=quote_id, - amount=amount_sat, - unit=unit.name, - request=payment_request, - fee_reserve=ret.fee or 0, - paid=False, - state=MeltQuoteState.unpaid.value, - expiry=invoice_obj.expiry, - ) - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + + #if mint doesn't support v1 melt-quote endpoint, fail explicitly + self.raise_on_unsupported_version(resp, "POST /v1/melt/quote") return_dict = resp.json() return PostMeltQuoteResponse.parse_obj(return_dict) @@ -554,16 +517,9 @@ def _meltrequest_include_fields( return PostMeltQuoteResponse.parse_obj(return_dict) except Exception as e: # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - melt_quote = await get_bolt11_melt_quote(quote=quote, db=self.db) - assert melt_quote, f"no melt_quote found for id {quote}" - ret: PostMeltResponse_deprecated = await self.melt_deprecated( - proofs=proofs, outputs=outputs, invoice=melt_quote.request - ) - elif isinstance(e, ValidationError): + # before 0.16.0, mints return PostMeltResponse_deprecated + if isinstance(e, ValidationError): # BEGIN backwards compatibility < 0.16.0 - # before 0.16.0, mints return PostMeltResponse_deprecated ret = PostMeltResponse_deprecated.parse_obj(return_dict) # END backwards compatibility < 0.16.0 else: @@ -617,13 +573,9 @@ def _splitrequest_include_fields(proofs: List[Proof]): "swap", json=split_payload.dict(include=_splitrequest_include_fields(proofs)), # type: ignore ) - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self.split_deprecated(proofs, outputs) - return ret - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + + #if mint doesn't support v1 swap endpoint, fail explicitly + self.raise_on_unsupported_version(resp, "POST /v1/swap") promises_dict = resp.json() mint_response = PostSwapResponse.parse_obj(promises_dict) promises = [BlindedSignature(**p.dict()) for p in mint_response.signatures] @@ -645,21 +597,9 @@ async def check_proof_state(self, proofs: List[Proof]) -> PostCheckStateResponse "checkstate", json=payload.dict(), ) - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self.check_proof_state_deprecated(proofs) - # convert CheckSpendableResponse_deprecated to CheckSpendableResponse - states: List[ProofState] = [] - for spendable, pending, p in zip(ret.spendable, ret.pending, proofs): - if spendable and not pending: - states.append(ProofState(Y=p.Y, state=ProofSpentState.unspent)) - elif spendable and pending: - states.append(ProofState(Y=p.Y, state=ProofSpentState.pending)) - else: - states.append(ProofState(Y=p.Y, state=ProofSpentState.spent)) - return PostCheckStateResponse(states=states) - # END backwards compatibility < 0.15.0 + + #fail if endpoint missing + self.raise_on_unsupported_version(resp, "POST /v1/checkstate") # BEGIN backwards compatibility < 0.16.0 # payload has "secrets" instead of "Ys" @@ -669,7 +609,7 @@ async def check_proof_state(self, proofs: List[Proof]) -> PostCheckStateResponse ) payload_secrets = {"secrets": [p.secret for p in proofs]} resp_secrets = await self._request(POST, "checkstate", json=payload_secrets) - self.raise_on_error(resp_secrets) + self.raise_on_error_request(resp_secrets) states = [ ProofState(Y=p.Y, state=ProofSpentState(s["state"])) for p, s in zip(proofs, resp_secrets.json()["states"]) @@ -690,13 +630,8 @@ async def restore_promises( """ payload = PostMintRequest(quote="restore", outputs=outputs) resp = await self._request(POST, "restore", json=payload.dict()) - # BEGIN backwards compatibility < 0.15.0 - # assume the mint has not upgraded yet if we get a 404 - if resp.status_code == 404: - ret = await self.restore_promises_deprecated(outputs) - return ret - # END backwards compatibility < 0.15.0 - self.raise_on_error_request(resp) + #fail if endpoint missing + self.raise_on_unsupported_version(resp, "POST /v1/restore") response_dict = resp.json() returnObj = PostRestoreResponse.parse_obj(response_dict) diff --git a/cashu/wallet/wallet_deprecated.py b/cashu/wallet/wallet_deprecated.py deleted file mode 100644 index fbd80250..00000000 --- a/cashu/wallet/wallet_deprecated.py +++ /dev/null @@ -1,489 +0,0 @@ -import json -from posixpath import join -from typing import List, Optional, Tuple, Union - -import bolt11 -import httpx -from httpx import Response -from loguru import logger - -from ..core.base import ( - BlindedMessage, - BlindedMessage_Deprecated, - BlindedSignature, - MintQuoteState, - Proof, - WalletKeyset, -) -from ..core.crypto.secp import PublicKey -from ..core.models import ( - CheckFeesRequest_deprecated, - CheckFeesResponse_deprecated, - CheckSpendableRequest_deprecated, - CheckSpendableResponse_deprecated, - GetInfoResponse, - GetInfoResponse_deprecated, - GetMintResponse_deprecated, - KeysetsResponse_deprecated, - KeysetsResponseKeyset, - PostMeltRequest_deprecated, - PostMeltResponse_deprecated, - PostMintQuoteResponse, - PostMintRequest_deprecated, - PostMintResponse_deprecated, - PostRestoreResponse, - PostSwapRequest_Deprecated, - PostSwapResponse_Deprecated, -) -from ..core.settings import settings -from ..tor.tor import TorProxy -from .protocols import SupportsHttpxClient, SupportsMintURL - - -def async_set_httpx_client(func): - """ - Decorator that wraps around any async class method of LedgerAPI that makes - API calls. Sets some HTTP headers and starts a Tor instance if none is - already running and and sets local proxy to use it. - """ - - async def wrapper(self, *args, **kwargs): - # set proxy - proxies_dict = {} - proxy_url: Union[str, None] = None - if settings.tor and TorProxy().check_platform(): - self.tor = TorProxy(timeout=True) - self.tor.run_daemon(verbose=True) - proxy_url = "socks5://localhost:9050" - elif settings.socks_proxy: - proxy_url = f"socks5://{settings.socks_proxy}" - elif settings.http_proxy: - proxy_url = settings.http_proxy - if proxy_url: - proxies_dict.update({"all://": proxy_url}) - - headers_dict = {"Client-version": settings.version} - - self.httpx = httpx.AsyncClient( - verify=not settings.debug, - proxies=proxies_dict, # type: ignore - headers=headers_dict, - base_url=self.url, - timeout=None if settings.debug else 60, - ) - return await func(self, *args, **kwargs) - - return wrapper - - -def async_ensure_mint_loaded_deprecated(func): - """Decorator that ensures that the mint is loaded before calling the wrapped - function. If the mint is not loaded, it will be loaded first. - """ - - async def wrapper(self, *args, **kwargs): - if not self.keysets: - await self.load_mint() - return await func(self, *args, **kwargs) - - return wrapper - - -class LedgerAPIDeprecated(SupportsHttpxClient, SupportsMintURL): - """Deprecated wallet class, will be removed in the future.""" - - httpx: httpx.AsyncClient - url: str - - @staticmethod - def raise_on_error( - resp: Response, - ) -> None: - """Raises an exception if the response from the mint contains an error. - - Args: - resp_dict (Response): Response dict (previously JSON) from mint - - Raises: - Exception: if the response contains an error - """ - try: - resp_dict = resp.json() - except json.JSONDecodeError: - # if we can't decode the response, raise for status - resp.raise_for_status() - return - if "detail" in resp_dict: - logger.trace(f"Error from mint: {resp_dict}") - error_message = f"Mint Error: {resp_dict['detail']}" - if "code" in resp_dict: - error_message += f" (Code: {resp_dict['code']})" - raise Exception(error_message) - # raise for status if no error - resp.raise_for_status() - - @async_set_httpx_client - async def _get_info_deprecated(self) -> GetInfoResponse: - """API that gets the mint info. - - Returns: - GetInfoResponse: Current mint info - - Raises: - Exception: If the mint info request fails - """ - logger.warning(f"Using deprecated API call: {self.url}/info") - resp = await self.httpx.get( - join(self.url, "/info"), - ) - self.raise_on_error(resp) - data: dict = resp.json() - mint_info_deprecated: GetInfoResponse_deprecated = ( - GetInfoResponse_deprecated.parse_obj(data) - ) - mint_info = GetInfoResponse( - **mint_info_deprecated.dict(exclude={"parameter", "nuts", "contact"}) - ) - # monkeypatch nuts - mint_info.nuts = {} - return mint_info - - @async_set_httpx_client - async def _get_keys_deprecated(self, url: str) -> WalletKeyset: - """API that gets the current keys of the mint - - Args: - url (str): Mint URL - - Returns: - WalletKeyset: Current mint keyset - - Raises: - Exception: If no keys are received from the mint - """ - logger.warning(f"Using deprecated API call: {url}/keys") - resp = await self.httpx.get( - f"{url}/keys", - ) - self.raise_on_error(resp) - keys: dict = resp.json() - assert len(keys), Exception("did not receive any keys") - keyset_keys = { - int(amt): PublicKey(bytes.fromhex(val), raw=True) - for amt, val in keys.items() - } - keyset = WalletKeyset(unit="sat", public_keys=keyset_keys, mint_url=url) - return keyset - - @async_set_httpx_client - async def _get_keyset_deprecated(self, url: str, keyset_id: str) -> WalletKeyset: - """API that gets the keys of a specific keyset from the mint. - - - Args: - url (str): Mint URL - keyset_id (str): base64 keyset ID, needs to be urlsafe-encoded before sending to mint (done in this method) - - Returns: - WalletKeyset: Keyset with ID keyset_id - - Raises: - Exception: If no keys are received from the mint - """ - logger.warning(f"Using deprecated API call: {url}/keys/{keyset_id}") - keyset_id_urlsafe = keyset_id.replace("+", "-").replace("/", "_") - resp = await self.httpx.get( - f"{url}/keys/{keyset_id_urlsafe}", - ) - self.raise_on_error(resp) - keys = resp.json() - assert len(keys), Exception("did not receive any keys") - keyset_keys = { - int(amt): PublicKey(bytes.fromhex(val), raw=True) - for amt, val in keys.items() - } - keyset = WalletKeyset( - unit="sat", - id=keyset_id, - public_keys=keyset_keys, - mint_url=url, - ) - return keyset - - @async_set_httpx_client - async def _get_keysets_deprecated(self, url: str) -> List[KeysetsResponseKeyset]: - """API that gets a list of all active keysets of the mint. - - Args: - url (str): Mint URL - - Returns: - KeysetsResponse (List[str]): List of all active keyset IDs of the mint - - Raises: - Exception: If no keysets are received from the mint - """ - logger.warning(f"Using deprecated API call: {url}/keysets") - resp = await self.httpx.get( - f"{url}/keysets", - ) - self.raise_on_error(resp) - keysets_dict = resp.json() - keysets = KeysetsResponse_deprecated.parse_obj(keysets_dict) - assert len(keysets.keysets), Exception("did not receive any keysets") - keysets_new = [ - KeysetsResponseKeyset(id=id, unit="sat", active=True) - for id in keysets.keysets - ] - return keysets_new - - @async_set_httpx_client - @async_ensure_mint_loaded_deprecated - async def request_mint_deprecated(self, amount) -> PostMintQuoteResponse: - """Requests a mint from the server and returns Lightning invoice. - - Args: - amount (int): Amount of tokens to mint - - Returns: - Invoice: Lightning invoice - - Raises: - Exception: If the mint request fails - """ - logger.warning("Using deprecated API call: Requesting mint: GET /mint") - resp = await self.httpx.get(f"{self.url}/mint", params={"amount": amount}) - - # Verbose logging of requests and responses when enabled - if settings.wallet_verbose_requests: - request_info = f"Request: GET {self.url}/mint?amount={amount}" - print(request_info) - - response_info = f"Response: {resp.status_code}" - try: - json_response = resp.json() - response_info += f"\n{json.dumps(json_response, indent=2)}" - except json.JSONDecodeError: - response_info += f"\n{resp.text}" - print(response_info) - - self.raise_on_error(resp) - return_dict = resp.json() - mint_response = GetMintResponse_deprecated.parse_obj(return_dict) - decoded_invoice = bolt11.decode(mint_response.pr) - assert decoded_invoice.amount_msat, Exception("no amount in invoice") - return PostMintQuoteResponse( - quote=mint_response.hash, - request=mint_response.pr, - amount=decoded_invoice.amount_msat // 1000, - unit="sat", - paid=False, - state=MintQuoteState.unpaid.value, - expiry=decoded_invoice.date + (decoded_invoice.expiry or 0), - pubkey=None, - ) - - @async_set_httpx_client - @async_ensure_mint_loaded_deprecated - async def mint_deprecated( - self, outputs: List[BlindedMessage], hash: Optional[str] = None - ) -> List[BlindedSignature]: - """Mints new coins and returns a proof of promise. - - Args: - outputs (List[BlindedMessage]): Outputs to mint new tokens with - hash (str, optional): Hash of the paid invoice. Defaults to None. - - Returns: - list[Proof]: List of proofs. - - Raises: - Exception: If the minting fails - """ - outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs] - outputs_payload = PostMintRequest_deprecated(outputs=outputs_deprecated) - - def _mintrequest_include_fields(outputs: List[BlindedMessage]): - """strips away fields from the model that aren't necessary for the /mint""" - outputs_include = {"amount", "B_"} - return { - "outputs": {i: outputs_include for i in range(len(outputs))}, - } - - payload = outputs_payload.dict(include=_mintrequest_include_fields(outputs)) # type: ignore - logger.warning( - "Using deprecated API call:Checking Lightning invoice. POST /mint" - ) - resp = await self.httpx.post( - f"{self.url}/mint", - json=payload, - params={ - "hash": hash, - "payment_hash": hash, # backwards compatibility pre 0.12.0 - }, - ) - - # Verbose logging of requests and responses when enabled - if settings.wallet_verbose_requests: - request_info = f"Request: POST {self.url}/mint?hash={hash}" - if payload: - request_info += f"\nPayload: {json.dumps(payload, indent=2)}" - print(request_info) - - response_info = f"Response: {resp.status_code}" - try: - json_response = resp.json() - response_info += f"\n{json.dumps(json_response, indent=2)}" - except json.JSONDecodeError: - response_info += f"\n{resp.text}" - print(response_info) - - self.raise_on_error(resp) - response_dict = resp.json() - logger.trace("Lightning invoice checked. POST /mint") - promises = PostMintResponse_deprecated.parse_obj(response_dict).promises - return promises - - @async_set_httpx_client - @async_ensure_mint_loaded_deprecated - async def melt_deprecated( - self, proofs: List[Proof], invoice: str, outputs: Optional[List[BlindedMessage]] - ) -> PostMeltResponse_deprecated: - """ - Accepts proofs and a lightning invoice to pay in exchange. - """ - logger.warning("Using deprecated API call: POST /melt") - outputs_deprecated = ( - [BlindedMessage_Deprecated(**o.dict()) for o in outputs] - if outputs - else None - ) - payload = PostMeltRequest_deprecated( - proofs=proofs, pr=invoice, outputs=outputs_deprecated - ) - - def _meltrequest_include_fields(proofs: List[Proof]): - """strips away fields from the model that aren't necessary for the /melt""" - proofs_include = {"id", "amount", "secret", "C", "witness"} - return { - "proofs": {i: proofs_include for i in range(len(proofs))}, - "pr": ..., - "outputs": ..., - } - - resp = await self.httpx.post( - f"{self.url}/melt", - json=payload.dict(include=_meltrequest_include_fields(proofs)), # type: ignore - ) - self.raise_on_error(resp) - return_dict = resp.json() - - return PostMeltResponse_deprecated.parse_obj(return_dict) - - @async_set_httpx_client - @async_ensure_mint_loaded_deprecated - async def split_deprecated( - self, - proofs: List[Proof], - outputs: List[BlindedMessage], - ) -> List[BlindedSignature]: - """Consume proofs and create new promises based on amount split.""" - logger.warning("Using deprecated API call: Calling split. POST /split") - outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs] - split_payload = PostSwapRequest_Deprecated( - proofs=proofs, outputs=outputs_deprecated - ) - - # construct payload - def _splitrequest_include_fields(proofs: List[Proof]): - """strips away fields from the model that aren't necessary for the /split""" - proofs_include = { - "id", - "amount", - "secret", - "C", - "witness", - } - return { - "outputs": ..., - "proofs": {i: proofs_include for i in range(len(proofs))}, - } - - resp = await self.httpx.post( - join(self.url, "/split"), - json=split_payload.dict(include=_splitrequest_include_fields(proofs)), # type: ignore - ) - self.raise_on_error(resp) - promises_dict = resp.json() - mint_response = PostSwapResponse_Deprecated.parse_obj(promises_dict) - promises = [BlindedSignature(**p.dict()) for p in mint_response.promises] - - if len(promises) == 0: - raise Exception("received no splits.") - - return promises - - @async_set_httpx_client - @async_ensure_mint_loaded_deprecated - async def check_proof_state_deprecated( - self, proofs: List[Proof] - ) -> CheckSpendableResponse_deprecated: - """ - Checks whether the secrets in proofs are already spent or not and returns a list of booleans. - """ - logger.warning("Using deprecated API call: POST /check") - payload = CheckSpendableRequest_deprecated(proofs=proofs) - - def _check_proof_state_include_fields(proofs): - """strips away fields from the model that aren't necessary for the /split""" - return { - "proofs": {i: {"secret"} for i in range(len(proofs))}, - } - - resp = await self.httpx.post( - join(self.url, "/check"), - json=payload.dict(include=_check_proof_state_include_fields(proofs)), # type: ignore - ) - self.raise_on_error(resp) - - return_dict = resp.json() - states = CheckSpendableResponse_deprecated.parse_obj(return_dict) - return states - - @async_set_httpx_client - @async_ensure_mint_loaded_deprecated - async def restore_promises_deprecated( - self, outputs: List[BlindedMessage] - ) -> Tuple[List[BlindedMessage], List[BlindedSignature]]: - """ - Asks the mint to restore promises corresponding to outputs. - """ - logger.warning("Using deprecated API call: POST /restore") - outputs_deprecated = [BlindedMessage_Deprecated(**o.dict()) for o in outputs] - payload = PostMintRequest_deprecated(outputs=outputs_deprecated) - resp = await self.httpx.post(join(self.url, "/restore"), json=payload.dict()) - self.raise_on_error(resp) - response_dict = resp.json() - returnObj = PostRestoreResponse.parse_obj(response_dict) - - # BEGIN backwards compatibility < 0.15.1 - # if the mint returns promises, duplicate into signatures - if returnObj.promises: - returnObj.signatures = returnObj.promises - # END backwards compatibility < 0.15.1 - - return returnObj.outputs, returnObj.signatures - - @async_set_httpx_client - @async_ensure_mint_loaded_deprecated - async def check_fees_deprecated(self, payment_request: str): - """Checks whether the Lightning payment is internal.""" - payload = CheckFeesRequest_deprecated(pr=payment_request) - resp = await self.httpx.post( - join(self.url, "/checkfees"), - json=payload.dict(), - ) - self.raise_on_error(resp) - - return_dict = resp.json() - return CheckFeesResponse_deprecated.parse_obj(return_dict) diff --git a/tests/mint/test_mint_api_deprecated.py b/tests/mint/test_mint_api_deprecated.py index 5426450b..408be589 100644 --- a/tests/mint/test_mint_api_deprecated.py +++ b/tests/mint/test_mint_api_deprecated.py @@ -15,6 +15,8 @@ from cashu.wallet.wallet import Wallet from tests.helpers import get_real_invoice, is_fake, is_regtest, pay_if_regtest +pytestmark = pytest.mark.skip(reason="Wallet v0 / deprecated mint API removed in https://github.com/cashubtc/nutshell/pull/814") + BASE_URL = "http://localhost:3337" diff --git a/tests/wallet/test_wallet.py b/tests/wallet/test_wallet.py index eb2aa9e4..f60be18c 100644 --- a/tests/wallet/test_wallet.py +++ b/tests/wallet/test_wallet.py @@ -1,4 +1,5 @@ import copy +from types import SimpleNamespace from typing import List, Union import pytest @@ -553,3 +554,39 @@ async def testactivate_keyset_specific_keyset(wallet1: Wallet): wallet1.activate_keyset(keyset_id="nonexistent"), KeysetNotFoundError("nonexistent"), ) + +def make_dummy_response(status_code: int, json_data=None): + def raise_for_status(): + if status_code >= 400: + raise Exception(f"HTTP {status_code}") + + return SimpleNamespace( + status_code=status_code, + json=lambda: json_data or {}, + raise_for_status=raise_for_status, + ) + +@pytest.mark.asyncio +async def test_raise_on_unsupported_version_404(wallet1: Wallet): + """ + If the mint returns 404 for an endpoint, the wallet should raise + a clear unsupported-version exception. + """ + resp = make_dummy_response(404) + + with pytest.raises(Exception) as excinfo: + wallet1.raise_on_unsupported_version(resp, "GET /v1/keys") + + assert "does not support endpoint GET /v1/keys" in str(excinfo.value) + + +@pytest.mark.asyncio +async def test_raise_on_unsupported_version_non_404(wallet1: Wallet): + """ + For non-404 responses, the function should delegate to + raise_on_error_request and NOT raise for a valid response. + """ + resp = make_dummy_response(200, {}) + + # should not raise + wallet1.raise_on_unsupported_version(resp, "GET /v1/keys") \ No newline at end of file