tx.new: user fee, fee estimate cleanups

This commit is contained in:
The MMGen Project 2024-10-18 10:32:04 +00:00
commit dece143f9b
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
3 changed files with 39 additions and 38 deletions

View file

@ -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

View file

@ -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 = (

View file

@ -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'):