BREAKING: PostMeltRequest, CheckSpendableResponse ` (#106)

* fix PostMeltRequest and /checkfees to GET

* POST /check -> GET /check

* fix GetCheckSpendableResponse

* rename models

* make format

* revert GET

* bump version to 0.9

* skip nostr test
This commit is contained in:
calle
2023-01-30 09:13:46 +01:00
committed by GitHub
parent e63db82641
commit 7e39e1b036
11 changed files with 40 additions and 29 deletions

View File

@@ -115,7 +115,7 @@ cashu info
Returns: Returns:
```bash ```bash
Version: 0.8.4 Version: 0.9.0
Debug: False Debug: False
Cashu dir: /home/user/.cashu Cashu dir: /home/user/.cashu
Wallet: wallet Wallet: wallet

View File

@@ -138,7 +138,7 @@ class GetMintResponse(BaseModel):
class PostMeltRequest(BaseModel): class PostMeltRequest(BaseModel):
proofs: List[Proof] proofs: List[Proof]
invoice: str pr: str
class GetMeltResponse(BaseModel): class GetMeltResponse(BaseModel):
@@ -163,11 +163,15 @@ class PostSplitResponse(BaseModel):
# ------- API: CHECK ------- # ------- API: CHECK -------
class GetCheckSpendableRequest(BaseModel): class CheckSpendableRequest(BaseModel):
proofs: List[Proof] proofs: List[Proof]
class GetCheckFeesRequest(BaseModel): class CheckSpendableResponse(BaseModel):
spendable: List[bool]
class CheckFeesRequest(BaseModel):
pr: str pr: str

View File

@@ -66,4 +66,4 @@ NOSTR_RELAYS = env.list(
) )
MAX_ORDER = 64 MAX_ORDER = 64
VERSION = "0.8.4" VERSION = "0.9.0"

View File

@@ -412,7 +412,7 @@ class Ledger:
async def check_spendable(self, proofs: List[Proof]): async def check_spendable(self, proofs: List[Proof]):
"""Checks if all provided proofs are valid and still spendable (i.e. have not been spent).""" """Checks if all provided proofs are valid and still spendable (i.e. have not been spent)."""
return {i: self._check_spendable(p) for i, p in enumerate(proofs)} return [self._check_spendable(p) for p in proofs]
async def check_fees(self, pr: str): async def check_fees(self, pr: str):
"""Returns the fees (in msat) required to pay this pr.""" """Returns the fees (in msat) required to pay this pr."""

View File

@@ -6,9 +6,10 @@ from secp256k1 import PublicKey
from cashu.core.base import ( from cashu.core.base import (
BlindedMessage, BlindedMessage,
BlindedSignature, BlindedSignature,
CheckFeesRequest,
CheckFeesResponse, CheckFeesResponse,
GetCheckFeesRequest, CheckSpendableRequest,
GetCheckSpendableRequest, CheckSpendableResponse,
GetMeltResponse, GetMeltResponse,
GetMintResponse, GetMintResponse,
KeysetsResponse, KeysetsResponse,
@@ -109,7 +110,7 @@ async def melt(payload: PostMeltRequest) -> Union[CashuError, GetMeltResponse]:
Requests tokens to be destroyed and sent out via Lightning. Requests tokens to be destroyed and sent out via Lightning.
""" """
try: try:
ok, preimage = await ledger.melt(payload.proofs, payload.invoice) ok, preimage = await ledger.melt(payload.proofs, payload.pr)
resp = GetMeltResponse(paid=ok, preimage=preimage) resp = GetMeltResponse(paid=ok, preimage=preimage)
except Exception as exc: except Exception as exc:
return CashuError(code=0, error=str(exc)) return CashuError(code=0, error=str(exc))
@@ -121,9 +122,12 @@ async def melt(payload: PostMeltRequest) -> Union[CashuError, GetMeltResponse]:
name="Check spendable", name="Check spendable",
summary="Check whether a proof has already been spent", summary="Check whether a proof has already been spent",
) )
async def check_spendable(payload: GetCheckSpendableRequest) -> Dict[int, bool]: async def check_spendable(
payload: CheckSpendableRequest,
) -> CheckSpendableResponse:
"""Check whether a secret has been spent already or not.""" """Check whether a secret has been spent already or not."""
return await ledger.check_spendable(payload.proofs) spendableList = await ledger.check_spendable(payload.proofs)
return CheckSpendableResponse(spendable=spendableList)
@router.post( @router.post(
@@ -131,7 +135,7 @@ async def check_spendable(payload: GetCheckSpendableRequest) -> Dict[int, bool]:
name="Check fees", name="Check fees",
summary="Check fee reserve for a Lightning payment", summary="Check fee reserve for a Lightning payment",
) )
async def check_fees(payload: GetCheckFeesRequest) -> CheckFeesResponse: async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse:
""" """
Responds with the fees necessary to pay a Lightning invoice. Responds with the fees necessary to pay a Lightning invoice.
Used by wallets for figuring out the fees they need to supply together with the payment amount. Used by wallets for figuring out the fees they need to supply together with the payment amount.

View File

@@ -15,8 +15,9 @@ import cashu.core.bolt11 as bolt11
from cashu.core.base import ( from cashu.core.base import (
BlindedMessage, BlindedMessage,
BlindedSignature, BlindedSignature,
GetCheckFeesRequest, CheckFeesRequest,
GetCheckSpendableRequest, CheckSpendableRequest,
CheckSpendableResponse,
GetMintResponse, GetMintResponse,
Invoice, Invoice,
KeysetsResponse, KeysetsResponse,
@@ -339,7 +340,7 @@ class LedgerAPI:
""" """
Cheks whether the secrets in proofs are already spent or not and returns a list of booleans. Cheks whether the secrets in proofs are already spent or not and returns a list of booleans.
""" """
payload = GetCheckSpendableRequest(proofs=proofs) payload = CheckSpendableRequest(proofs=proofs)
def _check_spendable_include_fields(proofs): def _check_spendable_include_fields(proofs):
"""strips away fields from the model that aren't necessary for the /split""" """strips away fields from the model that aren't necessary for the /split"""
@@ -355,11 +356,12 @@ class LedgerAPI:
resp.raise_for_status() resp.raise_for_status()
return_dict = resp.json() return_dict = resp.json()
self.raise_on_error(return_dict) self.raise_on_error(return_dict)
return return_dict spendable = CheckSpendableResponse.parse_obj(return_dict)
return spendable
async def check_fees(self, payment_request: str): async def check_fees(self, payment_request: str):
"""Checks whether the Lightning payment is internal.""" """Checks whether the Lightning payment is internal."""
payload = GetCheckFeesRequest(pr=payment_request) payload = CheckFeesRequest(pr=payment_request)
self.s = self._set_requests() self.s = self._set_requests()
resp = self.s.post( resp = self.s.post(
self.url + "/checkfees", self.url + "/checkfees",
@@ -374,14 +376,14 @@ class LedgerAPI:
""" """
Accepts proofs and a lightning invoice to pay in exchange. Accepts proofs and a lightning invoice to pay in exchange.
""" """
payload = PostMeltRequest(proofs=proofs, invoice=invoice) payload = PostMeltRequest(proofs=proofs, pr=invoice)
def _meltrequest_include_fields(proofs): def _meltrequest_include_fields(proofs):
"""strips away fields from the model that aren't necessary for the /melt""" """strips away fields from the model that aren't necessary for the /melt"""
proofs_include = {"id", "amount", "secret", "C", "script"} proofs_include = {"id", "amount", "secret", "C", "script"}
return { return {
"amount": ..., "amount": ...,
"invoice": ..., "pr": ...,
"proofs": {i: proofs_include for i in range(len(proofs))}, "proofs": {i: proofs_include for i in range(len(proofs))},
} }
@@ -609,10 +611,10 @@ class Wallet(LedgerAPI):
"""Invalidates all spendable tokens supplied in proofs.""" """Invalidates all spendable tokens supplied in proofs."""
spendables = await self.check_spendable(proofs) spendables = await self.check_spendable(proofs)
invalidated_proofs = [] invalidated_proofs = []
for idx, spendable in spendables.items(): for i, spendable in enumerate(spendables.spendable):
if not spendable: if not spendable:
invalidated_proofs.append(proofs[int(idx)]) invalidated_proofs.append(proofs[i])
await invalidate_proof(proofs[int(idx)], db=self.db) await invalidate_proof(proofs[i], db=self.db)
invalidate_secrets = [p["secret"] for p in invalidated_proofs] invalidate_secrets = [p["secret"] for p in invalidated_proofs]
self.proofs = list( self.proofs = list(
filter(lambda p: p["secret"] not in invalidate_secrets, self.proofs) filter(lambda p: p["secret"] not in invalidate_secrets, self.proofs)

View File

@@ -21,7 +21,7 @@ With the data being of the form `PostMeltRequest`:
Proof, Proof,
... ...
], ],
"invoice": str "pr": str
} }
``` ```
@@ -43,7 +43,7 @@ curl -X POST https://mint.host:3338/mint&payment_hash=67d1d9ea6ada225c115418671b
... ...
} }
], ],
"invoice": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q..." "pr": "lnbc100n1p3kdrv5sp5lpdxzghe5j67q..."
} }
``` ```

View File

@@ -97,8 +97,8 @@ Note that the following steps can also be performed by `Alice` herself if she wa
## 5 - Burn sent tokens ## 5 - Burn sent tokens
Here we describe how `Alice` checks with the mint whether the tokens she sent `Carol` have been redeemed so she can safely delete them from her database. This step is optional but highly recommended so `Alice` can properly account for the tokens and adjust her balance accordingly. Here we describe how `Alice` checks with the mint whether the tokens she sent `Carol` have been redeemed so she can safely delete them from her database. This step is optional but highly recommended so `Alice` can properly account for the tokens and adjust her balance accordingly.
- `Alice` loads all `<send_proofs>` with `pending=True` from her database and might group them by the `send_id`. - `Alice` loads all `<send_proofs>` with `pending=True` from her database and might group them by the `send_id`.
- `Alice` constructs a JSON of the form `{"proofs" : [{"amount" : <amount>, "secret" : s, "C" : Z}, ...]}` from these (grouped) tokens. [*TODO: this object is called GetCheckSpendableRequest*] - `Alice` constructs a JSON of the form `{"proofs" : [{"amount" : <amount>, "secret" : s, "C" : Z}, ...]}` from these (grouped) tokens. [*TODO: this object is called CheckSpendableRequest*]
- `Alice` sends them to the mint `Bob` via the endpoint `POST /check` with the JSON as the body of the request. - `Alice` sends them to the mint `Bob` via the endpoint `GET /check` with the JSON as the body of the request.
- `Alice` receives a JSON of the form `{"1" : <spendable : bool>, "2" : ...}` where `"1"` is the index of the proof she sent to the mint before and `<spendable>` is a boolean that is `True` if the token has not been claimed yet by `Carol` and `False` if it has already been claimed. - `Alice` receives a JSON of the form `{"1" : <spendable : bool>, "2" : ...}` where `"1"` is the index of the proof she sent to the mint before and `<spendable>` is a boolean that is `True` if the token has not been claimed yet by `Carol` and `False` if it has already been claimed.
- If `<spendable>` is `False`, `Alice` removes the proof [*NOTE: consistent name?*] from her list of spendable proofs. - If `<spendable>` is `False`, `Alice` removes the proof [*NOTE: consistent name?*] from her list of spendable proofs.
@@ -109,7 +109,7 @@ Here we describe how `Alice` can request from `Bob` to make a Lightning payment
- `Alice` asks `Bob` for the Lightning fee via `GET /checkfee` with the body `CheckFeeRequest` being the json `{pr : <invoice>}` - `Alice` asks `Bob` for the Lightning fee via `GET /checkfee` with the body `CheckFeeRequest` being the json `{pr : <invoice>}`
- `Alice` receives the `CheckFeeResponse` in the form of the json `{"fee" : <fee>}` resulting in `<total> = <invoice_amount> + <fee>`. - `Alice` receives the `CheckFeeResponse` in the form of the json `{"fee" : <fee>}` resulting in `<total> = <invoice_amount> + <fee>`.
- `Alice` now performs the same set of instructions as in Step 3.1 and 3.2 and splits her spendable tokens into a set `<keep_proofs>` that she keeps and and a set `<send_proofs>` with a sum of at least `<total>` that she can send for making the Lightning payment. - `Alice` now performs the same set of instructions as in Step 3.1 and 3.2 and splits her spendable tokens into a set `<keep_proofs>` that she keeps and and a set `<send_proofs>` with a sum of at least `<total>` that she can send for making the Lightning payment.
- `Alice` constructs the JSON `PostMeltRequest` of the form `{"proofs" : <List[Proof]>, "invoice" : <invoice>}` [*NOTE: Maybe use notation List[Proof] everywhere. Used PostMeltRequest here, maybe define each payload at the beginning of each section.*] - `Alice` constructs the JSON `PostMeltRequest` of the form `{"proofs" : <List[Proof]>, "pr" : <invoice>}` [*NOTE: Maybe use notation List[Proof] everywhere. Used PostMeltRequest here, maybe define each payload at the beginning of each section.*]
- `Alice` requests a payment from `Bob` via the endpoint `POST /melt` with the JSON as the body of the request. - `Alice` requests a payment from `Bob` via the endpoint `POST /melt` with the JSON as the body of the request.
- `Alice` receives a JSON of the form `{"paid" : <status:bool>}` with `<status>` being `True` if the payment was successful and `False` otherwise. - `Alice` receives a JSON of the form `{"paid" : <status:bool>}` with `<status>` being `True` if the payment was successful and `False` otherwise.
- If `<status> == True`, `Alice` removes `<send_proofs>` from her database of spendable tokens [*NOTE: called it tokens again*] - If `<status> == True`, `Alice` removes `<send_proofs>` from her database of spendable tokens [*NOTE: called it tokens again*]

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "cashu" name = "cashu"
version = "0.8.4" version = "0.9.0"
description = "Ecash wallet and mint." description = "Ecash wallet and mint."
authors = ["calle <callebtc@protonmail.com>"] authors = ["calle <callebtc@protonmail.com>"]
license = "MIT" license = "MIT"

View File

@@ -13,7 +13,7 @@ entry_points = {"console_scripts": ["cashu = cashu.wallet.cli:cli"]}
setuptools.setup( setuptools.setup(
name="cashu", name="cashu",
version="0.8.4", version="0.9.0",
description="Ecash wallet and mint for Bitcoin Lightning", description="Ecash wallet and mint for Bitcoin Lightning",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",

View File

@@ -117,6 +117,7 @@ def test_receive_tokenv1(mint):
print(result.output) print(result.output)
@pytest.mark.skip
@pytest.mark.asyncio() @pytest.mark.asyncio()
def test_nostr_send(mint): def test_nostr_send(mint):
runner = CliRunner() runner = CliRunner()