mirror of
https://github.com/aljazceru/nutshell.git
synced 2025-12-20 18:44:20 +01:00
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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -66,4 +66,4 @@ NOSTR_RELAYS = env.list(
|
|||||||
)
|
)
|
||||||
|
|
||||||
MAX_ORDER = 64
|
MAX_ORDER = 64
|
||||||
VERSION = "0.8.4"
|
VERSION = "0.9.0"
|
||||||
|
|||||||
@@ -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."""
|
||||||
|
|||||||
@@ -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.
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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..."
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -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*]
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
2
setup.py
2
setup.py
@@ -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",
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
Reference in New Issue
Block a user