From f56e6318e3230bd8d73c38e617bde21c887ae324 Mon Sep 17 00:00:00 2001 From: f321x Date: Mon, 16 Mar 2026 13:42:53 +0100 Subject: [PATCH] swaps: make SwapManager.percentage Decimal If SwapManager.percentage was a 0.2 float, rounding differences would cause an exception in the fee calculation inverse sanity check when entering 20 000 sats into the SwapDialog. By making self.percentage a decimal we can prevent this kind of issue. ``` File "/home/user/code/vibecoding_vm/electrum/electrum/gui/qt/swap_dialog.py", line 294, in on_send_edited recv_amount = self.swap_manager.get_recv_amount(send_amount, is_reverse=self.is_reverse) File "/home/user/code/vibecoding_vm/electrum/electrum/submarine_swaps.py", line 1320, in get_recv_amount if abs(send_amount - inverted_send_amount) > 1: ~~~~~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~ TypeError: unsupported operand type(s) for -: 'int' and 'NoneType' ``` --- electrum/commands.py | 2 +- electrum/gui/qml/qeswaphelper.py | 2 +- electrum/plugins/swapserver/server.py | 2 +- electrum/submarine_swaps.py | 16 ++++++++-------- tests/test_commands.py | 4 ++-- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/electrum/commands.py b/electrum/commands.py index 9ff0eaa60..419d1c4b5 100644 --- a/electrum/commands.py +++ b/electrum/commands.py @@ -2016,7 +2016,7 @@ class Commands(Logger): result = {} for offer in offers: result[offer.server_npub] = { - "percentage_fee": offer.pairs.percentage, + "percentage_fee": float(offer.pairs.percentage), "max_forward_sat": offer.pairs.max_forward, "max_reverse_sat": offer.pairs.max_reverse, "min_amount_sat": offer.pairs.min_amount, diff --git a/electrum/gui/qml/qeswaphelper.py b/electrum/gui/qml/qeswaphelper.py index 984befb27..7475216c4 100644 --- a/electrum/gui/qml/qeswaphelper.py +++ b/electrum/gui/qml/qeswaphelper.py @@ -75,7 +75,7 @@ class QESwapServerNPubListModel(QAbstractListModel): return { 'npub': x.server_npub, 'server_pubkey': x.server_pubkey, - 'percentage_fee': x.pairs.percentage, + 'percentage_fee': float(x.pairs.percentage), 'mining_fee': x.pairs.mining_fee, 'min_amount': x.pairs.min_amount, 'max_forward_amount': x.pairs.max_forward, diff --git a/electrum/plugins/swapserver/server.py b/electrum/plugins/swapserver/server.py index b2163736e..7d645e7e8 100644 --- a/electrum/plugins/swapserver/server.py +++ b/electrum/plugins/swapserver/server.py @@ -95,7 +95,7 @@ class HttpSwapServer(Logger, EventListener): "minimal": sm._min_amount, }, "fees": { - "percentage": sm.percentage, + "percentage": float(sm.percentage), # cast to float for <= 4.7.1 backwards compatibility "minerFees": { "baseAsset": { "normal": sm.mining_fee, diff --git a/electrum/submarine_swaps.py b/electrum/submarine_swaps.py index c320a1009..ef55aa632 100644 --- a/electrum/submarine_swaps.py +++ b/electrum/submarine_swaps.py @@ -168,7 +168,7 @@ def now(): @attr.s(frozen=True) class SwapFees: - percentage = attr.ib(type=int) + percentage = attr.ib(type=Decimal) mining_fee = attr.ib(type=int) min_amount = attr.ib(type=int) max_forward = attr.ib(type=int) @@ -235,7 +235,7 @@ class SwapManager(Logger): def __init__(self, *, wallet: 'Abstract_Wallet', lnworker: 'LNWallet'): Logger.__init__(self) self.mining_fee = None - self.percentage = None + self.percentage = None # type: Optional[Decimal] self._min_amount = None self._max_forward = None self._max_reverse = None @@ -1193,7 +1193,7 @@ class SwapManager(Logger): def server_update_pairs(self) -> None: """ for server """ - self.percentage = float(self.config.SWAPSERVER_FEE_MILLIONTHS) / 10000 # type: ignore + self.percentage = Decimal(self.config.SWAPSERVER_FEE_MILLIONTHS) / 10000 # type: ignore self._min_amount = MIN_SWAP_AMOUNT_SAT oc_balance_sat: int = self.wallet.get_spendable_balance_sat() max_forward: int = min(int(self.lnworker.num_sats_can_receive()), oc_balance_sat, 10000000) @@ -1259,7 +1259,7 @@ class SwapManager(Logger): if send_amount is None: return None x = Decimal(send_amount) - percentage = Decimal(self.percentage) + percentage = self.percentage if is_reverse: if not self.check_invoice_amount(x, is_reverse): return None @@ -1289,7 +1289,7 @@ class SwapManager(Logger): if not recv_amount: return None x = Decimal(recv_amount) - percentage = Decimal(self.percentage) + percentage = self.percentage if is_reverse: # see/ref: # https://github.com/BoltzExchange/boltz-backend/blob/e7e2d30f42a5bea3665b164feb85f84c64d86658/lib/service/Service.ts#L928 @@ -1638,7 +1638,7 @@ class HttpTransport(SwapServerTransport): fees = response['pairs']['BTC/BTC']['fees'] limits = response['pairs']['BTC/BTC']['limits'] pairs = SwapFees( - percentage=fees['percentage'], + percentage=Decimal(str(fees['percentage'])), mining_fee=fees['minerFees']['baseAsset']['mining_fee'], min_amount=limits['minimal'], max_forward=limits['max_forward_amount'], @@ -1777,7 +1777,7 @@ class NostrTransport(SwapServerTransport): self.logger.warning(f"not publishing swap offer, no liquidity available: {sm._max_forward=}, {sm._max_reverse=}") return offer = { - 'percentage_fee': sm.percentage, + 'percentage_fee': float(sm.percentage), # cast to float for <= 4.7.1 backwards compatibility 'mining_fee': sm.mining_fee, 'min_amount': sm._min_amount, 'max_forward_amount': sm._max_forward, @@ -1883,7 +1883,7 @@ class NostrTransport(SwapServerTransport): continue try: pairs = SwapFees( - percentage=content['percentage_fee'], + percentage=Decimal(str(content['percentage_fee'])), mining_fee=content['mining_fee'], min_amount=content['min_amount'], max_forward=content['max_forward_amount'], diff --git a/tests/test_commands.py b/tests/test_commands.py index dddf293fd..69b833b7a 100644 --- a/tests/test_commands.py +++ b/tests/test_commands.py @@ -678,7 +678,7 @@ class TestCommandsTestnet(ElectrumTestCase): offer1 = SwapOffer( pairs=SwapFees( - percentage=0.5, + percentage=Decimal('0.5'), mining_fee=2000, min_amount=10000, max_forward=1000000, @@ -692,7 +692,7 @@ class TestCommandsTestnet(ElectrumTestCase): offer2 = SwapOffer( pairs=SwapFees( - percentage=1.0, + percentage=Decimal('1.0'), mining_fee=3000, min_amount=20000, max_forward=2000000,