diff --git a/cashu/wallet/api/router.py b/cashu/wallet/api/router.py index f9ff181..78309fc 100644 --- a/cashu/wallet/api/router.py +++ b/cashu/wallet/api/router.py @@ -166,13 +166,16 @@ async def send_command( default=None, description="Mint URL to send from (None for default mint)", ), + nosplit: bool = Query( + default=False, description="Do not split tokens before sending." + ), ): global wallet - wallet = await load_mint(wallet, mint) - await wallet.load_proofs() if not nostr: - balance, token = await send(wallet, amount, lock, legacy=False) + balance, token = await send( + wallet, amount, lock, legacy=False, split=not nosplit + ) return SendResponse(balance=balance, token=token) else: token, pubkey = await send_nostr(wallet, amount, nostr) diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index ffb5bb2..f6bf304 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -275,6 +275,14 @@ async def balance(ctx: Context, verbose): @click.option( "--yes", "-y", default=False, is_flag=True, help="Skip confirmation.", type=bool ) +@click.option( + "--nosplit", + "-s", + default=False, + is_flag=True, + help="Do not split tokens before sending.", + type=bool, +) @click.pass_context @coro async def send_command( @@ -286,10 +294,11 @@ async def send_command( legacy: bool, verbose: bool, yes: bool, + nosplit: bool, ): wallet: Wallet = ctx.obj["WALLET"] if not nostr and not nopt: - await send(wallet, amount, lock, legacy) + await send(wallet, amount, lock, legacy, split=not nosplit) else: await send_nostr(wallet, amount, nostr or nopt, verbose, yes) diff --git a/cashu/wallet/helpers.py b/cashu/wallet/helpers.py index 3939373..318c197 100644 --- a/cashu/wallet/helpers.py +++ b/cashu/wallet/helpers.py @@ -114,8 +114,6 @@ async def receive( tokenObj: TokenV3, lock: str, ): - # await wallet.load_mint() - # check for P2SH locks if lock: # load the script and signature of this address from the database @@ -166,10 +164,7 @@ async def receive( async def send( - wallet: Wallet, - amount: int, - lock: str, - legacy: bool, + wallet: Wallet, amount: int, lock: str, legacy: bool, split: bool = True ): """ Prints token to send to stdout. @@ -182,11 +177,24 @@ async def send( if lock and len(lock.split("P2SH:")) == 2: p2sh = True - await wallet.load_mint() await wallet.load_proofs() - _, send_proofs = await wallet.split_to_send( - wallet.proofs, amount, lock, set_reserved=True - ) + if split: + await wallet.load_mint() + _, send_proofs = await wallet.split_to_send( + wallet.proofs, amount, lock, set_reserved=True + ) + else: + # get a proof with specific amount + send_proofs = [] + for p in wallet.proofs: + if not p.reserved and p.amount == amount: + send_proofs = [p] + break + assert send_proofs, Exception( + f"No proof with this amount found. Available amounts: {set([p.amount for p in wallet.proofs])}" + ) + await wallet.set_reserved(send_proofs, reserved=True) + token = await wallet.serialize_proofs( send_proofs, include_mints=True, diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index ac3c0fa..efea5cf 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -5,7 +5,7 @@ import secrets as scrts import time import uuid from itertools import groupby -from typing import Dict, List, Optional +from typing import Dict, List, Optional, Tuple import requests from loguru import logger @@ -355,7 +355,7 @@ class LedgerAPI: If scnd_secret is provided, the wallet will create blinded secrets with those to attach a predefined spending condition to the tokens they want to send.""" - + logger.debug("Calling split. POST /split") total = sum_proofs(proofs) frst_amt, scnd_amt = total - amount, amount frst_outputs = amount_split(frst_amt) @@ -853,14 +853,6 @@ class Wallet(LedgerAPI): amount = math.ceil((decoded_invoice.amount_msat + fees * 1000) / 1000) # 1% fee return amount, fees - async def split_to_pay(self, invoice: str): - """ - Splits proofs such that a Lightning invoice can be paid. - """ - amount, _ = await self.get_pay_amount_with_fees(invoice) - _, send_proofs = await self.split_to_send(self.proofs, amount) - return send_proofs - async def split_to_send( self, proofs: List[Proof], diff --git a/tests/test_cli.py b/tests/test_cli.py index f20f868..fe47398 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -116,6 +116,29 @@ def test_send(mint, cli_prefix): assert "cashuA" in result.output, "output does not have a token" +@pytest.mark.asyncio +def test_send_without_split(mint, cli_prefix): + runner = CliRunner() + result = runner.invoke( + cli, + [*cli_prefix, "send", "2", "--nosplit"], + ) + assert result.exception is None + print("SEND") + print(result.output) + assert "cashuA" in result.output, "output does not have a token" + + +@pytest.mark.asyncio +def test_send_without_split_but_wrong_amount(mint, cli_prefix): + runner = CliRunner() + result = runner.invoke( + cli, + [*cli_prefix, "send", "10", "--nosplit"], + ) + assert "No proof with this amount found" in str(result.exception) + + @pytest.mark.asyncio def test_receive_tokenv3(mint, cli_prefix): runner = CliRunner() diff --git a/tests/test_wallet_api.py b/tests/test_wallet_api.py index 1c7b287..091367c 100644 --- a/tests/test_wallet_api.py +++ b/tests/test_wallet_api.py @@ -57,6 +57,19 @@ def test_send(mint): assert response.json()["balance"] +def test_send_without_split(mint): + with TestClient(app) as client: + response = client.post("/send?amount=1&nosplit=true") + assert response.status_code == 200 + assert response.json()["balance"] + + +def test_send_without_split_but_wrong_amount(mint): + with TestClient(app) as client: + response = client.post("/send?amount=10&nosplit=true") + assert response.status_code == 400 + + def test_pending(): with TestClient(app) as client: response = client.get("/pending")