From d4a89ac76f1f52ed91cc4d42b7c2d449b9538999 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:19:53 +0200 Subject: [PATCH 01/11] add tests --- cashu/mint/ledger.py | 12 +---- cashu/wallet/wallet.py | 114 ++++++++++++++++++++++------------------- tests/test_crypto.py | 37 +++++++++++++ 3 files changed, 99 insertions(+), 64 deletions(-) create mode 100644 tests/test_crypto.py diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 8f6a885..83b6b31 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -116,17 +116,7 @@ class Ledger: C = PublicKey(bytes.fromhex(proof.C), raw=True) - # backwards compatibility with old hash_to_curve - logger.debug(f"Client version {context.get('client-version')}") - if self.keysets.keysets.get(proof.id): - logger.debug( - f"Token keyset: {self.keysets.keysets.get(proof.id)}, token version: {self.keysets.keysets[proof.id].version}" - ) - # if not context.get("client-version") or ( - # self.keysets.keysets.get(proof.id) - # and not self.keysets.keysets[proof.id].version - # ): - # return legacy.verify_pre_0_3_3(secret_key, C, proof.secret) + # backwards compatibility with old hash_to_curve < 0.3.3 try: ret = legacy.verify_pre_0_3_3(secret_key, C, proof.secret) if ret: diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 8c95034..02ca6eb 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -50,28 +50,8 @@ class LedgerAPI: def __init__(self, url): self.url = url - - async def _get_keys(self, url): - resp = requests.get( - url + "/keys", - headers={"Client-version": VERSION}, - ).json() - keys = resp - 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(pubkeys=keyset_keys, mint_url=url) - return keyset - - async def _get_keysets(self, url): - keysets = requests.get( - url + "/keysets", - headers={"Client-version": VERSION}, - ).json() - assert len(keysets), Exception("did not receive any keysets") - return keysets + self.s = requests.Session() + self.s.headers.update({"Client-version": VERSION}) @staticmethod def _get_output_split(amount): @@ -100,6 +80,11 @@ class LedgerAPI: proofs.append(proof) return proofs + @staticmethod + def raise_on_error(resp_dict): + if "error" in resp_dict: + raise Exception("Mint Error: {}".format(resp_dict["error"])) + @staticmethod def _generate_secret(randombits=128): """Returns base64 encoded random string.""" @@ -138,12 +123,6 @@ class LedgerAPI: self.keys = keyset.public_keys self.keyset_id = keyset.id - def request_mint(self, amount): - """Requests a mint from the server and returns Lightning invoice.""" - r = requests.get(self.url + "/mint", params={"amount": amount}) - r.raise_for_status() - return r.json() - @staticmethod def _construct_outputs(amounts: List[int], secrets: List[str]): """Takes a list of amounts and secrets and returns outputs. @@ -173,25 +152,55 @@ class LedgerAPI: return [f"{secret}:{self._generate_secret()}" for i in range(n)] return [f"{i}:{secret}" for i in range(n)] + """ + ENDPOINTS + """ + + async def _get_keys(self, url): + resp = self.s.get( + url + "/keys", + ) + resp.raise_for_status() + 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(pubkeys=keyset_keys, mint_url=url) + return keyset + + async def _get_keysets(self, url): + resp = self.s.get( + url + "/keysets", + ).json() + resp.raise_for_status() + keysets = resp.json() + assert len(keysets), Exception("did not receive any keysets") + return keysets + + def request_mint(self, amount): + """Requests a mint from the server and returns Lightning invoice.""" + resp = self.s.get(self.url + "/mint", params={"amount": amount}) + resp.raise_for_status() + return_dict = resp.json() + self.raise_on_error(return_dict) + return return_dict + async def mint(self, amounts, payment_hash=None): """Mints new coins and returns a proof of promise.""" secrets = [self._generate_secret() for s in range(len(amounts))] await self._check_used_secrets(secrets) payloads, rs = self._construct_outputs(amounts, secrets) - resp = requests.post( + resp = self.s.post( self.url + "/mint", json=payloads.dict(), params={"payment_hash": payment_hash}, - headers={"Client-version": VERSION}, ) resp.raise_for_status() - try: - promises_list = resp.json() - except: - raise Exception("Unkown mint error.") - if "error" in promises_list: - raise Exception("Error: {}".format(promises_list["error"])) + promises_list = resp.json() + self.raise_on_error(promises_list) promises = [BlindedSignature.from_dict(p) for p in promises_list] return self._construct_proofs(promises, secrets, rs) @@ -239,18 +248,14 @@ class LedgerAPI: "proofs": {i: proofs_include for i in range(len(proofs))}, } - resp = requests.post( + resp = self.s.post( self.url + "/split", json=split_payload.dict(include=_splitrequest_include_fields(proofs)), - headers={"Client-version": VERSION}, ) resp.raise_for_status() - try: - promises_dict = resp.json() - except: - raise Exception("Unkown mint error.") - if "error" in promises_dict: - raise Exception("Mint Error: {}".format(promises_dict["error"])) + promises_dict = resp.json() + self.raise_on_error(promises_dict) + promises_fst = [BlindedSignature.from_dict(p) for p in promises_dict["fst"]] promises_snd = [BlindedSignature.from_dict(p) for p in promises_dict["snd"]] # Construct proofs from promises (i.e., unblind signatures) @@ -264,31 +269,35 @@ class LedgerAPI: return frst_proofs, scnd_proofs async def check_spendable(self, proofs: List[Proof]): + """ + Cheks whether the secrets in proofs are already spent or not and returns a list of booleans. + """ payload = CheckRequest(proofs=proofs) - resp = requests.post( + resp = self.s.post( self.url + "/check", json=payload.dict(), - headers={"Client-version": VERSION}, ) resp.raise_for_status() return_dict = resp.json() - + self.raise_on_error(return_dict) return return_dict async def check_fees(self, payment_request: str): """Checks whether the Lightning payment is internal.""" payload = CheckFeesRequest(pr=payment_request) - resp = requests.post( + resp = self.s.post( self.url + "/checkfees", json=payload.dict(), - headers={"Client-version": VERSION}, ) resp.raise_for_status() - return_dict = resp.json() + self.raise_on_error(return_dict) return return_dict async def pay_lightning(self, proofs: List[Proof], invoice: str): + """ + Accepts proofs and a lightning invoice to pay in exchange. + """ payload = MeltRequest(proofs=proofs, invoice=invoice) def _meltequest_include_fields(proofs): @@ -300,14 +309,13 @@ class LedgerAPI: "proofs": {i: proofs_include for i in range(len(proofs))}, } - resp = requests.post( + resp = self.s.post( self.url + "/melt", json=payload.dict(include=_meltequest_include_fields(proofs)), - headers={"Client-version": VERSION}, ) resp.raise_for_status() - return_dict = resp.json() + self.raise_on_error(return_dict) return return_dict diff --git a/tests/test_crypto.py b/tests/test_crypto.py new file mode 100644 index 0000000..947dc2a --- /dev/null +++ b/tests/test_crypto.py @@ -0,0 +1,37 @@ +import pytest + +from cashu.core.b_dhke import hash_to_curve + + +def test_hash_to_curve(): + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000000" + ) + ) + assert ( + result.serialize().hex() + == "0266687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925" + ) + + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000001" + ) + ) + assert ( + result.serialize().hex() + == "02ec4916dd28fc4c10d78e287ca5d9cc51ee1ae73cbfde08c6b37324cbfaac8bc5" + ) + + +def test_hash_to_curve_iteration(): + result = hash_to_curve( + bytes.fromhex( + "0000000000000000000000000000000000000000000000000000000000000002" + ) + ) + assert ( + result.serialize().hex() + == "02076c988b353fcbb748178ecb286bc9d0b4acf474d4ba31ba62334e46c97c416a" + ) From 6838b0be0527a2fb4d032f0063097dbf892e66d7 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:41:55 +0200 Subject: [PATCH 02/11] mypy --- cashu/core/base.py | 9 ++++----- cashu/core/crypto.py | 2 +- cashu/core/settings.py | 4 ++-- cashu/lightning/lnbits.py | 41 ++++++++++++++++++--------------------- cashu/mint/crud.py | 10 +++++----- cashu/mint/router.py | 10 ++++------ cashu/wallet/__init__.py | 2 +- cashu/wallet/crud.py | 12 ++++++------ tests/test_wallet.py | 8 +++----- 9 files changed, 45 insertions(+), 53 deletions(-) diff --git a/cashu/core/base.py b/cashu/core/base.py index 2c7a31d..99ec2a4 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -166,7 +166,6 @@ class CheckFeesResponse(BaseModel): class MeltRequest(BaseModel): proofs: List[Proof] - amount: int = None # deprecated invoice: str @@ -233,7 +232,7 @@ class MintKeyset: id: str derivation_path: str private_keys: Dict[int, PrivateKey] - public_keys: Dict[int, PublicKey] = None + public_keys: Dict[int, PublicKey] = {} valid_from: Union[str, None] = None valid_to: Union[str, None] = None first_seen: Union[str, None] = None @@ -247,9 +246,9 @@ class MintKeyset: valid_to=None, first_seen=None, active=None, - seed: Union[None, str] = None, - derivation_path: str = None, - version: str = None, + seed: str = "", + derivation_path: str = "", + version: str = "", ): self.derivation_path = derivation_path self.id = id diff --git a/cashu/core/crypto.py b/cashu/core/crypto.py index 6cad72b..1563d94 100644 --- a/cashu/core/crypto.py +++ b/cashu/core/crypto.py @@ -27,7 +27,7 @@ def derive_pubkeys(keys: Dict[int, PrivateKey]): return {amt: keys[amt].pubkey for amt in [2**i for i in range(MAX_ORDER)]} -def derive_keyset_id(keys: Dict[str, PublicKey]): +def derive_keyset_id(keys: Dict[int, PublicKey]): """Deterministic derivation keyset_id from set of public keys.""" pubkeys_concat = "".join([p.serialize().hex() for _, p in keys.items()]) return base64.b64encode( diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 30f2270..7c92601 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -7,13 +7,13 @@ from environs import Env # type: ignore env = Env() -ENV_FILE: Union[str, None] = os.path.join(str(Path.home()), ".cashu", ".env") +ENV_FILE = os.path.join(str(Path.home()), ".cashu", ".env") if not os.path.isfile(ENV_FILE): ENV_FILE = os.path.join(os.getcwd(), ".env") if os.path.isfile(ENV_FILE): env.read_env(ENV_FILE) else: - ENV_FILE = None + ENV_FILE = "" env.read_env() DEBUG = env.bool("DEBUG", default=False) diff --git a/cashu/lightning/lnbits.py b/cashu/lightning/lnbits.py index 97f8907..c422d10 100644 --- a/cashu/lightning/lnbits.py +++ b/cashu/lightning/lnbits.py @@ -1,8 +1,5 @@ -import asyncio -import hashlib -import json from os import getenv -from typing import AsyncGenerator, Dict, Optional +from typing import Dict, Optional import requests @@ -133,26 +130,26 @@ class LNbitsWallet(Wallet): return PaymentStatus(data["paid"], data["details"]["fee"], data["preimage"]) - async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: - url = f"{self.endpoint}/api/v1/payments/sse" + # async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + # url = f"{self.endpoint}/api/v1/payments/sse" - while True: - try: - async with requests.stream("GET", url) as r: - async for line in r.aiter_lines(): - if line.startswith("data:"): - try: - data = json.loads(line[5:]) - except json.decoder.JSONDecodeError: - continue + # while True: + # try: + # async with requests.stream("GET", url) as r: + # async for line in r.aiter_lines(): + # if line.startswith("data:"): + # try: + # data = json.loads(line[5:]) + # except json.decoder.JSONDecodeError: + # continue - if type(data) is not dict: - continue + # if type(data) is not dict: + # continue - yield data["payment_hash"] # payment_hash + # yield data["payment_hash"] # payment_hash - except: - pass + # except: + # pass - print("lost connection to lnbits /payments/sse, retrying in 5 seconds") - await asyncio.sleep(5) + # print("lost connection to lnbits /payments/sse, retrying in 5 seconds") + # await asyncio.sleep(5) diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index fbb3bf3..3d4c99d 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -1,4 +1,4 @@ -from typing import Optional +from typing import Optional, List, Any from cashu.core.base import Invoice, MintKeyset, Proof from cashu.core.db import Connection, Database @@ -118,7 +118,7 @@ async def store_keyset( conn: Optional[Connection] = None, ): - await (conn or db).execute( + await (conn or db).execute( # type: ignore """ INSERT INTO keysets (id, derivation_path, valid_from, valid_to, first_seen, active, version) @@ -138,12 +138,12 @@ async def store_keyset( async def get_keyset( id: str = None, - derivation_path: str = None, + derivation_path: str = "", db: Database = None, conn: Optional[Connection] = None, ): clauses = [] - values = [] + values: List[Any] = [] clauses.append("active = ?") values.append(True) if id: @@ -156,7 +156,7 @@ async def get_keyset( if clauses: where = f"WHERE {' AND '.join(clauses)}" - rows = await (conn or db).fetchall( + rows = await (conn or db).fetchall( # type: ignore f""" SELECT * from keysets {where} diff --git a/cashu/mint/router.py b/cashu/mint/router.py index db64ea0..084d1f0 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -20,10 +20,6 @@ from cashu.mint import ledger router: APIRouter = APIRouter() -from starlette.requests import Request -from starlette_context import context - - @router.get("/keys") def keys(): """Get the public keys of the mint""" @@ -73,7 +69,7 @@ async def mint( @router.post("/melt") -async def melt(request: Request, payload: MeltRequest): +async def melt(payload: MeltRequest): """ Requests tokens to be destroyed and sent out via Lightning. """ @@ -100,7 +96,7 @@ async def check_fees(payload: CheckFeesRequest): @router.post("/split") -async def split(request: Request, payload: SplitRequest): +async def split(payload: SplitRequest): """ Requetst a set of tokens with amount "total" to be split into two newly minted sets with amount "split" and "total-split". @@ -108,6 +104,8 @@ async def split(request: Request, payload: SplitRequest): proofs = payload.proofs amount = payload.amount outputs = payload.outputs.blinded_messages if payload.outputs else None + # backwards compatibility with clients < v0.2.2 + assert outputs, Exception("no outputs provided.") try: split_return = await ledger.split(proofs, amount, outputs) except Exception as exc: diff --git a/cashu/wallet/__init__.py b/cashu/wallet/__init__.py index bf11b51..b4cdb6a 100644 --- a/cashu/wallet/__init__.py +++ b/cashu/wallet/__init__.py @@ -1,3 +1,3 @@ import sys -sys.tracebacklimit = None +sys.tracebacklimit = None # type: ignore diff --git a/cashu/wallet/crud.py b/cashu/wallet/crud.py index 12d8401..d6f8c47 100644 --- a/cashu/wallet/crud.py +++ b/cashu/wallet/crud.py @@ -93,7 +93,7 @@ async def update_proof_reserved( clauses.append("time_reserved = ?") values.append(int(time.time())) - await (conn or db).execute( + await (conn or db).execute( # type: ignore f"UPDATE proofs SET {', '.join(clauses)} WHERE secret = ?", (*values, str(proof.secret)), ) @@ -155,7 +155,7 @@ async def get_unused_locks( if clause: where = f"WHERE {' AND '.join(clause)}" - rows = await (conn or db).fetchall( + rows = await (conn or db).fetchall( # type: ignore f""" SELECT * from p2sh {where} @@ -176,7 +176,7 @@ async def update_p2sh_used( clauses.append("used = ?") values.append(used) - await (conn or db).execute( + await (conn or db).execute( # type: ignore f"UPDATE proofs SET {', '.join(clauses)} WHERE address = ?", (*values, str(p2sh.address)), ) @@ -189,7 +189,7 @@ async def store_keyset( conn: Optional[Connection] = None, ): - await (conn or db).execute( + await (conn or db).execute( # type: ignore """ INSERT INTO keysets (id, mint_url, valid_from, valid_to, first_seen, active) @@ -213,7 +213,7 @@ async def get_keyset( conn: Optional[Connection] = None, ): clauses = [] - values = [] + values: List[Any] = [] clauses.append("active = ?") values.append(True) if id: @@ -226,7 +226,7 @@ async def get_keyset( if clauses: where = f"WHERE {' AND '.join(clauses)}" - row = await (conn or db).fetchone( + row = await (conn or db).fetchone( # type: ignore f""" SELECT * from keysets {where} diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 300896b..74ac8ff 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1,11 +1,9 @@ import time -from distutils.command.build_scripts import first_line_re -from re import S -from typing import List import pytest import pytest_asyncio +from typing import List from cashu.core.base import Proof from cashu.core.helpers import async_unwrap, sum_proofs from cashu.core.migrations import migrate_databases @@ -27,9 +25,9 @@ async def assert_err(f, msg): ) -def assert_amt(proofs, expected): +def assert_amt(proofs: List[Proof], expected: int): """Assert amounts the proofs contain.""" - assert [p["amount"] for p in proofs] == expected + assert [p.amount for p in proofs] == expected @pytest_asyncio.fixture(scope="function") From 74df219d6dbedb6798463d6ada6c392532f62f67 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:48:05 +0200 Subject: [PATCH 03/11] mypy --- .github/workflows/formatting.yml | 22 +++++++++++++++++++++- cashu/lightning/base.py | 6 +++--- cashu/mint/router.py | 23 +++++++++++++---------- 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml index b913e54..0449e96 100644 --- a/.github/workflows/formatting.yml +++ b/.github/workflows/formatting.yml @@ -7,7 +7,7 @@ on: branches: [main] jobs: - poetry: + format: runs-on: ubuntu-latest strategy: matrix: @@ -29,3 +29,23 @@ jobs: run: poetry run black --check . - name: Check isort run: poetry run isort --profile black --check-only . + mypy: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9"] + poetry-version: ["1.2.1"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install packages + run: poetry install --with dev + - name: Mypy + run: poetry run mypy cashu --ignore-missing diff --git a/cashu/lightning/base.py b/cashu/lightning/base.py index e38b6d8..adde18b 100644 --- a/cashu/lightning/base.py +++ b/cashu/lightning/base.py @@ -79,9 +79,9 @@ class Wallet(ABC): ) -> Coroutine[None, None, PaymentStatus]: pass - @abstractmethod - def paid_invoices_stream(self) -> AsyncGenerator[str, None]: - pass + # @abstractmethod + # def paid_invoices_stream(self) -> AsyncGenerator[str, None]: + # pass class Unsupported(Exception): diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 084d1f0..1ade005 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -1,4 +1,4 @@ -from typing import Union +from typing import Union, List, Dict from fastapi import APIRouter from secp256k1 import PublicKey @@ -13,6 +13,7 @@ from cashu.core.base import ( MintRequest, PostSplitResponse, SplitRequest, + BlindedSignature, ) from cashu.core.errors import CashuError from cashu.mint import ledger @@ -21,19 +22,19 @@ router: APIRouter = APIRouter() @router.get("/keys") -def keys(): +def keys() -> dict[int, str]: """Get the public keys of the mint""" return ledger.get_keyset() @router.get("/keysets") -def keysets(): +def keysets() -> dict[str, list[str]]: """Get all active keysets of the mint""" return {"keysets": ledger.keysets.get_ids()} @router.get("/mint") -async def request_mint(amount: int = 0): +async def request_mint(amount: int = 0) -> GetMintResponse: """ Request minting of new tokens. The mint responds with a Lightning invoice. This endpoint can be used for a Lightning invoice UX flow. @@ -50,7 +51,7 @@ async def request_mint(amount: int = 0): async def mint( payloads: MintRequest, payment_hash: Union[str, None] = None, -): +) -> Union[List[BlindedSignature], CashuError]: """ Requests the minting of tokens belonging to a paid payment request. @@ -69,7 +70,7 @@ async def mint( @router.post("/melt") -async def melt(payload: MeltRequest): +async def melt(payload: MeltRequest) -> GetMeltResponse: """ Requests tokens to be destroyed and sent out via Lightning. """ @@ -79,13 +80,13 @@ async def melt(payload: MeltRequest): @router.post("/check") -async def check_spendable(payload: CheckRequest): +async def check_spendable(payload: CheckRequest) -> Dict[int, bool]: """Check whether a secret has been spent already or not.""" return await ledger.check_spendable(payload.proofs) @router.post("/checkfees") -async def check_fees(payload: CheckFeesRequest): +async def check_fees(payload: CheckFeesRequest) -> CheckFeesResponse: """ Responds with the fees necessary to pay a Lightning invoice. Used by wallets for figuring out the fees they need to supply. @@ -96,7 +97,9 @@ async def check_fees(payload: CheckFeesRequest): @router.post("/split") -async def split(payload: SplitRequest): +async def split( + payload: SplitRequest, +) -> Union[CashuError, PostSplitResponse]: """ Requetst a set of tokens with amount "total" to be split into two newly minted sets with amount "split" and "total-split". @@ -111,7 +114,7 @@ async def split(payload: SplitRequest): except Exception as exc: return CashuError(error=str(exc)) if not split_return: - return {"error": "there was a problem with the split."} + return CashuError(error="there was an error with the split") frst_promises, scnd_promises = split_return resp = PostSplitResponse(fst=frst_promises, snd=scnd_promises) return resp From d4df155a39c2cd7a9eb0ea06652c5c09600aaa84 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:49:05 +0200 Subject: [PATCH 04/11] rename tests --- .github/workflows/formatting.yml | 51 -------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 .github/workflows/formatting.yml diff --git a/.github/workflows/formatting.yml b/.github/workflows/formatting.yml deleted file mode 100644 index 0449e96..0000000 --- a/.github/workflows/formatting.yml +++ /dev/null @@ -1,51 +0,0 @@ -name: formatting - -on: - push: - branches: [main] - pull_request: - branches: [main] - -jobs: - format: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.9"] - poetry-version: ["1.2.1"] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Set up Poetry ${{ matrix.poetry-version }} - uses: abatilo/actions-poetry@v2 - with: - poetry-version: ${{ matrix.poetry-version }} - - name: Install packages - run: poetry install --with dev - - name: Check black - run: poetry run black --check . - - name: Check isort - run: poetry run isort --profile black --check-only . - mypy: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.9"] - poetry-version: ["1.2.1"] - steps: - - uses: actions/checkout@v2 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - name: Set up Poetry ${{ matrix.poetry-version }} - uses: abatilo/actions-poetry@v2 - with: - poetry-version: ${{ matrix.poetry-version }} - - name: Install packages - run: poetry install --with dev - - name: Mypy - run: poetry run mypy cashu --ignore-missing From 7eb8ec8ccc85f950e7230e5a8a9145aa198f5ad5 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:49:27 +0200 Subject: [PATCH 05/11] make format --- cashu/mint/crud.py | 2 +- cashu/mint/router.py | 4 ++-- tests/test_wallet.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/cashu/mint/crud.py b/cashu/mint/crud.py index 3d4c99d..0e6cd38 100644 --- a/cashu/mint/crud.py +++ b/cashu/mint/crud.py @@ -1,4 +1,4 @@ -from typing import Optional, List, Any +from typing import Any, List, Optional from cashu.core.base import Invoice, MintKeyset, Proof from cashu.core.db import Connection, Database diff --git a/cashu/mint/router.py b/cashu/mint/router.py index 1ade005..2845850 100644 --- a/cashu/mint/router.py +++ b/cashu/mint/router.py @@ -1,9 +1,10 @@ -from typing import Union, List, Dict +from typing import Dict, List, Union from fastapi import APIRouter from secp256k1 import PublicKey from cashu.core.base import ( + BlindedSignature, CheckFeesRequest, CheckFeesResponse, CheckRequest, @@ -13,7 +14,6 @@ from cashu.core.base import ( MintRequest, PostSplitResponse, SplitRequest, - BlindedSignature, ) from cashu.core.errors import CashuError from cashu.mint import ledger diff --git a/tests/test_wallet.py b/tests/test_wallet.py index 74ac8ff..087ca9f 100644 --- a/tests/test_wallet.py +++ b/tests/test_wallet.py @@ -1,9 +1,9 @@ import time +from typing import List import pytest import pytest_asyncio -from typing import List from cashu.core.base import Proof from cashu.core.helpers import async_unwrap, sum_proofs from cashu.core.migrations import migrate_databases From f22fb5f843cfb7326939470f4f183aead1e83431 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:51:02 +0200 Subject: [PATCH 06/11] setup mypy --- .github/workflows/checks.yml | 53 ++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 .github/workflows/checks.yml diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml new file mode 100644 index 0000000..4f2c649 --- /dev/null +++ b/.github/workflows/checks.yml @@ -0,0 +1,53 @@ +name: formatting + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + formatting: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9"] + poetry-version: ["1.2.1"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install packages + run: poetry install --with dev + - name: Check black + run: poetry run black --check . + - name: Check isort + run: poetry run isort --profile black --check-only . + linting: + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.9"] + poetry-version: ["1.2.1"] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Set up Poetry ${{ matrix.poetry-version }} + uses: abatilo/actions-poetry@v2 + with: + poetry-version: ${{ matrix.poetry-version }} + - name: Install packages + run: poetry install --with dev + - name: Setup mypy + run: poetry run mypy --install-types + - name: Run mypy + run: poetry run mypy cashu --ignore-missing From a0bd7ae06e554a659703e1cbf35009d6016e4ccf Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:52:17 +0200 Subject: [PATCH 07/11] mypyyy --- .github/workflows/checks.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 4f2c649..04746ec 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -1,4 +1,4 @@ -name: formatting +name: checks on: push: @@ -48,6 +48,6 @@ jobs: - name: Install packages run: poetry install --with dev - name: Setup mypy - run: poetry run mypy --install-types + run: poetry run mypy cashu --install-types - name: Run mypy run: poetry run mypy cashu --ignore-missing From 993a9ae706a00eec5e72bf6e3bedcb9d9f00d6a3 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:54:30 +0200 Subject: [PATCH 08/11] test again --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 04746ec..907ee40 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -48,6 +48,6 @@ jobs: - name: Install packages run: poetry install --with dev - name: Setup mypy - run: poetry run mypy cashu --install-types + run: poetry run mypy cashu --install-types -y - name: Run mypy run: poetry run mypy cashu --ignore-missing From ca14de533ce3ede092fe94c759b0e3b376aee80a Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 00:56:56 +0200 Subject: [PATCH 09/11] myppyyyyyy --- .github/workflows/checks.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index 907ee40..ce9e3bb 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -48,6 +48,6 @@ jobs: - name: Install packages run: poetry install --with dev - name: Setup mypy - run: poetry run mypy cashu --install-types -y + run: yes | poetry run mypy cashu --install-types || true - name: Run mypy run: poetry run mypy cashu --ignore-missing From eeff943e248c7f85e54916c217c9c8b4c13af257 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 01:01:15 +0200 Subject: [PATCH 10/11] codecov --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f32ad3d..5f0768a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -37,3 +37,5 @@ jobs: MINT_PORT: 3338 run: | poetry run pytest tests + - name: Upload coverage to Codecov + uses: codecov/codecov-action@v3 From b77370417760cb75d9249b90fd431f7a969ec932 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Tue, 11 Oct 2022 01:14:27 +0200 Subject: [PATCH 11/11] yes question for cashu pay --- cashu/wallet/cli.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/cashu/wallet/cli.py b/cashu/wallet/cli.py index dae84e0..d4bdfc1 100755 --- a/cashu/wallet/cli.py +++ b/cashu/wallet/cli.py @@ -121,21 +121,31 @@ async def mint(ctx, amount: int, hash: str): @cli.command("pay", help="Pay Lightning invoice.") @click.argument("invoice", type=str) +@click.option( + "--yes", "-y", default=False, is_flag=True, help="Skip confirmation.", type=bool +) @click.pass_context @coro -async def pay(ctx, invoice: str): +async def pay(ctx, invoice: str, yes: bool): wallet: Wallet = ctx.obj["WALLET"] await wallet.load_mint() wallet.status() decoded_invoice: Invoice = bolt11.decode(invoice) + # check if it's an internal payment fees = (await wallet.check_fees(invoice))["fee"] amount = math.ceil( (decoded_invoice.amount_msat + fees * 1000) / 1000 ) # 1% fee for Lightning - print( - f"Paying Lightning invoice of {decoded_invoice.amount_msat//1000} sat ({amount} sat incl. fees)" - ) + + if not yes: + click.confirm( + f"Pay {decoded_invoice.amount_msat//1000} sat ({amount} sat incl. fees)?", + abort=True, + default=True, + ) + + print(f"Paying Lightning invoice ...") assert amount > 0, "amount is not positive" if wallet.available_balance < amount: print("Error: Balance too low.")