From db27105d1739bfcf2062ad6d0040949780468f53 Mon Sep 17 00:00:00 2001 From: calle <93376500+callebtc@users.noreply.github.com> Date: Tue, 28 Mar 2023 22:35:22 +0200 Subject: [PATCH] TokenV3 and new Mint startup in tests (#149) * tokenv3 send and receive * receive v2 and v1 tokens with tests --- .github/workflows/tests.yml | 8 -- cashu/core/base.py | 26 ++++++ cashu/mint/startup.py | 2 +- cashu/wallet/cli/cli.py | 97 ++++++++++----------- cashu/wallet/cli/cli_helpers.py | 131 +++++++++++++++++++---------- cashu/wallet/wallet.py | 138 +++++++++++++++++++++++------- data/mint/.placeholder | 0 data/wallet/.placeholder | 0 tests/conftest.py | 24 +++++- tests/test_cli.py | 144 ++++++++++++++++++++++++-------- 10 files changed, 402 insertions(+), 168 deletions(-) delete mode 100644 data/mint/.placeholder delete mode 100644 data/wallet/.placeholder diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 2ea696c..5f411d9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,14 +27,6 @@ jobs: run: | poetry install --with dev shell: bash - - name: Run mint - env: - LIGHTNING: False - MINT_PRIVATE_KEY: "testingkey" - MINT_LISTEN_HOST: 0.0.0.0 - MINT_LISTEN_PORT: 3337 - run: | - nohup poetry run mint & - name: Run tests env: LIGHTNING: False diff --git a/cashu/core/base.py b/cashu/core/base.py index a6237a9..8504826 100644 --- a/cashu/core/base.py +++ b/cashu/core/base.py @@ -331,3 +331,29 @@ class TokenV2(BaseModel): ) else: return dict(proofs=[p.to_dict() for p in self.proofs]) + + +class TokenV3Token(BaseModel): + mint: Optional[str] = None + proofs: List[Proof] + + def to_dict(self): + return_dict = dict(proofs=[p.to_dict() for p in self.proofs]) + if self.mint: + return_dict.update(dict(mint=self.mint)) # type: ignore + return return_dict + + +class TokenV3(BaseModel): + """ + A Cashu token that includes proofs and their respective mints. Can include proofs from multiple different mints and keysets. + """ + + token: List[TokenV3Token] = [] + memo: Optional[str] = None + + def to_dict(self): + return_dict = dict(token=[t.to_dict() for t in self.token]) + if self.memo: + return_dict.update(dict(memo=self.memo)) # type: ignore + return return_dict diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index 632a305..2234616 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -23,7 +23,7 @@ lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)() ledger = Ledger( db=Database("mint", settings.mint_database), seed=settings.mint_private_key, - derivation_path="0/0/0/0", + derivation_path="0/0/0/1", lightning=lightning_backend, ) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index eada9ad..907db59 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -18,7 +18,7 @@ import click from click import Context from loguru import logger -from cashu.core.base import Proof, TokenV2 +from cashu.core.base import Proof, TokenV1, TokenV2 from cashu.core.helpers import sum_proofs from cashu.core.migrations import migrate_databases from cashu.core.settings import settings @@ -26,6 +26,7 @@ from cashu.nostr.nostr.client.client import NostrClient from cashu.tor.tor import TorProxy from cashu.wallet import migrations from cashu.wallet.crud import ( + get_keyset, get_lightning_invoices, get_reserved_proofs, get_unused_locks, @@ -35,10 +36,9 @@ from cashu.wallet.wallet import Wallet as Wallet from .cli_helpers import ( get_mint_wallet, print_mint_balances, - proofs_to_serialized_tokenv2, - redeem_multimint, - token_from_lnbits_link, - verify_mints, + redeem_TokenV3_multimint, + serialize_TokenV1_to_TokenV3, + serialize_TokenV2_to_TokenV3, ) from .nostr import receive_nostr, send_nostr @@ -243,9 +243,7 @@ async def send(ctx: Context, amount: int, lock: str, legacy: bool): if legacy: print("") - print( - "Legacy token without mint information for older clients. This token can only be be received by wallets who use the mint the token is issued from:" - ) + print("Old token format:") print("") token = await wallet.serialize_proofs( send_proofs, @@ -306,7 +304,7 @@ async def send_command( async def receive(ctx: Context, token: str, lock: str): wallet: Wallet = ctx.obj["WALLET"] - await wallet.load_mint() + # await wallet.load_mint() # check for P2SH locks if lock: @@ -325,60 +323,63 @@ async def receive(ctx: Context, token: str, lock: str): # ----- backwards compatibility ----- - # we support old tokens (< 0.7) without mint information and (W3siaWQ...) - # new tokens (>= 0.7) with multiple mint support (eyJ0b2...) - try: - # backwards compatibility: tokens without mint information - # supports tokens of the form W3siaWQiOiJH - - # if it's an lnbits https:// link with a token as an argument, speacial treatment - token, url = token_from_lnbits_link(token) - - # assume W3siaWQiOiJH.. token - # next line trows an error if the desirialization with the old format doesn't - # work and we can assume it's the new format - proofs = [Proof(**p) for p in json.loads(base64.urlsafe_b64decode(token))] - - # we take the proofs parsed from the old format token and produce a new format token with it - token = await proofs_to_serialized_tokenv2(wallet, proofs, url) - except: - pass + # V2Tokens (0.7-0.11.0) (eyJwcm9...) + if token.startswith("eyJwcm9"): + try: + tokenv2 = TokenV2.parse_obj(json.loads(base64.urlsafe_b64decode(token))) + token = await serialize_TokenV2_to_TokenV3(wallet, tokenv2) + except: + pass + # V1Tokens (<0.7) (W3siaWQ...) + if token.startswith("W3siaWQ"): + try: + tokenv1 = TokenV1.parse_obj(json.loads(base64.urlsafe_b64decode(token))) + token = await serialize_TokenV1_to_TokenV3(wallet, tokenv1) + print(token) + except: + pass # ----- receive token ----- # deserialize token - dtoken = json.loads(base64.urlsafe_b64decode(token)) + # dtoken = json.loads(base64.urlsafe_b64decode(token)) + tokenObj = wallet._deserialize_token_V3(token) - # backwards compatibility wallet to wallet < 0.8.0: V2 tokens renamed "tokens" field to "proofs" - if "tokens" in dtoken: - dtoken["proofs"] = dtoken.pop("tokens") - - # backwards compatibility wallet to wallet < 0.8.3: V2 tokens got rid of the "MINT_NAME" key in "mints" and renamed "ks" to "ids" - if "mints" in dtoken and isinstance(dtoken["mints"], dict): - dtoken["mints"] = list(dtoken["mints"].values()) - for m in dtoken["mints"]: - m["ids"] = m.pop("ks") - - tokenObj = TokenV2.parse_obj(dtoken) - assert len(tokenObj.proofs), Exception("no proofs in token") - includes_mint_info: bool = tokenObj.mints is not None and len(tokenObj.mints) > 0 + # tokenObj = TokenV2.parse_obj(dtoken) + assert len(tokenObj.token), Exception("no proofs in token") + assert len(tokenObj.token[0].proofs), Exception("no proofs in token") + includes_mint_info: bool = any([t.mint for t in tokenObj.token]) # if there is a `mints` field in the token # we check whether the token has mints that we don't know yet # and ask the user if they want to trust the new mitns if includes_mint_info: # we ask the user to confirm any new mints the tokens may include - await verify_mints(ctx, tokenObj) + # await verify_mints(ctx, tokenObj) # redeem tokens with new wallet instances - await redeem_multimint(ctx, tokenObj, script, signature) - # reload main wallet so the balance updates - await wallet.load_proofs() + await redeem_TokenV3_multimint(ctx, tokenObj, script, signature) else: # no mint information present, we extract the proofs and use wallet's default mint - proofs = [Proof(**p) for p in dtoken["proofs"]] - _, _ = await wallet.redeem(proofs, script, signature) + + proofs = [p for t in tokenObj.token for p in t.proofs] + # first we load the mint URL from the DB + keyset_in_token = proofs[0].id + assert keyset_in_token + # we get the keyset from the db + mint_keysets = await get_keyset(id=keyset_in_token, db=wallet.db) + assert mint_keysets, Exception("we don't know this keyset") + assert mint_keysets.mint_url, Exception("we don't know this mint's URL") + # now we have the URL + mint_wallet = Wallet( + mint_keysets.mint_url, + os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]), + ) + await mint_wallet.load_mint(keyset_in_token) + _, _ = await mint_wallet.redeem(proofs, script, signature) print(f"Received {sum_proofs(proofs)} sats") + # reload main wallet so the balance updates + await wallet.load_proofs() wallet.status() @@ -413,7 +414,7 @@ async def receive_cli( elif all: reserved_proofs = await get_reserved_proofs(wallet.db) if len(reserved_proofs): - for (key, value) in groupby(reserved_proofs, key=itemgetter("send_id")): + for (key, value) in groupby(reserved_proofs, key=itemgetter("send_id")): # type: ignore proofs = list(value) token = await wallet.serialize_proofs(proofs) await receive(ctx, token, lock) diff --git a/cashu/wallet/cli/cli_helpers.py b/cashu/wallet/cli/cli_helpers.py index 5633056..2895273 100644 --- a/cashu/wallet/cli/cli_helpers.py +++ b/cashu/wallet/cli/cli_helpers.py @@ -6,14 +6,49 @@ import click from click import Context from loguru import logger -from cashu.core.base import Proof, TokenV2, TokenV2Mint, WalletKeyset +from cashu.core.base import ( + Proof, + TokenV1, + TokenV2, + TokenV2Mint, + TokenV3, + TokenV3Token, + WalletKeyset, +) from cashu.core.helpers import sum_proofs from cashu.core.settings import settings from cashu.wallet.crud import get_keyset from cashu.wallet.wallet import Wallet as Wallet -async def verify_mints(ctx: Context, token: TokenV2): +async def verify_mint(mint_wallet: Wallet, url: str): + """A helper function that asks the user if they trust the mint if the user + has not encountered the mint before (there is no entry in the database). + + Throws an Exception if the user chooses to not trust the mint. + """ + logger.debug(f"Verifying mint {url}") + # dummy Wallet to check the database later + # mint_wallet = Wallet(url, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"])) + # we check the db whether we know this mint already and ask the user if not + mint_keysets = await get_keyset(mint_url=url, db=mint_wallet.db) + if mint_keysets is None: + # we encountered a new mint and ask for a user confirmation + print("") + print("Warning: Tokens are from a mint you don't know yet.") + print("\n") + print(f"Mint URL: {url}") + print("\n") + click.confirm( + f"Do you trust this mint and want to receive the tokens?", + abort=True, + default=True, + ) + else: + logger.debug(f"We know keyset {mint_keysets.id} already") + + +async def verify_mints_tokenv2(ctx: Context, token: TokenV2): """ A helper function that iterates through all mints in the token and if it has not been encountered before, asks the user to confirm. @@ -64,7 +99,7 @@ async def verify_mints(ctx: Context, token: TokenV2): assert trust_token_mints, Exception("Aborted!") -async def redeem_multimint(ctx: Context, token: TokenV2, script, signature): +async def redeem_TokenV2_multimint(ctx: Context, token: TokenV2, script, signature): """ Helper function to iterate thruogh a token with multiple mints and redeem them from these mints one keyset at a time. @@ -95,6 +130,30 @@ async def redeem_multimint(ctx: Context, token: TokenV2, script, signature): print(f"Received {sum_proofs(redeem_proofs)} sats") +async def redeem_TokenV3_multimint(ctx: Context, token: TokenV3, script, signature): + """ + Helper function to iterate thruogh a token with multiple mints and redeem them from + these mints one keyset at a time. + """ + for t in token.token: + assert t.mint, Exception("Multimint redeem without URL") + mint_wallet = Wallet( + t.mint, os.path.join(settings.cashu_dir, ctx.obj["WALLET_NAME"]) + ) + await verify_mint(mint_wallet, t.mint) + keysets = mint_wallet._get_proofs_keysets(t.proofs) + # logger.debug(f"Keysets in tokens: {keysets}") + # loop over all keysets + for keyset in set(keysets): + await mint_wallet.load_mint(keyset_id=keyset) + # redeem proofs of this keyset + redeem_proofs = [p for p in t.proofs if p.id == keyset] + _, _ = await mint_wallet.redeem( + redeem_proofs, scnd_script=script, scnd_siganture=signature + ) + print(f"Received {sum_proofs(redeem_proofs)} sats") + + async def print_mint_balances(ctx: Context, wallet, show_mints=False): """ Helper function that prints the balances for each mint URL that we have tokens from. @@ -165,49 +224,29 @@ async def get_mint_wallet(ctx: Context): return mint_wallet -# LNbits token link parsing -# can extract minut URL from LNbits token links like: -# https://lnbits.server/cashu/wallet?mint_id=aMintId&recv_token=W3siaWQiOiJHY2... -def token_from_lnbits_link(link): - url, token = "", "" - if len(link.split("&recv_token=")) == 2: - # extract URL params - params = urllib.parse.parse_qs(link.split("?")[1]) - # extract URL - if "mint_id" in params: - url = ( - link.split("?")[0].split("/wallet")[0] - + "/api/v1/" - + params["mint_id"][0] - ) - # extract token - token = params["recv_token"][0] - return token, url - else: - return link, "" +async def serialize_TokenV2_to_TokenV3(wallet: Wallet, tokenv2: TokenV2): + """Helper function for the CLI to receive legacy TokenV2 tokens. + Takes a list of proofs and constructs a *serialized* TokenV3 to be received through + the ordinary path. - -async def proofs_to_serialized_tokenv2(wallet, proofs: List[Proof], url: str): + Returns: + TokenV3: TokenV3 """ - Ingests list of proofs and produces a serialized TokenV2 - """ - # and add url and keyset id to token - token: TokenV2 = await wallet._make_token(proofs, include_mints=False) - token.mints = [] - - # get keysets of proofs - keysets = list(set([p.id for p in proofs if p.id is not None])) - - # check whether we know the mint urls for these proofs - for k in keysets: - ks = await get_keyset(id=k, db=wallet.db) - url = ks.mint_url if ks and ks.mint_url else "" - - url = url or ( - input(f"Enter mint URL (press enter for default {settings.mint_url}): ") - or settings.mint_url - ) - - token.mints.append(TokenV2Mint(url=url, ids=keysets)) - token_serialized = await wallet._serialize_token_base64(token) + tokenv3 = TokenV3(token=[TokenV3Token(proofs=tokenv2.proofs)]) + if tokenv2.mints: + tokenv3.token[0].mint = tokenv2.mints[0].url + token_serialized = await wallet._serialize_token_V3(tokenv3) + return token_serialized + + +async def serialize_TokenV1_to_TokenV3(wallet: Wallet, tokenv1: TokenV1): + """Helper function for the CLI to receive legacy TokenV1 tokens. + Takes a list of proofs and constructs a *serialized* TokenV3 to be received through + the ordinary path. + + Returns: + TokenV3: TokenV3 + """ + tokenv3 = TokenV3(token=[TokenV3Token(proofs=tokenv1.__root__)]) + token_serialized = await wallet._serialize_token_V3(tokenv3) return token_serialized diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index f6e27d2..7915796 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -31,6 +31,8 @@ from cashu.core.base import ( Proof, TokenV2, TokenV2Mint, + TokenV3, + TokenV3Token, WalletKeyset, ) from cashu.core.bolt11 import Invoice as InvoiceBolt11 @@ -630,7 +632,105 @@ class Wallet(LedgerAPI): ret[keyset.mint_url].extend([p for p in proofs if p.id == id]) return ret + def _get_proofs_keysets(self, proofs: List[Proof]): + """Extracts all keyset ids from a list of proofs. + + Args: + proofs (List[Proof]): List of proofs to get the keyset id's of + """ + keysets: List[str] = [proof.id for proof in proofs if proof.id] + return keysets + + async def _get_keyset_urls(self, keysets: List[str]): + """Retrieves the mint URLs for a list of keyset id's from the wallet's database. + Returns a dictionary from URL to keyset ID + + Args: + keysets (List[str]): List of keysets. + """ + mint_urls: Dict[str, List[str]] = {} + for ks in set(keysets): + keyset_db = await get_keyset(id=ks, db=self.db) + if keyset_db and keyset_db.mint_url: + mint_urls[keyset_db.mint_url] = ( + mint_urls[keyset_db.mint_url] + [ks] + if mint_urls.get(keyset_db.mint_url) + else [ks] + ) + return mint_urls + async def _make_token(self, proofs: List[Proof], include_mints=True): + """ + Takes list of proofs and produces a TokenV3 by looking up + the mint URLs by the keyset id from the database. + """ + token = TokenV3() + + if include_mints: + # we create a map from mint url to keyset id and then group + # all proofs with their mint url to build a tokenv3 + + # extract all keysets from proofs + keysets = self._get_proofs_keysets(proofs) + # get all mint URLs for all unique keysets from db + mint_urls = await self._get_keyset_urls(keysets) + + # append all url-grouped proofs to token + for url, ids in mint_urls.items(): + mint_proofs = [p for p in proofs if p.id in ids] + token.token.append(TokenV3Token(mint=url, proofs=mint_proofs)) + else: + token_proofs = TokenV3Token(proofs=proofs) + token.token.append(token_proofs) + return token + + async def _serialize_token_V3(self, token: TokenV3): + """ + Takes a TokenV3 and serializes it as "cashuA. + """ + prefix = "cashuA" + tokenv3_serialized = prefix + # encode the token as a base64 string + tokenv3_serialized += base64.urlsafe_b64encode( + json.dumps(token.to_dict()).encode() + ).decode() + return tokenv3_serialized + + def _deserialize_token_V3(self, tokenv3_serialized: str) -> TokenV3: + """ + Takes a TokenV3 and serializes it as "cashuA. + """ + prefix = "cashuA" + assert tokenv3_serialized.startswith(prefix), Exception( + f"Token prefix not valid. Expected {prefix}." + ) + token_base64 = tokenv3_serialized[len(prefix) :] + token = json.loads(base64.urlsafe_b64decode(token_base64)) + return TokenV3.parse_obj(token) + + async def serialize_proofs( + self, proofs: List[Proof], include_mints=True, legacy=False + ): + """ + Produces sharable token with proofs and mint information. + """ + + if legacy: + # V2 tokens + token = await self._make_token_v2(proofs, include_mints) + return await self._serialize_token_base64_tokenv2(token) + + # # deprecated code for V1 tokens + # proofs_serialized = [p.to_dict() for p in proofs] + # return base64.urlsafe_b64encode( + # json.dumps(proofs_serialized).encode() + # ).decode() + + # V3 tokens + token = await self._make_token(proofs, include_mints) + return await self._serialize_token_V3(token) + + async def _make_token_v2(self, proofs: List[Proof], include_mints=True): """ Takes list of proofs and produces a TokenV2 by looking up the keyset id and mint URLs from the database. @@ -642,31 +742,27 @@ class Wallet(LedgerAPI): # dummy object to hold information about the mint mints: Dict[str, TokenV2Mint] = {} # dummy object to hold all keyset id's we need to fetch from the db later - keysets: List[str] = [] - # iterate through all proofs and remember their keyset ids for the next step - for proof in proofs: - if proof.id: - keysets.append(proof.id) + keysets: List[str] = [proof.id for proof in proofs if proof.id] # iterate through unique keyset ids for id in set(keysets): # load the keyset from the db - keyset = await get_keyset(id=id, db=self.db) - if keyset and keyset.mint_url and keyset.id: + keyset_db = await get_keyset(id=id, db=self.db) + if keyset_db and keyset_db.mint_url and keyset_db.id: # we group all mints according to URL - if keyset.mint_url not in mints: - mints[keyset.mint_url] = TokenV2Mint( - url=keyset.mint_url, - ids=[keyset.id], + if keyset_db.mint_url not in mints: + mints[keyset_db.mint_url] = TokenV2Mint( + url=keyset_db.mint_url, + ids=[keyset_db.id], ) else: # if a mint URL has multiple keysets, append to the already existing list - mints[keyset.mint_url].ids.append(keyset.id) + mints[keyset_db.mint_url].ids.append(keyset_db.id) if len(mints) > 0: # add mints grouped by url to the token token.mints = list(mints.values()) return token - async def _serialize_token_base64(self, token: TokenV2): + async def _serialize_token_base64_tokenv2(self, token: TokenV2): """ Takes a TokenV2 and serializes it in urlsafe_base64. """ @@ -676,22 +772,6 @@ class Wallet(LedgerAPI): ).decode() return token_base64 - async def serialize_proofs( - self, proofs: List[Proof], include_mints=True, legacy=False - ): - """ - Produces sharable token with proofs and mint information. - """ - - if legacy: - proofs_serialized = [p.to_dict() for p in proofs] - return base64.urlsafe_b64encode( - json.dumps(proofs_serialized).encode() - ).decode() - - token = await self._make_token(proofs, include_mints) - return await self._serialize_token_base64(token) - async def _select_proofs_to_send(self, proofs: List[Proof], amount_to_send: int): """ Selects proofs that can be used with the current mint. diff --git a/data/mint/.placeholder b/data/mint/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/data/wallet/.placeholder b/data/wallet/.placeholder deleted file mode 100644 index e69de29..0000000 diff --git a/tests/conftest.py b/tests/conftest.py index 969292e..ef066b8 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,7 @@ import multiprocessing +import shutil import time +from pathlib import Path import pytest import pytest_asyncio @@ -7,6 +9,7 @@ import uvicorn from uvicorn import Config, Server from cashu.core.migrations import migrate_databases +from cashu.core.settings import settings from cashu.wallet import migrations from cashu.wallet.wallet import Wallet @@ -23,15 +26,32 @@ class UvicornServer(multiprocessing.Process): self.terminate() def run(self, *args, **kwargs): + settings.lightning = False + settings.mint_lightning_backend = "FakeWallet" + settings.mint_listen_port = 3337 + settings.mint_database = "data/test_mint" + settings.mint_private_key = "privatekeyofthemint" + + dirpath = Path(settings.mint_database) + if dirpath.exists() and dirpath.is_dir(): + shutil.rmtree(dirpath) + + dirpath = Path("data/test_wallet") + if dirpath.exists() and dirpath.is_dir(): + shutil.rmtree(dirpath) + self.server.run() @pytest.fixture(autouse=True, scope="session") def mint(): - + settings.mint_listen_port = 3337 + settings.port = 3337 + settings.mint_url = "http://localhost:3337" + settings.port = settings.mint_listen_port config = uvicorn.Config( "cashu.mint.app:app", - port=3337, + port=settings.mint_listen_port, host="127.0.0.1", ) diff --git a/tests/test_cli.py b/tests/test_cli.py index 7c1b63f..b45bf60 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -10,23 +10,35 @@ from cashu.wallet.cli.cli import cli from cashu.wallet.wallet import Wallet from tests.conftest import SERVER_ENDPOINT, mint -cli_prefix = ["--wallet", "test_wallet", "--host", SERVER_ENDPOINT] + +@pytest.fixture(autouse=True, scope="session") +def cli_prefix(): + yield ["--wallet", "test_wallet", "--host", settings.mint_url] + + +@pytest.fixture(scope="session") +def wallet(): + wallet = Wallet(settings.mint_host, "data/test_wallet", "wallet") + asyncio.run(migrate_databases(wallet.db, migrations)) + asyncio.run(wallet.load_proofs()) + yield wallet async def init_wallet(): - wallet = Wallet(SERVER_ENDPOINT, "data/test_wallet", "wallet") + wallet = Wallet(settings.mint_host, "data/test_wallet", "wallet") await migrate_databases(wallet.db, migrations) await wallet.load_proofs() return wallet @pytest.mark.asyncio -def test_info(): +def test_info(cli_prefix): runner = CliRunner() result = runner.invoke( cli, [*cli_prefix, "info"], ) + assert result.exception is None print("INFO") print(result.output) result.output.startswith(f"Version: {settings.version}") @@ -34,26 +46,43 @@ def test_info(): @pytest.mark.asyncio -def test_balance(): +def test_balance(cli_prefix): runner = CliRunner() result = runner.invoke( cli, [*cli_prefix, "balance"], ) + assert result.exception is None print("------ BALANCE ------") print(result.output) - wallet = asyncio.run(init_wallet()) - assert f"Balance: {wallet.available_balance} sat" in result.output + w = asyncio.run(init_wallet()) + assert f"Balance: {w.available_balance} sat" in result.output assert result.exit_code == 0 @pytest.mark.asyncio -def test_wallets(): +def test_invoice(mint, cli_prefix): + runner = CliRunner() + result = runner.invoke( + cli, + [*cli_prefix, "invoice", "1000"], + ) + assert result.exception is None + print("INVOICE") + print(result.output) + # wallet = asyncio.run(init_wallet()) + # assert f"Balance: {wallet.available_balance} sat" in result.output + assert result.exit_code == 0 + + +@pytest.mark.asyncio +def test_wallets(cli_prefix): runner = CliRunner() result = runner.invoke( cli, [*cli_prefix, "wallets"], ) + assert result.exception is None print("WALLETS") # on github this is empty if len(result.output): @@ -62,62 +91,109 @@ def test_wallets(): @pytest.mark.asyncio -def test_invoice(): - runner = CliRunner() - result = runner.invoke( - cli, - [*cli_prefix, "invoice", "1000"], - ) - print("INVOICE") - print(result.output) - wallet = asyncio.run(init_wallet()) - assert f"Balance: {wallet.available_balance} sat" in result.output - assert result.exit_code == 0 - - -@pytest.mark.asyncio -def test_send(mint): +def test_send(mint, cli_prefix): runner = CliRunner() result = runner.invoke( cli, [*cli_prefix, "send", "10"], ) + assert result.exception is None print("SEND") print(result.output) - - token = [l for l in result.output.split("\n") if l.startswith("ey")][0] - print("TOKEN") - print(token) + assert "cashuA" in result.output, "output does not have a token" @pytest.mark.asyncio -def test_receive_tokenv2(mint): +def test_receive_tokenv3(mint, cli_prefix): runner = CliRunner() - token = "eyJwcm9vZnMiOiBbeyJpZCI6ICJEU0FsOW52dnlmdmEiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICJ3MEs4dE9OcFJOdVFvUzQ1Y2g1NkJ3IiwgIkMiOiAiMDI3NzcxODY4NWQ0MDgxNmQ0MTdmZGE1NWUzN2YxOTFkN2E5ODA0N2QyYWE2YzFlNDRhMWZjNTM1ZmViZDdjZDQ5In0sIHsiaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiX2J4cDVHeG1JQUVaRFB5Sm5qaFUxdyIsICJDIjogIjAzZTY2M2UzOWYyNTZlZTAzOTBiNGFiMThkZDA2OTc0NjRjZjIzYTM4OTc1MDlmZDFlYzQ1MzMxMTRlMTcwMDQ2NCJ9XSwgIm1pbnRzIjogW3sidXJsIjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzNyIsICJpZHMiOiBbIkRTQWw5bnZ2eWZ2YSJdfV19" + token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogImF5TVViZTk4NVVzeiIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogImdQNDlRdl9EZkhxck5zMjVxY1E4a0EiLCAiQyI6ICIwMzZiNjY1MzIxYzBlNGRkYTIwMTI1YTYwOWU4Y2FlMmEzMzRkODRhZDhjZWU4NjY2NTQxYjYyZjk1YjA0Y2FhNmUifSwgeyJpZCI6ICJheU1VYmU5ODVVc3oiLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJzTWJ4WGtVTlZKVTh0MTd5cVFJMnFBIiwgIkMiOiAiMDM5ZmIzMTQxN2IyNmY2YWUwMjE1NmYxNzgyZWExYTQ4NTAwMzU2OTVlMTUxODZkNmMwM2MxMzI3ZWU3YWQwZjhlIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzcifV19" result = runner.invoke( cli, - [*cli_prefix, "receive", token], + [ + *cli_prefix, + "receive", + token, + ], ) - + assert result.exception is None print("RECEIVE") print(result.output) @pytest.mark.asyncio -def test_receive_tokenv1(mint): +def test_receive_tokenv3_no_mint(mint, cli_prefix): + # this test works only if the previous test succeeds because we simulate the case where the mint URL is not in the token + # therefore, we need to know the mint keyset already and have the mint URL in the db runner = CliRunner() - token = "3siaWQiOiAiRFNBbDludnZ5ZnZhIiwgImFtb3VudCI6IDIsICJzZWNyZXQiOiAiX3VOV1ZNeDRhQndieWszRDZoLWREZyIsICJDIjogIjAyMmEzMzRmZTIzYTA1OTJhZmM3OTk3OWQyZDJmMmUwOTgxMGNkZTRlNDY5ZGYwYzZhMGE4ZDg0ZmY1MmIxOTZhNyJ9LCB7ImlkIjogIkRTQWw5bnZ2eWZ2YSIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogIk9VUUxnRE90WXhHOXJUMzZKdHFwbWciLCAiQyI6ICIwMzVmMGM2NTNhNTEzMGY4ZmQwNjY5NDg5YzEwMDY3N2Q5NGU0MGFlZjhkYWE0OWZiZDIyZTgzZjhjNThkZjczMTUifV0" + token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogImF5TVViZTk4NVVzeiIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogIkw4XzlBc3d0Rzh1UENmZ29xWnRVRFEiLCAiQyI6ICIwMmE1ZWMzYmY0Nzk2ZTg1NjJhNGRjYjM2YWRkOWYwNDhmZTU3ZGU0ZjEyMjgxMzA3N2FlZjBlM2Y2ZGIwY2U3ZGQifSwgeyJpZCI6ICJheU1VYmU5ODVVc3oiLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJ2WWJKZXNhS3BMTnNwaXl3cXd3ejFRIiwgIkMiOiAiMDJjNWVkNDc4YjZjOWU0MTExYjhlOGU1MjBlNThhMTVhYzQzMjUwMGM1MTU2ZmFjNDkyN2Q0ODVhNzM3ZTdlYzA4In1dfV19" + result = runner.invoke( + cli, + [ + *cli_prefix, + "receive", + token, + ], + ) + assert result.exception is None + print("RECEIVE") + print(result.output) + + +# @pytest.mark.asyncio +# def test_receive_tokenv3(mint): +# wallet = asyncio.run(init_wallet()) +# runner = CliRunner() +# token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjVXRWJoUzJiOXZrTyIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogInpINEM3OXpwZWJYaDIxTDBEWk1qb1EiLCAiQyI6ICIwMmI4ZDZjYzA3NjliMWNiZmQyNzkwN2U2YTQ5YmY2MGMyYzUwYmUwNzhmOGNjMWU1YWE1NTY2NjE1Y2QwOGZmM2YifSwgeyJpZCI6ICI1V0ViaFMyYjl2a08iLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJSYW1aZEJ4a01ybWtmdXh6SjFIOU9RIiwgIkMiOiAiMDI2ZGU2ZDNjZDlmNDY4MDYzMTJkYTczZDE2YzQ2ZDc3NGNkODlhZTk2NzUwMWI3MzA1MmQwNTVmODZkNmJmMmMwIn1dLCAibWludCI6ICJodHRwOi8vbG9jYWxob3N0OjMzMzcifV19" +# result = runner.invoke( +# cli, +# [*cli_prefix, "receive", token], +# ) +# assert result.exception is None +# print("RECEIVE") +# print(result.output) + + +# @pytest.mark.asyncio +# def test_receive_tokenv3_no_mint(mint): +# runner = CliRunner() +# token = "cashuAeyJ0b2tlbiI6IFt7InByb29mcyI6IFt7ImlkIjogIjVXRWJoUzJiOXZrTyIsICJhbW91bnQiOiAyLCAic2VjcmV0IjogInpINEM3OXpwZWJYaDIxTDBEWk1qb1EiLCAiQyI6ICIwMmI4ZDZjYzA3NjliMWNiZmQyNzkwN2U2YTQ5YmY2MGMyYzUwYmUwNzhmOGNjMWU1YWE1NTY2NjE1Y2QwOGZmM2YifSwgeyJpZCI6ICI1V0ViaFMyYjl2a08iLCAiYW1vdW50IjogOCwgInNlY3JldCI6ICJSYW1aZEJ4a01ybWtmdXh6SjFIOU9RIiwgIkMiOiAiMDI2ZGU2ZDNjZDlmNDY4MDYzMTJkYTczZDE2YzQ2ZDc3NGNkODlhZTk2NzUwMWI3MzA1MmQwNTVmODZkNmJmMmMwIn1dfV19" +# result = runner.invoke( +# cli, +# [*cli_prefix, "receive", token], +# ) +# assert result.exception is None +# print("RECEIVE") +# print(result.output) + + +@pytest.mark.asyncio +def test_receive_tokenv2(mint, cli_prefix): + runner = CliRunner() + token = "eyJwcm9vZnMiOiBbeyJpZCI6ICJheU1VYmU5ODVVc3oiLCAiYW1vdW50IjogMiwgInNlY3JldCI6ICJ5WWxWR2lmSmJQbGRJZmp5YUxYSnNBIiwgIkMiOiAiMDJlNDE5ZjExNGFlNTFiMzI1MGVkYjE5YTI4NzQ0MjgwMjAwMGE3NTFhZmEwZGZmZDM2N2QxYTI0NTI3NjY2NmIwIn0sIHsiaWQiOiAiYXlNVWJlOTg1VXN6IiwgImFtb3VudCI6IDgsICJzZWNyZXQiOiAiVlZraDZGTW5sUVZ2WlZOR2Z6emUwQSIsICJDIjogIjAyZGMxZDhjZmFiNDA2NGI4MWFhZThiZWEzNTBjNjIzNWM1NDIzOGNiN2E5ZmYxNTJjNjMxMTAwN2FlNDEzZmFlNyJ9XSwgIm1pbnRzIjogW3sidXJsIjogImh0dHA6Ly9sb2NhbGhvc3Q6MzMzNyIsICJpZHMiOiBbImF5TVViZTk4NVVzeiJdfV19" result = runner.invoke( cli, [*cli_prefix, "receive", token], ) + assert result.exception is None + print("RECEIVE") + print(result.output) + +@pytest.mark.asyncio +def test_receive_tokenv1(mint, cli_prefix): + runner = CliRunner() + token = "W3siaWQiOiAiYXlNVWJlOTg1VXN6IiwgImFtb3VudCI6IDIsICJzZWNyZXQiOiAicTR6WFdzYl84cGlBRHRQSzB1MFAwdyIsICJDIjogIjAyNDVlYjFmY2E1ODhlYWM0Y2M3OGJkZTJiYmMzOGQwMmY4YTIyZTEyMjcyMjQ2M2RiNDk5ZjA0ZWQ2ZDMzNjZkZCJ9LCB7ImlkIjogImF5TVViZTk4NVVzeiIsICJhbW91bnQiOiA4LCAic2VjcmV0IjogInBsaTNKX0QwNkxQZ3RmaW5EZkFWckEiLCAiQyI6ICIwMmU0MDFlMTBhYjI3ODJlYzQzYjMxZmZmMGMxZjc4N2FlYjgyODViNjkxMTAyMzlmYTJiN2VkNzA2MzdhMTliNzUifV0=" + result = runner.invoke( + cli, + [*cli_prefix, "receive", token], + ) + assert result.exception is None print("RECEIVE") print(result.output) @pytest.mark.asyncio() -def test_nostr_send(mint): +def test_nostr_send(mint, cli_prefix): runner = CliRunner() result = runner.invoke( cli, @@ -130,6 +206,6 @@ def test_nostr_send(mint): "-y", ], ) - + assert result.exception is None print("NOSTR_SEND") print(result.output)