diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 2c23515..2a39a41 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -36,6 +36,7 @@ from ..cli.cli_helpers import ( get_unit_wallet, print_balance, print_mint_balances, + receive_all_pending, verify_mint, ) from ..helpers import ( @@ -601,7 +602,7 @@ async def receive_cli( all: bool, ): wallet: Wallet = ctx.obj["WALLET"] - + # receive a specific token if token: token_obj = deserialize_token_from_string(token) # verify that we trust the mint in this tokens @@ -615,30 +616,16 @@ async def receive_cli( await verify_mint(mint_wallet, mint_url) receive_wallet = await receive(mint_wallet, token_obj) ctx.obj["WALLET"] = receive_wallet + # receive tokens via nostr elif nostr: await receive_nostr(wallet) # exit on keypress input("Enter any text to exit.") print("Exiting.") os._exit(0) + # receive all pending outgoing tokens back to the wallet 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")): # type: ignore - proofs = list(value) - token = await wallet.serialize_proofs(proofs) - token_obj = TokenV4.deserialize(token) - # verify that we trust the mint of this token - # ask the user if they want to trust the mint - mint_url = token_obj.mint - mint_wallet = Wallet( - mint_url, - os.path.join(settings.cashu_dir, wallet.name), - unit=token_obj.unit, - ) - await verify_mint(mint_wallet, mint_url) - receive_wallet = await receive(wallet, token_obj) - ctx.obj["WALLET"] = receive_wallet + await receive_all_pending(ctx, wallet) else: print("Error: enter token or use either flag --nostr or --all.") return @@ -767,7 +754,9 @@ async def pending(ctx: Context, legacy, number: int, offset: int): ) print(f"Legacy token: {token_legacy}\n") print("--------------------------\n") - print("To remove all spent tokens use: cashu burn -a") + print("To remove all pending tokens that are already spent use: cashu burn -a") + print("To remove a specific pending token use: cashu burn ") + print("To receive all pending tokens use: cashu receive -a") @cli.command("lock", help="Generate receiving lock.") diff --git a/cashu/wallet/cli/cli_helpers.py b/cashu/wallet/cli/cli_helpers.py index 95e7618..1e146ca 100644 --- a/cashu/wallet/cli/cli_helpers.py +++ b/cashu/wallet/cli/cli_helpers.py @@ -1,4 +1,7 @@ +#!/usr/bin/env python import os +from itertools import groupby +from operator import itemgetter import click from click import Context @@ -6,8 +9,15 @@ from loguru import logger from ...core.base import Unit from ...core.settings import settings -from ...wallet.crud import get_keysets +from ...wallet.crud import ( + get_keysets, + get_reserved_proofs, +) from ...wallet.wallet import Wallet as Wallet +from ..helpers import ( + deserialize_token_from_string, + receive, +) async def print_balance(ctx: Context): @@ -170,3 +180,40 @@ async def verify_mint(mint_wallet: Wallet, url: str): ) else: logger.debug(f"We know mint {url} already") + + +async def receive_all_pending(ctx: Context, wallet: Wallet): + reserved_proofs = await get_reserved_proofs(wallet.db) + if not len(reserved_proofs): + print("No pending proofs to receive.") + return + for key, value in groupby(reserved_proofs, key=itemgetter("send_id")): # type: ignore + mint_url = None + token_obj = None + try: + proofs = list(value) + mint_url, unit = await wallet._get_proofs_mint_unit(proofs) + mint_wallet = await Wallet.with_db( + url=mint_url, + db=os.path.join(settings.cashu_dir, wallet.name), + name=wallet.name, + unit=unit.name, + ) + # verify that we trust the mint of this token + # ask the user if they want to trust the mint + await verify_mint(mint_wallet, mint_url) + + token = await mint_wallet.serialize_proofs(proofs) + token_obj = deserialize_token_from_string(token) + mint_url = token_obj.mint + receive_wallet = await receive(mint_wallet, token_obj) + ctx.obj["WALLET"] = receive_wallet + except Exception as e: + if mint_url and token_obj: + unit = Unit[token_obj.unit] + print( + f"Could not receive {unit.str(token_obj.amount)} from mint {mint_url}: {str(e)}" + ) + else: + print(f"Could not receive token: {str(e)}") + continue diff --git a/cashu/wallet/proofs.py b/cashu/wallet/proofs.py index 62a58c9..ae8b839 100644 --- a/cashu/wallet/proofs.py +++ b/cashu/wallet/proofs.py @@ -1,5 +1,5 @@ from itertools import groupby -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Tuple from loguru import logger @@ -91,6 +91,44 @@ class WalletProofs(SupportsDb, SupportsKeysets): ) return mint_urls + async def _get_proofs_keysets(self, proofs: List[Proof]) -> Dict[str, WalletKeyset]: + keyset_ids = self._get_proofs_keyset_ids(proofs) + keysets_dict = {} + async with self.db.get_connection() as conn: + for keyset_id in keyset_ids: + keyset = await get_keysets(id=keyset_id, db=self.db, conn=conn) + if len(keyset) == 1: + keysets_dict[keyset_id] = keyset[0] + return keysets_dict + + async def _get_proofs_mint_unit(self, proofs: List[Proof]) -> Tuple[str, Unit]: + """Helper function that extracts the mint URL and unit from a list of proofs. It raises an exception if the proofs are from multiple mints or units. + + Args: + proofs (List[Proof]): List of proofs to extract the mint URL and unit from. + + Raises: + Exception: If the proofs are from multiple mints or units. + Exception: If the proofs are from an unknown mint or keyset. + + Returns: + Tuple[str, Unit]: Mint URL and `Unit` of the proofs + """ + proofs_keysets = await self._get_proofs_keysets(proofs) + mint_urls = [k.mint_url for k in proofs_keysets.values()] + if not mint_urls: + raise Exception("Proofs from unknown mint or keyset.") + if len(set(mint_urls)) != 1: + raise Exception("Proofs from multiple mints.") + mint_url = mint_urls[0] + if not mint_url: + raise Exception("No mint URL found for keyset") + proofs_units = [k.unit for k in proofs_keysets.values()] + if len(set(proofs_units)) != 1: + raise Exception("Proofs from multiple units.") + unit = proofs_units[0] + return mint_url, unit + async def serialize_proofs( self, proofs: List[Proof], @@ -136,6 +174,8 @@ class WalletProofs(SupportsDb, SupportsKeysets): # extract all keysets IDs from proofs keyset_ids = self._get_proofs_keyset_ids(proofs) keysets = {k.id: k for k in self.keysets.values() if k.id in keyset_ids} + if not keysets: + raise ValueError("No keysets found for proofs") assert ( len(set([k.unit for k in keysets.values()])) == 1 ), "All keysets must have the same unit" @@ -170,7 +210,10 @@ class WalletProofs(SupportsDb, SupportsKeysets): # get all keysets from proofs keyset_ids = set(self._get_proofs_keyset_ids(proofs)) - keysets = [self.keysets[i] for i in keyset_ids] + try: + keysets = [self.keysets[i] for i in keyset_ids] + except KeyError: + raise ValueError("Keysets of proofs are not loaded in wallet") # we make sure that all proofs are from keysets of the same mint if len(set([k.mint_url for k in keysets])) > 1: raise ValueError("TokenV4 can only contain proofs from a single mint URL")