From 4197d6c8ca56b4f27c09e8f8a329443798e23456 Mon Sep 17 00:00:00 2001 From: callebtc <93376500+callebtc@users.noreply.github.com> Date: Wed, 29 May 2024 15:07:30 +0200 Subject: [PATCH] clean up --- cashu/core/settings.py | 1 - cashu/mint/ledger.py | 35 +++++++++++------------------------ cashu/wallet/cli/cli.py | 18 ++++++++++++++++-- cashu/wallet/helpers.py | 11 ++++++++--- cashu/wallet/nostr.py | 2 +- cashu/wallet/transactions.py | 34 ++++++++++++++++++++++++++-------- cashu/wallet/wallet.py | 23 ++++++++++++++++++----- tests/conftest.py | 1 + 8 files changed, 81 insertions(+), 44 deletions(-) diff --git a/cashu/core/settings.py b/cashu/core/settings.py index 44b46ad7..205c3099 100644 --- a/cashu/core/settings.py +++ b/cashu/core/settings.py @@ -61,7 +61,6 @@ class MintSettings(CashuSettings): mint_max_secret_length: int = Field(default=512) mint_input_fee_ppk: int = Field(default=0) - mint_internal_quote_input_fee_reserve_percent: float = Field(default=0.0) class MintBackends(MintSettings): diff --git a/cashu/mint/ledger.py b/cashu/mint/ledger.py index 24548104..ca8dc312 100644 --- a/cashu/mint/ledger.py +++ b/cashu/mint/ledger.py @@ -1,5 +1,4 @@ import asyncio -import math import time from typing import Dict, List, Mapping, Optional, Tuple @@ -569,14 +568,7 @@ async def melt_quote( if not mint_quote.checking_id: raise TransactionError("mint quote has no checking id") - internal_fee = Amount( - unit, - math.ceil( - mint_quote.amount - / 100 - * settings.mint_internal_quote_input_fee_reserve_percent - ), - ) + internal_fee = Amount(unit, 0) # no internal fees amount = Amount(unit, mint_quote.amount) payment_quote = PaymentQuoteResponse( @@ -712,14 +704,6 @@ async def melt_mint_settle_internally( if melt_quote.paid: raise TransactionError("melt quote already paid") - # verify that the amount of the input proofs is equal to the amount of the quote - total_provided = sum_proofs(proofs) - total_needed = melt_quote.amount + melt_quote.fee_reserve - if not total_provided >= total_needed: - raise TransactionError( - f"not enough inputs provided for melt. Provided: {total_provided}, needed: {total_needed}" - ) - # verify amounts from bolt11 invoice bolt11_request = melt_quote.request invoice_obj = bolt11.decode(bolt11_request) @@ -744,17 +728,16 @@ async def melt_mint_settle_internally( f" {mint_quote.quote} ({melt_quote.amount} {melt_quote.unit})" ) - # the internal transaction costs at least the ecash input fee - melt_quote.fee_paid = min( - self.get_fees_for_proofs(proofs), melt_quote.fee_reserve - ) + melt_quote.fee_paid = 0 # no internal fees melt_quote.paid = True melt_quote.paid_time = int(time.time()) - await self.crud.update_melt_quote(quote=melt_quote, db=self.db) mint_quote.paid = True mint_quote.paid_time = melt_quote.paid_time - await self.crud.update_mint_quote(quote=mint_quote, db=self.db) + + async with self.db.connect() as conn: + await self.crud.update_melt_quote(quote=melt_quote, db=self.db, conn=conn) + await self.crud.update_mint_quote(quote=mint_quote, db=self.db, conn=conn) return melt_quote @@ -799,7 +782,11 @@ async def melt( # verify that the amount of the input proofs is equal to the amount of the quote total_provided = sum_proofs(proofs) - total_needed = melt_quote.amount + melt_quote.fee_reserve + total_needed = ( + melt_quote.amount + + melt_quote.fee_reserve + + self.get_fees_for_proofs(proofs) + ) if not total_provided >= total_needed: raise TransactionError( f"not enough inputs provided for melt. Provided: {total_provided}, needed: {total_needed}" diff --git a/cashu/wallet/cli/cli.py b/cashu/wallet/cli/cli.py index 668dbc73..60747f9d 100644 --- a/cashu/wallet/cli/cli.py +++ b/cashu/wallet/cli/cli.py @@ -217,7 +217,9 @@ async def pay( if wallet.available_balance < total_amount: print(" Error: Balance too low.") return - send_proofs, fees = await wallet.select_to_send(wallet.proofs, total_amount) + send_proofs, fees = await wallet.select_to_send( + wallet.proofs, total_amount, include_fees=True + ) try: melt_response = await wallet.melt( send_proofs, invoice, quote.fee_reserve, quote.quote @@ -457,6 +459,14 @@ async def balance(ctx: Context, verbose): help="Force offline send.", type=bool, ) +@click.option( + "--include-fees", + "-f", + default=False, + is_flag=True, + help="Include fees for receiving token.", + type=bool, +) @click.pass_context @coro async def send_command( @@ -470,6 +480,7 @@ async def send_command( verbose: bool, yes: bool, offline: bool, + include_fees: bool, ): wallet: Wallet = ctx.obj["WALLET"] amount = int(amount * 100) if wallet.unit == Unit.usd else int(amount) @@ -481,6 +492,7 @@ async def send_command( legacy=legacy, offline=offline, include_dleq=dleq, + include_fees=include_fees, ) else: await send_nostr( @@ -989,7 +1001,9 @@ async def selfpay(ctx: Context, all: bool = False): mint_balance_dict = await wallet.balance_per_minturl() mint_balance = int(mint_balance_dict[wallet.url]["available"]) # send balance once to mark as reserved - await wallet.select_to_send(wallet.proofs, mint_balance, set_reserved=True) + await wallet.select_to_send( + wallet.proofs, mint_balance, set_reserved=True, include_fees=False + ) # load all reserved proofs (including the one we just sent) reserved_proofs = await get_reserved_proofs(wallet.db) if not len(reserved_proofs): diff --git a/cashu/wallet/helpers.py b/cashu/wallet/helpers.py index 5c8ac075..b20e3c62 100644 --- a/cashu/wallet/helpers.py +++ b/cashu/wallet/helpers.py @@ -58,8 +58,8 @@ async def redeem_TokenV3_multimint(wallet: Wallet, token: TokenV3) -> Wallet: keyset_ids = mint_wallet._get_proofs_keysets(t.proofs) logger.trace(f"Keysets in tokens: {' '.join(set(keyset_ids))}") await mint_wallet.load_mint() - _, _ = await mint_wallet.redeem(t.proofs) - print(f"Received {mint_wallet.unit.str(sum_proofs(t.proofs))}") + proofs_to_keep, _ = await mint_wallet.redeem(t.proofs) + print(f"Received {mint_wallet.unit.str(sum_proofs(proofs_to_keep))}") # return the last mint_wallet return mint_wallet @@ -171,6 +171,7 @@ async def send( legacy: bool, offline: bool = False, include_dleq: bool = False, + include_fees: bool = False, ): """ Prints token to send to stdout. @@ -201,7 +202,11 @@ async def send( await wallet.load_mint() # get a proof with specific amount send_proofs, fees = await wallet.select_to_send( - wallet.proofs, amount, set_reserved=False, offline=offline, tolerance=0 + wallet.proofs, + amount, + set_reserved=False, + offline=offline, + include_fees=include_fees, ) token = await wallet.serialize_proofs( diff --git a/cashu/wallet/nostr.py b/cashu/wallet/nostr.py index 72b1a60e..357e33eb 100644 --- a/cashu/wallet/nostr.py +++ b/cashu/wallet/nostr.py @@ -63,7 +63,7 @@ async def send_nostr( await wallet.load_mint() await wallet.load_proofs() _, send_proofs = await wallet.split_to_send( - wallet.proofs, amount, set_reserved=True + wallet.proofs, amount, set_reserved=True, include_fees=False ) token = await wallet.serialize_proofs(send_proofs, include_dleq=include_dleq) diff --git a/cashu/wallet/transactions.py b/cashu/wallet/transactions.py index 47dc256f..7ac5ffa8 100644 --- a/cashu/wallet/transactions.py +++ b/cashu/wallet/transactions.py @@ -1,6 +1,6 @@ import math import uuid -from typing import Dict, List, Tuple +from typing import Dict, List, Tuple, Union from loguru import logger @@ -34,6 +34,9 @@ def get_fees_for_proofs(self, proofs: List[Proof]) -> int: ) return fees + def get_fees_for_proofs_ppk(self, proofs: List[Proof]) -> int: + return sum([self.keysets[p.id].input_fee_ppk for p in proofs]) + async def _select_proofs_to_send_( self, proofs: List[Proof], amount_to_send: int, tolerance: int = 0 ) -> List[Proof]: @@ -77,13 +80,18 @@ async def _select_proofs_to_send_( return send_proofs async def _select_proofs_to_send( - self, proofs: List[Proof], amount_to_send: int + self, + proofs: List[Proof], + amount_to_send: Union[int, float], + *, + include_fees: bool = True, ) -> List[Proof]: # check that enough spendable proofs exist if sum_proofs(proofs) < amount_to_send: - raise Exception("balance too low.") + return [] + logger.trace( - f"_select_proofs_to_send – amount_to_send: {amount_to_send} – amounts we have: {amount_summary(proofs, self.unit)}" + f"_select_proofs_to_send – amount_to_send: {amount_to_send} – amounts we have: {amount_summary(proofs, self.unit)} (sum: {sum_proofs(proofs)})" ) sorted_proofs = sorted(proofs, key=lambda p: p.amount) @@ -96,26 +104,36 @@ async def _select_proofs_to_send( smaller_proofs = sorted(smaller_proofs, key=lambda p: p.amount, reverse=True) if not smaller_proofs and next_bigger: + logger.trace( + "> no proofs smaller than amount_to_send, adding next bigger proof" + ) return [next_bigger] if not smaller_proofs and not next_bigger: + logger.trace("> no proofs to select from") return [] remainder = amount_to_send selected_proofs = [smaller_proofs[0]] - logger.debug(f"adding proof: {smaller_proofs[0].amount}") - remainder -= smaller_proofs[0].amount + fee_ppk = self.get_fees_for_proofs_ppk(selected_proofs) if include_fees else 0 + logger.debug(f"adding proof: {smaller_proofs[0].amount} – fee: {fee_ppk} ppk") + remainder -= smaller_proofs[0].amount - fee_ppk / 1000 + logger.debug(f"remainder: {remainder}") if remainder > 0: + logger.trace( + f"> selecting more proofs from {amount_summary(smaller_proofs[1:], self.unit)} sum: {sum_proofs(smaller_proofs[1:])} to reach {remainder}" + ) selected_proofs += await self._select_proofs_to_send( - smaller_proofs[1:], remainder + smaller_proofs[1:], remainder, include_fees=include_fees ) sum_selected_proofs = sum_proofs(selected_proofs) if sum_selected_proofs < amount_to_send and next_bigger: + logger.trace("> adding next bigger proof") return [next_bigger] logger.trace( - f"_select_proofs_to_send - selected proof amounts: {[p.amount for p in selected_proofs]}" + f"_select_proofs_to_send - selected proof amounts: {amount_summary(selected_proofs, self.unit)} (sum: {sum_proofs(selected_proofs)})" ) return selected_proofs diff --git a/cashu/wallet/wallet.py b/cashu/wallet/wallet.py index 256a1d04..104188a9 100644 --- a/cashu/wallet/wallet.py +++ b/cashu/wallet/wallet.py @@ -878,10 +878,15 @@ async def select_to_send( *, set_reserved: bool = False, offline: bool = False, - tolerance: int = 0, + include_fees: bool = True, ) -> Tuple[List[Proof], int]: """ - Selects proofs such that a desired amount can be sent. + Selects proofs such that a desired `amount` can be sent. If the offline coin selection is unsuccessful, + and `offline` is set to False (default), we split the available proofs with the mint to get the desired `amount`. + + If `set_reserved` is set to True, the proofs are marked as reserved so they aren't used in other transactions. + + If `include_fees` is set to False, the swap fees are not included in the amount to be selected. Args: proofs (List[Proof]): Proofs to split @@ -894,14 +899,19 @@ async def select_to_send( """ # select proofs that are not reserved and are in the active keysets of the mint proofs = self.active_proofs(proofs) + if sum_proofs(proofs) < amount: + raise Exception("balance too low.") + # coin selection for potentially offline sending - send_proofs = await self._select_proofs_to_send(proofs, amount) + send_proofs = await self._select_proofs_to_send( + proofs, amount, include_fees=include_fees + ) fees = self.get_fees_for_proofs(send_proofs) logger.trace( f"select_to_send: selected: {self.unit.str(sum_proofs(send_proofs))} (+ {self.unit.str(fees)} fees) – wanted: {self.unit.str(amount)}" ) # offline coin selection unsuccessful, we need to swap proofs before we can send - if not send_proofs or sum_proofs(send_proofs) > amount + tolerance: + if not send_proofs or sum_proofs(send_proofs) > amount + fees: if not offline: logger.debug("Offline coin selection unsuccessful. Splitting proofs.") # we set the proofs as reserved later @@ -924,6 +934,7 @@ async def split_to_send( *, secret_lock: Optional[Secret] = None, set_reserved: bool = False, + include_fees: bool = True, ) -> Tuple[List[Proof], List[Proof]]: """ Swaps a set of proofs with the mint to get a set that sums up to a desired amount that can be sent. The remaining @@ -947,7 +958,9 @@ async def split_to_send( # coin selection for swapping # spendable_proofs, fees = await self._select_proofs_to_split(proofs, amount) - swap_proofs = await self._select_proofs_to_send(proofs, amount) + swap_proofs = await self._select_proofs_to_send( + proofs, amount, include_fees=True + ) # add proofs from inactive keysets to swap_proofs to get rid of them swap_proofs += [ p diff --git a/tests/conftest.py b/tests/conftest.py index f3a9a6b8..6a23880a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -45,6 +45,7 @@ settings.mint_seed_decryption_key = "" settings.mint_max_balance = 0 settings.mint_lnd_enable_mpp = True +settings.mint_input_fee_ppk = 0 assert "test" in settings.cashu_dir shutil.rmtree(settings.cashu_dir, ignore_errors=True)