Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
155 changes: 45 additions & 110 deletions cashu/wallet/v1_api.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import json
import uuid
from posixpath import join
from typing import List, Optional, Tuple, Union

Expand All @@ -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,
Expand All @@ -25,7 +22,6 @@
from ..core.crypto.secp import PublicKey
from ..core.db import Database
from ..core.models import (
CheckFeesResponse_deprecated,
GetInfoResponse,
KeysetsResponse,
KeysetsResponseKeyset,
Expand Down Expand Up @@ -55,7 +51,6 @@
invalidate_proof,
)
from .protocols import SupportsAuth
from .wallet_deprecated import LedgerAPIDeprecated

GET = "GET"
POST = "POST"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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)

Expand All @@ -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)

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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]
Expand All @@ -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"
Expand All @@ -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"])
Expand All @@ -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)

Expand Down
Loading
Loading