Skip to content

Commit

Permalink
clean up
Browse files Browse the repository at this point in the history
  • Loading branch information
callebtc committed May 29, 2024
1 parent 561c0cc commit 4197d6c
Show file tree
Hide file tree
Showing 8 changed files with 81 additions and 44 deletions.
1 change: 0 additions & 1 deletion cashu/core/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
35 changes: 11 additions & 24 deletions cashu/mint/ledger.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import asyncio
import math
import time
from typing import Dict, List, Mapping, Optional, Tuple

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down Expand Up @@ -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}"
Expand Down
18 changes: 16 additions & 2 deletions cashu/wallet/cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand All @@ -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)
Expand All @@ -481,6 +492,7 @@ async def send_command(
legacy=legacy,
offline=offline,
include_dleq=dleq,
include_fees=include_fees,
)
else:
await send_nostr(
Expand Down Expand Up @@ -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):
Expand Down
11 changes: 8 additions & 3 deletions cashu/wallet/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion cashu/wallet/nostr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
34 changes: 26 additions & 8 deletions cashu/wallet/transactions.py
Original file line number Diff line number Diff line change
@@ -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

Expand Down Expand Up @@ -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]:
Expand Down Expand Up @@ -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)
Expand All @@ -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

Expand Down
23 changes: 18 additions & 5 deletions cashu/wallet/wallet.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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
Expand All @@ -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
Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit 4197d6c

Please sign in to comment.