From 3ba1e81fcb7cc55a377a6d8b7586d04c20853748 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Fri, 22 Mar 2024 12:11:40 +0100 Subject: [PATCH] Mint: Fakewallet support for USD (#488) * fakewallet usd wip * FakeWallet support for USD * fix api return for receive * use MINT_BACKEND_BOLT11_SAT everywhere --- .env.example | 4 +-- .github/workflows/tests.yml | 2 +- Makefile | 2 +- README.md | 15 +++++++--- cashu/core/settings.py | 10 ++++++- cashu/lightning/__init__.py | 4 +-- cashu/lightning/base.py | 9 ++++-- cashu/lightning/blink.py | 10 +++++-- cashu/lightning/corelightningrest.py | 7 +++-- cashu/lightning/fake.py | 39 ++++++++++++++++++++++---- cashu/lightning/lnbits.py | 7 +++-- cashu/lightning/lndrest.py | 7 +++-- cashu/lightning/strike.py | 6 ++-- cashu/mint/ledger.py | 22 +++++++-------- cashu/mint/startup.py | 33 ++++++++++++---------- cashu/wallet/api/router.py | 41 +++++++++++++++------------- cashu/wallet/cli/cli.py | 25 +++++++++-------- cashu/wallet/cli/cli_helpers.py | 3 +- cashu/wallet/helpers.py | 11 +++++--- docker-compose.yaml | 2 +- tests/helpers.py | 2 +- 21 files changed, 168 insertions(+), 93 deletions(-) diff --git a/.env.example b/.env.example index d8e1b7cd..76641906 100644 --- a/.env.example +++ b/.env.example @@ -55,9 +55,9 @@ MINT_DERIVATION_PATH="m/0'/0'/0'" MINT_DATABASE=data/mint -# Lightning +# Funding source backends # Supported: FakeWallet, LndRestWallet, CoreLightningRestWallet, BlinkWallet, LNbitsWallet, StrikeUSDWallet -MINT_LIGHTNING_BACKEND=FakeWallet +MINT_BACKEND_BOLT11_SAT=FakeWallet # for use with LNbitsWallet MINT_LNBITS_ENDPOINT=https://legend.lnbits.com diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index cf1a176b..42069702 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,7 +49,7 @@ jobs: poetry-version: ${{ inputs.poetry-version }} - name: Run tests env: - MINT_LIGHTNING_BACKEND: FakeWallet + MINT_BACKEND_BOLT11_SAT: FakeWallet WALLET_NAME: test_wallet MINT_HOST: localhost MINT_PORT: 3337 diff --git a/Makefile b/Makefile index f6cb065b..3dc8c347 100644 --- a/Makefile +++ b/Makefile @@ -30,7 +30,7 @@ test: test-lndrest: PYTHONUNBUFFERED=1 \ DEBUG=true \ - MINT_LIGHTNING_BACKEND=LndRestWallet \ + MINT_BACKEND_BOLT11_SAT=LndRestWallet \ MINT_LND_REST_ENDPOINT=https://localhost:8081/ \ MINT_LND_REST_CERT=../cashu-regtest-enviroment/data/lnd-3/tls.cert \ MINT_LND_REST_MACAROON=../cashu-regtest-enviroment/data/lnd-3/data/chain/bitcoin/regtest/admin.macaroon \ diff --git a/README.md b/README.md index fb6aa6b4..9a9a2b0f 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ *Disclaimer: The author is NOT a cryptographer and this work has not been reviewed. This means that there is very likely a fatal flaw somewhere. Cashu is still experimental and not production-ready.* -Cashu is an Ecash implementation based on David Wagner's variant of Chaumian blinding ([protocol specs](https://github.com/cashubtc/nuts)). Token logic based on [minicash](https://github.com/phyro/minicash) ([description](https://gist.github.com/phyro/935badc682057f418842c72961cf096c)) which implements a [Blind Diffie-Hellman Key Exchange](https://cypherpunks.venona.com/date/1996/03/msg01848.html) scheme written down [here](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406). The database mechanics in Cashu Nutshell and the Lightning backend uses parts from [LNbits](https://github.com/lnbits/lnbits-legend). +Cashu is an Ecash implementation based on David Wagner's variant of Chaumian blinding ([protocol specs](https://github.com/cashubtc/nuts)). Token logic based on [minicash](https://github.com/phyro/minicash) ([description](https://gist.github.com/phyro/935badc682057f418842c72961cf096c)) which implements a [Blind Diffie-Hellman Key Exchange](https://cypherpunks.venona.com/date/1996/03/msg01848.html) scheme written down [here](https://gist.github.com/RubenSomsen/be7a4760dd4596d06963d67baf140406).

Cashu protocol ยท @@ -169,12 +169,19 @@ You can find the API docs at [http://localhost:4448/docs](http://localhost:4448/ # Running a mint This command runs the mint on your local computer. Skip this step if you want to use the [public test mint](#test-instance) instead. -Before you can run your own mint, make sure to enable a Lightning backend in `MINT_LIGHTNING_BACKEND` and set `MINT_PRIVATE_KEY` in your `.env` file. +## Docker + +``` +docker run -d -p 3338:3338 --name nutshell -e MINT_BACKEND_BOLT11_SAT=FakeWallet -e MINT_LISTEN_HOST=0.0.0.0 -e MINT_LISTEN_PORT=3338 -e MINT_PRIVATE_KEY=TEST_PRIVATE_KEY cashubtc/nutshell:0.15.2 poetry run mint +``` + +## From this repository +Before you can run your own mint, make sure to enable a Lightning backend in `MINT_BACKEND_BOLT11_SAT` and set `MINT_PRIVATE_KEY` in your `.env` file. ```bash poetry run mint ``` -For testing, you can use Nutshell without a Lightning backend by setting `MINT_LIGHTNING_BACKEND=FakeWallet` in the `.env` file. +For testing, you can use Nutshell without a Lightning backend by setting `MINT_BACKEND_BOLT11_SAT=FakeWallet` in the `.env` file. # Running tests @@ -185,7 +192,7 @@ poetry install --with dev Then, make sure to set up your mint's `.env` file to use a fake Lightning backend and disable Tor: ```bash -MINT_LIGHTNING_BACKEND=FakeWallet +MINT_BACKEND_BOLT11_SAT=FakeWallet TOR=FALSE ``` You can run the tests with diff --git a/cashu/core/settings.py b/cashu/core/settings.py index a27fddac..f1f1946a 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -55,7 +55,11 @@ class MintSettings(CashuSettings): mint_derivation_path_list: List[str] = Field(default=[]) mint_listen_host: str = Field(default="127.0.0.1") mint_listen_port: int = Field(default=3338) - mint_lightning_backend: str = Field(default="LNbitsWallet") + + mint_lightning_backend: str = Field(default="") # deprecated + mint_backend_bolt11_sat: str = Field(default="") + mint_backend_bolt11_usd: str = Field(default="") + mint_database: str = Field(default="data/mint") mint_test_database: str = Field(default="test_data/test_mint") mint_peg_out_only: bool = Field( @@ -204,5 +208,9 @@ def startup_settings_tasks(): if settings.socks_host and settings.socks_port: settings.socks_proxy = f"socks5://{settings.socks_host}:{settings.socks_port}" + # backwards compatibility: set mint_backend_bolt11_sat from mint_lightning_backend + if settings.mint_lightning_backend: + settings.mint_backend_bolt11_sat = settings.mint_lightning_backend + startup_settings_tasks() diff --git a/cashu/lightning/__init__.py b/cashu/lightning/__init__.py index 89e46188..521eb497 100644 --- a/cashu/lightning/__init__.py +++ b/cashu/lightning/__init__.py @@ -7,5 +7,5 @@ from .lndrest import LndRestWallet # noqa: F401 from .strike import StrikeUSDWallet # noqa: F401 -if settings.mint_lightning_backend is None: - raise Exception("MINT_LIGHTNING_BACKEND not configured") +if settings.mint_backend_bolt11_sat is None or settings.mint_backend_bolt11_usd is None: + raise Exception("MINT_BACKEND_BOLT11_SAT or MINT_BACKEND_BOLT11_USD not set") diff --git a/cashu/lightning/base.py b/cashu/lightning/base.py index 089a0290..083554fd 100644 --- a/cashu/lightning/base.py +++ b/cashu/lightning/base.py @@ -62,12 +62,17 @@ def __str__(self) -> str: class LightningBackend(ABC): - units: set[Unit] + supported_units: set[Unit] + unit: Unit def assert_unit_supported(self, unit: Unit): - if unit not in self.units: + if unit not in self.supported_units: raise Unsupported(f"Unit {unit} is not supported") + @abstractmethod + def __init__(self, unit: Unit, **kwargs): + pass + @abstractmethod def status(self) -> Coroutine[None, None, StatusResponse]: pass diff --git a/cashu/lightning/blink.py b/cashu/lightning/blink.py index 5379120b..0e12fb1b 100644 --- a/cashu/lightning/blink.py +++ b/cashu/lightning/blink.py @@ -36,7 +36,6 @@ class BlinkWallet(LightningBackend): Create API Key at: https://dashboard.blink.sv/ """ - units = set([Unit.sat, Unit.usd]) wallet_ids: Dict[Unit, str] = {} endpoint = "https://api.blink.sv/graphql" invoice_statuses = {"PENDING": None, "PAID": True, "EXPIRED": False} @@ -47,7 +46,12 @@ class BlinkWallet(LightningBackend): } payment_statuses = {"SUCCESS": True, "PENDING": None, "FAILURE": False} - def __init__(self): + supported_units = set([Unit.sat, Unit.msat]) + unit = Unit.sat + + def __init__(self, unit: Unit = Unit.sat, **kwargs): + self.assert_unit_supported(unit) + self.unit = unit assert settings.mint_blink_key, "MINT_BLINK_KEY not set" self.client = httpx.AsyncClient( verify=not settings.debug, @@ -297,7 +301,7 @@ async def get_payment_status(self, checking_id: str) -> PaymentStatus: ... on SettlementViaLn { preImage } - } + } } } } diff --git a/cashu/lightning/corelightningrest.py b/cashu/lightning/corelightningrest.py index 066a1b89..dff61a41 100644 --- a/cashu/lightning/corelightningrest.py +++ b/cashu/lightning/corelightningrest.py @@ -26,9 +26,12 @@ class CoreLightningRestWallet(LightningBackend): - units = set([Unit.sat, Unit.msat]) + supported_units = set([Unit.sat, Unit.msat]) + unit = Unit.sat - def __init__(self): + def __init__(self, unit: Unit = Unit.sat, **kwargs): + self.assert_unit_supported(unit) + self.unit = unit macaroon = settings.mint_corelightning_rest_macaroon assert macaroon, "missing cln-rest macaroon" diff --git a/cashu/lightning/fake.py b/cashu/lightning/fake.py index 8da840ee..111eae1b 100644 --- a/cashu/lightning/fake.py +++ b/cashu/lightning/fake.py @@ -1,5 +1,6 @@ import asyncio import hashlib +import math import random from datetime import datetime from os import urandom @@ -28,7 +29,7 @@ class FakeWallet(LightningBackend): - units = set([Unit.sat, Unit.msat]) + fake_btc_price = 1e8 / 1337 queue: asyncio.Queue[Bolt11] = asyncio.Queue(0) payment_secrets: Dict[str, str] = dict() paid_invoices: Set[str] = set() @@ -41,6 +42,13 @@ class FakeWallet(LightningBackend): 32, ).hex() + supported_units = set([Unit.sat, Unit.msat, Unit.usd]) + unit = Unit.sat + + def __init__(self, unit: Unit = Unit.sat, **kwargs): + self.assert_unit_supported(unit) + self.unit = unit + async def status(self) -> StatusResponse: return StatusResponse(error_message=None, balance=1337) @@ -80,9 +88,19 @@ async def create_invoice( self.payment_secrets[payment_hash] = secret + amount_msat = 0 + if self.unit == Unit.sat: + amount_msat = MilliSatoshi(amount.to(Unit.msat, round="up").amount) + elif self.unit == Unit.usd: + amount_msat = MilliSatoshi( + math.ceil(amount.amount / self.fake_btc_price * 1e9) + ) + else: + raise NotImplementedError() + bolt11 = Bolt11( currency="bc", - amount_msat=MilliSatoshi(amount.to(Unit.msat, round="up").amount), + amount_msat=amount_msat, date=int(datetime.now().timestamp()), tags=tags, ) @@ -137,10 +155,19 @@ async def paid_invoices_stream(self) -> AsyncGenerator[str, None]: async def get_payment_quote(self, bolt11: str) -> PaymentQuoteResponse: invoice_obj = decode(bolt11) assert invoice_obj.amount_msat, "invoice has no amount." - amount_msat = int(invoice_obj.amount_msat) - fees_msat = fee_reserve(amount_msat) - fees = Amount(unit=Unit.msat, amount=fees_msat) - amount = Amount(unit=Unit.msat, amount=amount_msat) + + if self.unit == Unit.sat: + amount_msat = int(invoice_obj.amount_msat) + fees_msat = fee_reserve(amount_msat) + fees = Amount(unit=Unit.msat, amount=fees_msat) + amount = Amount(unit=Unit.msat, amount=amount_msat) + elif self.unit == Unit.usd: + amount_usd = math.ceil(invoice_obj.amount_msat / 1e9 * self.fake_btc_price) + amount = Amount(unit=Unit.usd, amount=amount_usd) + fees = Amount(unit=Unit.usd, amount=1) + else: + raise NotImplementedError() + return PaymentQuoteResponse( checking_id=invoice_obj.payment_hash, fee=fees, amount=amount ) diff --git a/cashu/lightning/lnbits.py b/cashu/lightning/lnbits.py index 3896a4e1..916cb710 100644 --- a/cashu/lightning/lnbits.py +++ b/cashu/lightning/lnbits.py @@ -22,9 +22,12 @@ class LNbitsWallet(LightningBackend): """https://github.com/lnbits/lnbits""" - units = set([Unit.sat]) + supported_units = set([Unit.sat]) + unit = Unit.sat - def __init__(self): + def __init__(self, unit: Unit = Unit.sat, **kwargs): + self.assert_unit_supported(unit) + self.unit = unit self.endpoint = settings.mint_lnbits_endpoint self.client = httpx.AsyncClient( verify=not settings.debug, diff --git a/cashu/lightning/lndrest.py b/cashu/lightning/lndrest.py index 3aa61a0a..dd47ea0e 100644 --- a/cashu/lightning/lndrest.py +++ b/cashu/lightning/lndrest.py @@ -27,9 +27,12 @@ class LndRestWallet(LightningBackend): """https://api.lightning.community/rest/index.html#lnd-rest-api-reference""" - units = set([Unit.sat, Unit.msat]) + supported_units = set([Unit.sat, Unit.msat]) + unit = Unit.sat - def __init__(self): + def __init__(self, unit: Unit = Unit.sat, **kwargs): + self.assert_unit_supported(unit) + self.unit = unit endpoint = settings.mint_lnd_rest_endpoint cert = settings.mint_lnd_rest_cert diff --git a/cashu/lightning/strike.py b/cashu/lightning/strike.py index c755ad17..5abd5fb6 100644 --- a/cashu/lightning/strike.py +++ b/cashu/lightning/strike.py @@ -19,9 +19,11 @@ class StrikeUSDWallet(LightningBackend): """https://github.com/lnbits/lnbits""" - units = [Unit.usd] + supported_units = [Unit.usd] - def __init__(self): + def __init__(self, unit: Unit = Unit.usd, **kwargs): + self.assert_unit_supported(unit) + self.unit = unit self.endpoint = "https://api.strike.me" # bearer auth with settings.mint_strike_key diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index cff10b4d..d994448a 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -1,6 +1,5 @@ import asyncio import copy -import math import time from typing import Dict, List, Mapping, Optional, Tuple @@ -493,10 +492,10 @@ async def melt_quote( if mint_quote: # internal transaction, validate and return amount from # associated mint quote and demand zero fees - assert ( - Amount(unit, mint_quote.amount).to(Unit.msat).amount - == invoice_obj.amount_msat - ), "amounts do not match" + # assert ( + # Amount(unit, mint_quote.amount).to(Unit.msat).amount + # == invoice_obj.amount_msat + # ), "amounts do not match" assert request == mint_quote.request, "bolt11 requests do not match" assert mint_quote.unit == melt_quote.unit, "units do not match" assert mint_quote.method == method.name, "methods do not match" @@ -506,7 +505,7 @@ async def melt_quote( payment_quote = PaymentQuoteResponse( checking_id=mint_quote.checking_id, amount=Amount(unit, mint_quote.amount), - fee=Amount(unit=Unit.msat, amount=0), + fee=Amount(unit, amount=0), ) logger.info( f"Issuing internal melt quote: {request} ->" @@ -622,11 +621,12 @@ async def melt_mint_settle_internally(self, melt_quote: MeltQuote) -> MeltQuote: bolt11_request = melt_quote.request invoice_obj = bolt11.decode(bolt11_request) assert invoice_obj.amount_msat, "invoice has no amount." - invoice_amount_sat = math.ceil(invoice_obj.amount_msat / 1000) - assert ( - Amount(Unit[melt_quote.unit], mint_quote.amount).to(Unit.sat).amount - == invoice_amount_sat - ), "amounts do not match" + # invoice_amount_sat = math.ceil(invoice_obj.amount_msat / 1000) + # assert ( + # Amount(Unit[melt_quote.unit], mint_quote.amount).to(Unit.sat).amount + # == invoice_amount_sat + # ), "amounts do not match" + assert mint_quote.amount == melt_quote.amount, "amounts do not match" assert bolt11_request == mint_quote.request, "bolt11 requests do not match" assert mint_quote.unit == melt_quote.unit, "units do not match" assert mint_quote.method == melt_quote.method, "methods do not match" diff --git a/cashu/mint/startup.py b/cashu/mint/startup.py index f094f97b..94d16e61 100644 --- a/cashu/mint/startup.py +++ b/cashu/mint/startup.py @@ -3,6 +3,7 @@ import asyncio import importlib +from typing import Dict from loguru import logger @@ -10,6 +11,7 @@ from ..core.db import Database from ..core.migrations import migrate_databases from ..core.settings import settings +from ..lightning.base import LightningBackend from ..mint import migrations from ..mint.crud import LedgerCrudSqlite from ..mint.ledger import Ledger @@ -31,23 +33,24 @@ logger.debug(f"{key}: {value}") wallets_module = importlib.import_module("cashu.lightning") -lightning_backend = getattr(wallets_module, settings.mint_lightning_backend)() -assert settings.mint_private_key is not None, "No mint private key set." +backends: Dict[Method, Dict[Unit, LightningBackend]] = {} +if settings.mint_backend_bolt11_sat: + backend_bolt11_sat = getattr(wallets_module, settings.mint_backend_bolt11_sat)( + unit=Unit.sat + ) + backends.setdefault(Method.bolt11, {})[Unit.sat] = backend_bolt11_sat +if settings.mint_backend_bolt11_usd: + backend_bolt11_usd = getattr(wallets_module, settings.mint_backend_bolt11_usd)( + unit=Unit.usd + ) + backends.setdefault(Method.bolt11, {})[Unit.usd] = backend_bolt11_usd +if not backends: + raise Exception("No backends are set.") + +if not settings.mint_private_key: + raise Exception("No mint private key is set.") -# strike_backend = getattr(wallets_module, "StrikeUSDWallet")() -# backends = { -# Method.bolt11: {Unit.sat: lightning_backend, Unit.usd: strike_backend}, -# } -# backends = { -# Method.bolt11: {Unit.sat: lightning_backend, Unit.msat: lightning_backend}, -# } -# backends = { -# Method.bolt11: {Unit.sat: lightning_backend, Unit.msat: lightning_backend, -# } -backends = { - Method.bolt11: {Unit.sat: lightning_backend}, -} ledger = Ledger( db=Database("mint", settings.mint_database), seed=settings.mint_private_key, diff --git a/cashu/wallet/api/router.py b/cashu/wallet/api/router.py index 611bfbfa..bf8a0576 100644 --- a/cashu/wallet/api/router.py +++ b/cashu/wallet/api/router.py @@ -265,10 +265,9 @@ async def receive_command( if token: tokenObj: TokenV3 = deserialize_token_from_string(token) await verify_mints(wallet, tokenObj) - balance = await receive(wallet, tokenObj) + await receive(wallet, tokenObj) elif nostr: await receive_nostr(wallet) - balance = wallet.available_balance elif all: reserved_proofs = await get_reserved_proofs(wallet.db) balance = None @@ -278,10 +277,10 @@ async def receive_command( token = await wallet.serialize_proofs(proofs) tokenObj = deserialize_token_from_string(token) await verify_mints(wallet, tokenObj) - balance = await receive(wallet, tokenObj) + await receive(wallet, tokenObj) else: raise Exception("enter token or use either flag --nostr or --all.") - assert balance + balance = wallet.available_balance return ReceiveResponse(initial_balance=initial_balance, balance=balance) @@ -359,15 +358,17 @@ async def pending( reserved_date = datetime.utcfromtimestamp( int(grouped_proofs[0].time_reserved) # type: ignore ).strftime("%Y-%m-%d %H:%M:%S") - result.update({ - f"{i}": { - "amount": sum_proofs(grouped_proofs), - "time": reserved_date, - "ID": key, - "token": token, - "mint": mint, + result.update( + { + f"{i}": { + "amount": sum_proofs(grouped_proofs), + "time": reserved_date, + "ID": key, + "token": token, + "mint": mint, + } } - }) + ) return PendingResponse(pending_token=result) @@ -412,14 +413,16 @@ async def wallets(): if w == wallet.name: active_wallet = True if active_wallet: - result.update({ - f"{w}": { - "balance": sum_proofs(wallet.proofs), - "available": sum_proofs([ - p for p in wallet.proofs if not p.reserved - ]), + result.update( + { + f"{w}": { + "balance": sum_proofs(wallet.proofs), + "available": sum_proofs( + [p for p in wallet.proofs if not p.reserved] + ), + } } - }) + ) except Exception: pass return WalletsResponse(wallets=result) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 10251477..6332d5da 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -184,7 +184,7 @@ async def cli(ctx: Context, host: str, walletname: str, unit: str, tests: bool): async def pay(ctx: Context, invoice: str, yes: bool): wallet: Wallet = ctx.obj["WALLET"] await wallet.load_mint() - print_balance(ctx) + await print_balance(ctx) quote = await wallet.get_pay_amount_with_fees(invoice) logger.debug(f"Quote: {quote}") total_amount = quote.amount + quote.fee_reserve @@ -219,7 +219,7 @@ async def pay(ctx: Context, invoice: str, yes: bool): print(f" (Preimage: {melt_response.payment_preimage}).") else: print(".") - print_balance(ctx) + await print_balance(ctx) @cli.command("invoice", help="Create Lighting invoice.") @@ -242,11 +242,12 @@ async def pay(ctx: Context, invoice: str, yes: bool): ) @click.pass_context @coro -async def invoice(ctx: Context, amount: int, id: str, split: int, no_check: bool): +async def invoice(ctx: Context, amount: float, id: str, split: int, no_check: bool): wallet: Wallet = ctx.obj["WALLET"] await wallet.load_mint() - print_balance(ctx) + await print_balance(ctx) amount = int(amount * 100) if wallet.unit == Unit.usd else int(amount) + print(f"Requesting invoice for {wallet.unit.str(amount)} {wallet.unit}.") # in case the user wants a specific split, we create a list of amounts optional_split = None if split: @@ -305,7 +306,7 @@ async def invoice(ctx: Context, amount: int, id: str, split: int, no_check: bool elif amount and id: await wallet.mint(amount, split=optional_split, id=id) print("") - print_balance(ctx) + await print_balance(ctx) return @@ -474,7 +475,7 @@ async def send_command( await send_nostr( wallet, amount=amount, pubkey=nostr or nopt, verbose=verbose, yes=yes ) - print_balance(ctx) + await print_balance(ctx) @cli.command("receive", help="Receive tokens.") @@ -508,8 +509,9 @@ async def receive_cli( mint_url, os.path.join(settings.cashu_dir, wallet.name) ) await verify_mint(mint_wallet, mint_url) + receive_wallet = await receive(wallet, tokenObj) + ctx.obj["WALLET"] = receive_wallet - await receive(wallet, tokenObj) elif nostr: await receive_nostr(wallet) # exit on keypress @@ -530,11 +532,12 @@ async def receive_cli( mint_url, os.path.join(settings.cashu_dir, wallet.name) ) await verify_mint(mint_wallet, mint_url) - await receive(wallet, tokenObj) + receive_wallet = await receive(wallet, tokenObj) + ctx.obj["WALLET"] = receive_wallet else: print("Error: enter token or use either flag --nostr or --all.") return - print_balance(ctx) + await print_balance(ctx) @cli.command("burn", help="Burn spent tokens.") @@ -586,7 +589,7 @@ async def burn(ctx: Context, token: str, all: bool, force: bool, delete: str): for i in range(0, len(proofs), settings.proofs_batch_size) ]: await wallet.invalidate(_proofs, check_spendable=True) - print_balance(ctx) + await print_balance(ctx) @cli.command("pending", help="Show pending tokens.") @@ -865,7 +868,7 @@ async def restore(ctx: Context, to: int, batch: int): await wallet.restore_wallet_from_mnemonic(mnemonic, to=to, batch=batch) await wallet.load_proofs() - print_balance(ctx) + await print_balance(ctx) @cli.command("selfpay", help="Refresh tokens.") diff --git a/cashu/wallet/cli/cli_helpers.py b/cashu/wallet/cli/cli_helpers.py index 8de0a1c7..f5102534 100644 --- a/cashu/wallet/cli/cli_helpers.py +++ b/cashu/wallet/cli/cli_helpers.py @@ -10,8 +10,9 @@ from ...wallet.wallet import Wallet as Wallet -def print_balance(ctx: Context): +async def print_balance(ctx: Context): wallet: Wallet = ctx.obj["WALLET"] + await wallet.load_proofs(reload=True, unit=wallet.unit) print(f"Balance: {wallet.unit.str(wallet.available_balance)}") diff --git a/cashu/wallet/helpers.py b/cashu/wallet/helpers.py index 26f0d1c6..547cf6bc 100644 --- a/cashu/wallet/helpers.py +++ b/cashu/wallet/helpers.py @@ -35,7 +35,7 @@ async def list_mints(wallet: Wallet): return mints -async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3): +async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3) -> Wallet: """ Helper function to iterate thruogh a token with multiple mints and redeem them from these mints one keyset at a time. @@ -58,6 +58,9 @@ async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3): _, _ = await mint_wallet.redeem(redeem_proofs) print(f"Received {mint_wallet.unit.str(sum_proofs(redeem_proofs))}") + # return the last mint_wallet + return mint_wallet + def serialize_TokenV2_to_TokenV3(tokenv2: TokenV2): """Helper function to receive legacy TokenV2 tokens. @@ -120,7 +123,7 @@ def deserialize_token_from_string(token: str) -> TokenV3: async def receive( wallet: Wallet, tokenObj: TokenV3, -): +) -> Wallet: logger.debug(f"receive: {tokenObj}") proofs = [p for t in tokenObj.token for p in t.proofs] @@ -128,7 +131,7 @@ async def receive( if includes_mint_info: # redeem tokens with new wallet instances - await redeem_TokenV3_multimint( + mint_wallet = await redeem_TokenV3_multimint( wallet, tokenObj, ) @@ -154,7 +157,7 @@ async def receive( # reload main wallet so the balance updates await wallet.load_proofs(reload=True) - return wallet.available_balance + return mint_wallet async def send( diff --git a/docker-compose.yaml b/docker-compose.yaml index 557388b7..f2695db0 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -8,7 +8,7 @@ services: ports: - "3338:3338" environment: - - MINT_LIGHTNING_BACKEND=FakeWallet + - MINT_BACKEND_BOLT11_SAT=FakeWallet - MINT_LISTEN_HOST=0.0.0.0 - MINT_LISTEN_PORT=3338 - MINT_PRIVATE_KEY=TEST_PRIVATE_KEY diff --git a/tests/helpers.py b/tests/helpers.py index 94fe729b..456ab21b 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -25,7 +25,7 @@ async def get_random_invoice_data(): wallets_module = importlib.import_module("cashu.lightning") -wallet_class = getattr(wallets_module, settings.mint_lightning_backend) +wallet_class = getattr(wallets_module, settings.mint_backend_bolt11_sat) WALLET = wallet_class() is_fake: bool = WALLET.__class__.__name__ == "FakeWallet" is_regtest: bool = not is_fake