From dece143f9b80213c806d90b8808de4d7fd656e13 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Fri, 18 Oct 2024 10:32:04 +0000 Subject: [PATCH] tx.new: user fee, fee estimate cleanups --- mmgen/proto/btc/tx/new.py | 21 +++++++++++----- mmgen/proto/eth/tx/new.py | 4 ++- mmgen/tx/new.py | 52 ++++++++++++++++----------------------- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/mmgen/proto/btc/tx/new.py b/mmgen/proto/btc/tx/new.py index 747d45f9..cf185595 100755 --- a/mmgen/proto/btc/tx/new.py +++ b/mmgen/proto/btc/tx/new.py @@ -37,19 +37,30 @@ class New(Base,TxBase.New): pink(str(self.cfg.fee_estimate_confs)), suf(self.cfg.fee_estimate_confs)) + def warn_fee_estimate_fail(self, fe_type): + if not hasattr(self, '_fee_estimate_fail_warning_shown'): + msg(self.fee_fail_fs.format( + c = self.cfg.fee_estimate_confs, + t = fe_type)) + self._fee_estimate_fail_warning_shown = True + async def get_rel_fee_from_network(self): try: ret = await self.rpc.call( 'estimatesmartfee', self.cfg.fee_estimate_confs, - self.cfg.fee_estimate_mode.upper() ) - fee_per_kb = ret['feerate'] if 'feerate' in ret else -2 + self.cfg.fee_estimate_mode.upper()) + fee_per_kb = self.proto.coin_amt(ret['feerate']) if 'feerate' in ret else None fe_type = 'estimatesmartfee' except: args = self.rpc.daemon.estimatefee_args(self.rpc) - fee_per_kb = await self.rpc.call('estimatefee', *args) + ret = await self.rpc.call('estimatefee', *args) + fee_per_kb = self.proto.coin_amt(ret) fe_type = 'estimatefee' + if fee_per_kb is None: + self.warn_fee_estimate_fail(fe_type) + return fee_per_kb, fe_type # given tx size, rel fee and units, return absolute fee @@ -64,9 +75,7 @@ class New(Base,TxBase.New): def fee_est2abs(self,fee_per_kb,fe_type=None): from decimal import Decimal tx_size = self.estimate_size() - ret = self.proto.coin_amt( - fee_per_kb * Decimal(self.cfg.fee_adjust) * tx_size / 1024, - from_decimal = True) + ret = self.proto.coin_amt('1') * (fee_per_kb * self.cfg.fee_adjust * tx_size / 1024) if self.cfg.verbose: msg(fmt(f""" {fe_type.upper()} fee for {self.cfg.fee_estimate_confs} confirmations: {fee_per_kb} {self.coin}/kB diff --git a/mmgen/proto/eth/tx/new.py b/mmgen/proto/eth/tx/new.py index 8258b622..a7ae5670 100755 --- a/mmgen/proto/eth/tx/new.py +++ b/mmgen/proto/eth/tx/new.py @@ -27,6 +27,7 @@ class New(Base,TxBase.New): fee_fail_fs = 'Network fee estimation failed' no_chg_msg = 'Warning: Transaction leaves account with zero balance' usr_fee_prompt = 'Enter transaction fee or gas price: ' + msg_insufficient_funds = 'Account balance insufficient to fund this transaction ({} {} needed)' def __init__(self,*args,**kwargs): @@ -207,7 +208,8 @@ class TokenNew(TokenBase,New): return await super().precheck_sufficient_funds(inputs_sum,sel_unspent,outputs_sum) async def get_funds_available(self, fee, outputs_sum): - return (await self.twctl.get_eth_balance(self.inputs[0].addr)) - fee + bal = await self.twctl.get_eth_balance(self.inputs[0].addr) + return self._funds_available(bal >= fee, bal - fee if bal >= fee else fee - bal) def final_inputs_ok_msg(self,funds_left): token_bal = ( diff --git a/mmgen/tx/new.py b/mmgen/tx/new.py index f4bf4bcf..26999e00 100755 --- a/mmgen/tx/new.py +++ b/mmgen/tx/new.py @@ -73,13 +73,17 @@ def mmaddr2coinaddr(cfg, mmaddr, ad_w, ad_f, proto): class New(Base): fee_is_approximate = False - msg_low_coin = 'Selected outputs insufficient to fund this transaction ({} {} needed)' msg_wallet_low_coin = 'Wallet has insufficient funds for this transaction ({} {} needed)' msg_no_change_output = """ ERROR: No change address specified. If you wish to create a transaction with only one output, specify a single output address with no {} amount """ + msg_insufficient_funds = 'Selected outputs insufficient to fund this transaction ({} {} needed)' chg_autoselected = False + _funds_available = namedtuple('funds_available', ['is_positive', 'amt']) + + def warn_insufficient_funds(self, amt, coin): + msg(self.msg_insufficient_funds.format(amt.hl(), coin)) def update_output_amt(self, idx, amt): o = self.outputs[idx]._asdict() @@ -149,29 +153,10 @@ class New(Base): msg(self.msg_wallet_low_coin.format(outputs_sum-inputs_sum, self.dcoin)) return False if inputs_sum < outputs_sum: - msg(self.msg_low_coin.format(outputs_sum-inputs_sum, self.dcoin)) + self.warn_insufficient_funds(outputs_sum - inputs_sum, self.dcoin) return False return True - async def get_fee_from_user(self, have_estimate_fail=[]): - - if self.cfg.fee: - desc = 'User-selected' - start_fee = self.cfg.fee - else: - desc = self.network_estimated_fee_label - fee_per_kb, fe_type = await self.get_rel_fee_from_network() - - if fee_per_kb < 0: - if not have_estimate_fail: - msg(self.fee_fail_fs.format(c=self.cfg.fee_estimate_confs, t=fe_type)) - have_estimate_fail.append(True) - start_fee = None - else: - start_fee = self.fee_est2abs(fee_per_kb, fe_type) - - return self.get_usr_fee_interactive(start_fee, desc=desc) - def add_output(self, coinaddr, amt, is_chg=None): self.outputs.append(self.Output(self.proto, addr=coinaddr, amt=amt, is_chg=is_chg)) @@ -358,11 +343,10 @@ class New(Base): yield i self.inputs = type(self.inputs)(self, list(gen_inputs())) - def warn_insufficient_funds(self, funds_left): - msg(self.msg_low_coin.format(self.proto.coin_amt(-funds_left).hl(), self.coin)) - async def get_funds_available(self, fee, outputs_sum): - return self.sum_inputs() - outputs_sum - fee + in_ = self.sum_inputs() + out = outputs_sum + fee + return self._funds_available(in_ >= out, in_ - out if in_ >= out else out - in_) async def get_inputs_from_user(self, outputs_sum): @@ -379,19 +363,25 @@ class New(Base): self.copy_inputs_from_tw(sel_unspent) # makes self.inputs - self.usr_fee = await self.get_fee_from_user() + if self.cfg.fee: + self.usr_fee = self.get_usr_fee_interactive(self.cfg.fee, 'User-selected') + else: + fee_per_kb, fe_type = await self.get_rel_fee_from_network() + self.usr_fee = self.get_usr_fee_interactive( + None if fee_per_kb is None else self.fee_est2abs(fee_per_kb, fe_type), + self.network_estimated_fee_label) - funds_left = await self.get_funds_available(self.usr_fee,outputs_sum) + funds = await self.get_funds_available(self.usr_fee, outputs_sum) - if funds_left >= 0: - p = self.final_inputs_ok_msg(funds_left) + if funds.is_positive: + p = self.final_inputs_ok_msg(funds.amt) from ..ui import keypress_confirm if self.cfg.yes or keypress_confirm(self.cfg, p+'. OK?', default_yes=True): if self.cfg.yes: msg(p) - return funds_left + return funds.amt else: - self.warn_insufficient_funds(funds_left) + self.warn_insufficient_funds(funds.amt, self.coin) async def create(self, cmd_args, locktime=None, do_info=False, caller='txcreate'):