tx, tw: whitespace, cleanups
This commit is contained in:
parent
4ffe5c48d2
commit
fc55750cd2
15 changed files with 219 additions and 220 deletions
82
mmgen/amt.py
82
mmgen/amt.py
|
|
@ -21,12 +21,12 @@ amt: MMGen CoinAmt and related classes
|
|||
"""
|
||||
|
||||
from decimal import Decimal
|
||||
from .objmethods import Hilite,InitErrors
|
||||
from .objmethods import Hilite, InitErrors
|
||||
|
||||
class DecimalNegateResult(Decimal):
|
||||
pass
|
||||
|
||||
class CoinAmt(Decimal,Hilite,InitErrors): # abstract class
|
||||
class CoinAmt(Decimal, Hilite, InitErrors): # abstract class
|
||||
"""
|
||||
Instantiating with 'from_decimal' rounds value down to 'max_prec' precision.
|
||||
For addition and subtraction, operand types must match.
|
||||
|
|
@ -41,28 +41,30 @@ class CoinAmt(Decimal,Hilite,InitErrors): # abstract class
|
|||
max_amt = None # coin supply if known, otherwise None
|
||||
units = () # defined unit names, e.g. ('satoshi',...)
|
||||
|
||||
def __new__(cls,num,from_unit=None,from_decimal=False):
|
||||
if isinstance(num,cls):
|
||||
def __new__(cls, num, from_unit=None, from_decimal=False):
|
||||
|
||||
if isinstance(num, cls):
|
||||
return num
|
||||
|
||||
try:
|
||||
if from_unit:
|
||||
assert from_unit in cls.units, f'{from_unit!r}: unrecognized denomination for {cls.__name__}'
|
||||
assert type(num) is int,'value is not an integer'
|
||||
me = Decimal.__new__(cls,num * getattr(cls,from_unit))
|
||||
assert from_unit in cls.units, f'{from_unit!r}: unrecognized coin unit for {cls.__name__}'
|
||||
assert type(num) is int, 'value is not an integer'
|
||||
me = Decimal.__new__(cls, num * getattr(cls, from_unit))
|
||||
elif from_decimal:
|
||||
assert isinstance(num,Decimal), f'number must be of type Decimal, not {type(num).__name__})'
|
||||
me = Decimal.__new__(cls,num.quantize(Decimal('10') ** -cls.max_prec))
|
||||
assert isinstance(num, Decimal), f'number must be of type Decimal, not {type(num).__name__})'
|
||||
me = Decimal.__new__(cls, num.quantize(Decimal('10') ** -cls.max_prec))
|
||||
else:
|
||||
for bad_type in cls.forbidden_types:
|
||||
assert not isinstance(num,bad_type), f'number is of forbidden type {bad_type.__name__}'
|
||||
me = Decimal.__new__(cls,str(num))
|
||||
assert me.normalize().as_tuple()[-1] >= -cls.max_prec,'too many decimal places in coin amount'
|
||||
assert not isinstance(num, bad_type), f'number is of forbidden type {bad_type.__name__}'
|
||||
me = Decimal.__new__(cls, str(num))
|
||||
assert me.normalize().as_tuple()[-1] >= -cls.max_prec, 'too many decimal places in coin amount'
|
||||
if cls.max_amt:
|
||||
assert me <= cls.max_amt, f'{me}: coin amount too large (>{cls.max_amt})'
|
||||
assert me >= 0,'coin amount cannot be negative'
|
||||
assert me >= 0, 'coin amount cannot be negative'
|
||||
return me
|
||||
except Exception as e:
|
||||
return cls.init_fail(e,num)
|
||||
return cls.init_fail(e, num)
|
||||
|
||||
def to_unit(self,unit,show_decimal=False):
|
||||
ret = Decimal(self) // getattr(self,unit)
|
||||
|
|
@ -72,40 +74,36 @@ class CoinAmt(Decimal,Hilite,InitErrors): # abstract class
|
|||
return int(ret)
|
||||
|
||||
@classmethod
|
||||
def fmtc(cls,*args,**kwargs):
|
||||
def fmtc(cls, *args, **kwargs):
|
||||
cls.method_not_implemented()
|
||||
|
||||
def fmt(
|
||||
self,
|
||||
color = False,
|
||||
iwidth = 1, # width of the integer part
|
||||
prec = None ):
|
||||
def fmt(self, color=False, iwidth=1, prec=None): # iwidth: width of the integer part
|
||||
|
||||
s = str(self)
|
||||
prec = prec or self.max_prec
|
||||
|
||||
if '.' in s:
|
||||
a,b = s.split('.',1)
|
||||
a, b = s.split('.', 1)
|
||||
return self.colorize(
|
||||
a.rjust(iwidth) + '.' + b.ljust(prec)[:prec], # truncation, not rounding!
|
||||
color = color )
|
||||
color = color)
|
||||
else:
|
||||
return self.colorize(
|
||||
s.rjust(iwidth).ljust(iwidth+prec+1),
|
||||
color = color )
|
||||
color = color)
|
||||
|
||||
def hl(self,color=True):
|
||||
return self.colorize(str(self),color=color)
|
||||
def hl(self, color=True):
|
||||
return self.colorize(str(self), color=color)
|
||||
|
||||
# fancy highlighting with coin unit, enclosure, formatting
|
||||
def hl2(self,color=True,unit=False,fs='{}',encl=''):
|
||||
def hl2(self, color=True, unit=False, fs='{}', encl=''):
|
||||
res = fs.format(self)
|
||||
return (
|
||||
encl[:-1]
|
||||
+ self.colorize(
|
||||
(res.rstrip('0').rstrip('.') if '.' in res else res) +
|
||||
(' ' + self.coin if unit else ''),
|
||||
color = color )
|
||||
color = color)
|
||||
+ encl[1:]
|
||||
)
|
||||
|
||||
|
|
@ -113,26 +111,26 @@ class CoinAmt(Decimal,Hilite,InitErrors): # abstract class
|
|||
return str(int(self)) if int(self) == self else self.normalize().__format__('f')
|
||||
|
||||
def __repr__(self):
|
||||
return "{}('{}')".format(type(self).__name__,self.__str__())
|
||||
return "{}('{}')".format(type(self).__name__, self.__str__())
|
||||
|
||||
def __add__(self,other,*args,**kwargs):
|
||||
def __add__(self, other, *args, **kwargs):
|
||||
"""
|
||||
we must allow other to be int(0) to use the sum() builtin
|
||||
"""
|
||||
if type(other) not in ( type(self), DecimalNegateResult ) and other != 0:
|
||||
raise ValueError(
|
||||
f'operand {other} of incorrect type ({type(other).__name__} != {type(self).__name__})')
|
||||
return type(self)(Decimal.__add__(self,other,*args,**kwargs))
|
||||
return type(self)(Decimal.__add__(self, other, *args, **kwargs))
|
||||
|
||||
__radd__ = __add__
|
||||
|
||||
def __sub__(self,other,*args,**kwargs):
|
||||
def __sub__(self, other, *args, **kwargs):
|
||||
if type(other) is not type(self):
|
||||
raise ValueError(
|
||||
f'operand {other} of incorrect type ({type(other).__name__} != {type(self).__name__})')
|
||||
return type(self)(Decimal.__sub__(self,other,*args,**kwargs))
|
||||
return type(self)(Decimal.__sub__(self, other, *args, **kwargs))
|
||||
|
||||
def copy_negate(self,*args,**kwargs):
|
||||
def copy_negate(self, *args, **kwargs):
|
||||
"""
|
||||
We implement this so that __add__() can check type, because:
|
||||
class Decimal:
|
||||
|
|
@ -140,29 +138,29 @@ class CoinAmt(Decimal,Hilite,InitErrors): # abstract class
|
|||
...
|
||||
return self.__add__(other.copy_negate(), ...)
|
||||
"""
|
||||
return DecimalNegateResult(Decimal.copy_negate(self,*args,**kwargs))
|
||||
return DecimalNegateResult(Decimal.copy_negate(self, *args, **kwargs))
|
||||
|
||||
def __mul__(self,other,*args,**kwargs):
|
||||
def __mul__(self, other, *args, **kwargs):
|
||||
return type(self)('{:0.{p}f}'.format(
|
||||
Decimal.__mul__(self,Decimal(other),*args,**kwargs),
|
||||
Decimal.__mul__(self, Decimal(other), *args, **kwargs),
|
||||
p = self.max_prec
|
||||
))
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __truediv__(self,other,*args,**kwargs):
|
||||
def __truediv__(self, other, *args, **kwargs):
|
||||
return type(self)('{:0.{p}f}'.format(
|
||||
Decimal.__truediv__(self,Decimal(other),*args,**kwargs),
|
||||
Decimal.__truediv__(self, Decimal(other), *args, **kwargs),
|
||||
p = self.max_prec
|
||||
))
|
||||
|
||||
def __neg__(self,*args,**kwargs):
|
||||
def __neg__(self, *args, **kwargs):
|
||||
self.method_not_implemented()
|
||||
|
||||
def __floordiv__(self,*args,**kwargs):
|
||||
def __floordiv__(self, *args, **kwargs):
|
||||
self.method_not_implemented()
|
||||
|
||||
def __mod__(self,*args,**kwargs):
|
||||
def __mod__(self, *args, **kwargs):
|
||||
self.method_not_implemented()
|
||||
|
||||
class BTCAmt(CoinAmt):
|
||||
|
|
@ -195,7 +193,7 @@ class ETHAmt(CoinAmt):
|
|||
Gwei = Decimal('0.000000001')
|
||||
szabo = Decimal('0.000001')
|
||||
finney = Decimal('0.001')
|
||||
units = ('wei','Kwei','Mwei','Gwei','szabo','finney')
|
||||
units = ('wei', 'Kwei', 'Mwei', 'Gwei', 'szabo', 'finney')
|
||||
|
||||
def toWei(self):
|
||||
return int(Decimal(self) // self.wei)
|
||||
|
|
|
|||
|
|
@ -106,9 +106,9 @@ class BitcoinTwTransaction:
|
|||
'inputs': max(len(addr) for addr in gen_all_addrs('inputs')),
|
||||
'outputs': max(len(addr) for addr in gen_all_addrs('outputs'))
|
||||
}
|
||||
self.inputs_total = total( self.vouts_info['inputs'] )
|
||||
self.outputs_total = self.proto.coin_amt( sum(i['value'] for i in self.tx['decoded']['vout']) )
|
||||
self.wallet_outputs_total = total( self.vouts_info['outputs'] )
|
||||
self.inputs_total = total(self.vouts_info['inputs'])
|
||||
self.outputs_total = self.proto.coin_amt(sum(i['value'] for i in self.tx['decoded']['vout']))
|
||||
self.wallet_outputs_total = total(self.vouts_info['outputs'])
|
||||
self.fee = self.inputs_total - self.outputs_total
|
||||
self.nOutputs = len(self.tx['decoded']['vout'])
|
||||
self.confirmations = self.tx['confirmations']
|
||||
|
|
|
|||
|
|
@ -15,23 +15,23 @@ proto.btc.tx.base: Bitcoin base transaction class
|
|||
from collections import namedtuple
|
||||
|
||||
from ....tx import base as TxBase
|
||||
from ....obj import MMGenList,HexStr
|
||||
from ....util import msg,make_chksum_6,die,pp_fmt
|
||||
from ....obj import MMGenList, HexStr
|
||||
from ....util import msg, make_chksum_6, die, pp_fmt
|
||||
|
||||
def addr2scriptPubKey(proto,addr):
|
||||
def addr2scriptPubKey(proto, addr):
|
||||
|
||||
def decode_addr(proto,addr):
|
||||
def decode_addr(proto, addr):
|
||||
ap = proto.decode_addr(addr)
|
||||
assert ap, f'coin address {addr!r} could not be parsed'
|
||||
return ap.bytes.hex()
|
||||
|
||||
return {
|
||||
'p2pkh': '76a914' + decode_addr(proto,addr) + '88ac',
|
||||
'p2sh': 'a914' + decode_addr(proto,addr) + '87',
|
||||
'bech32': proto.witness_vernum_hex + '14' + decode_addr(proto,addr)
|
||||
'p2pkh': '76a914' + decode_addr(proto, addr) + '88ac',
|
||||
'p2sh': 'a914' + decode_addr(proto, addr) + '87',
|
||||
'bech32': proto.witness_vernum_hex + '14' + decode_addr(proto, addr)
|
||||
}[addr.addr_fmt]
|
||||
|
||||
def scriptPubKey2addr(proto,s):
|
||||
def scriptPubKey2addr(proto, s):
|
||||
if len(s) == 50 and s[:6] == '76a914' and s[-4:] == '88ac':
|
||||
return proto.pubhash2addr(bytes.fromhex(s[6:-4]), 'p2pkh'), 'p2pkh'
|
||||
elif len(s) == 46 and s[:4] == 'a914' and s[-2:] == '87':
|
||||
|
|
@ -41,19 +41,19 @@ def scriptPubKey2addr(proto,s):
|
|||
else:
|
||||
raise NotImplementedError(f'Unknown scriptPubKey ({s})')
|
||||
|
||||
def DeserializeTX(proto,txhex):
|
||||
def DeserializeTX(proto, txhex):
|
||||
"""
|
||||
Parse a serialized Bitcoin transaction
|
||||
For checking purposes, additionally reconstructs the serialized TX without signature
|
||||
"""
|
||||
|
||||
def bytes2int(bytes_le):
|
||||
return int(bytes_le[::-1].hex(),16)
|
||||
return int(bytes_le[::-1].hex(), 16)
|
||||
|
||||
def bytes2coin_amt(bytes_le):
|
||||
return proto.coin_amt(bytes2int(bytes_le) * proto.coin_amt.satoshi)
|
||||
|
||||
def bshift(n,skip=False,sub_null=False):
|
||||
def bshift(n, skip=False, sub_null=False):
|
||||
nonlocal idx, raw_tx
|
||||
ret = tx[idx:idx+n]
|
||||
idx += n
|
||||
|
|
@ -81,7 +81,7 @@ def DeserializeTX(proto,txhex):
|
|||
idx += vbytes_len
|
||||
if not skip:
|
||||
raw_tx += vbytes
|
||||
return int(vbytes[::-1].hex(),16)
|
||||
return int(vbytes[::-1].hex(), 16)
|
||||
|
||||
def make_txid(tx_bytes):
|
||||
from hashlib import sha256
|
||||
|
|
@ -94,20 +94,20 @@ def DeserializeTX(proto,txhex):
|
|||
d = { 'version': bytes2int(bshift(4)) }
|
||||
|
||||
if d['version'] > 0x7fffffff: # version is signed integer
|
||||
die(3,f"{d['version']}: transaction version greater than maximum allowed value (int32_t)!")
|
||||
die(3, f"{d['version']}: transaction version greater than maximum allowed value (int32_t)!")
|
||||
|
||||
has_witness = tx[idx] == 0
|
||||
if has_witness:
|
||||
u = bshift(2,skip=True).hex()
|
||||
u = bshift(2, skip=True).hex()
|
||||
if u != '0001':
|
||||
die( 'IllegalWitnessFlagValue', f'{u!r}: Illegal value for flag in transaction!' )
|
||||
die('IllegalWitnessFlagValue', f'{u!r}: Illegal value for flag in transaction!')
|
||||
|
||||
d['num_txins'] = readVInt()
|
||||
|
||||
d['txins'] = MMGenList([{
|
||||
'txid': bshift(32)[::-1].hex(),
|
||||
'vout': bytes2int(bshift(4)),
|
||||
'scriptSig': bshift(readVInt(skip=True),sub_null=True).hex(),
|
||||
'scriptSig': bshift(readVInt(skip=True), sub_null=True).hex(),
|
||||
'nSeq': bshift(4)[::-1].hex()
|
||||
} for i in range(d['num_txins'])])
|
||||
|
||||
|
|
@ -119,7 +119,7 @@ def DeserializeTX(proto,txhex):
|
|||
} for i in range(d['num_txouts'])])
|
||||
|
||||
for o in d['txouts']:
|
||||
o['address'] = scriptPubKey2addr(proto,o['scriptPubKey'])[0]
|
||||
o['address'] = scriptPubKey2addr(proto, o['scriptPubKey'])[0]
|
||||
|
||||
if has_witness:
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
|
||||
|
|
@ -131,21 +131,21 @@ def DeserializeTX(proto,txhex):
|
|||
|
||||
for txin in d['txins']:
|
||||
if tx[idx] == 0:
|
||||
bshift(1,skip=True)
|
||||
bshift(1, skip=True)
|
||||
continue
|
||||
txin['witness'] = [
|
||||
bshift(readVInt(skip=True),skip=True).hex() for item in range(readVInt(skip=True)) ]
|
||||
bshift(readVInt(skip=True), skip=True).hex() for item in range(readVInt(skip=True)) ]
|
||||
else:
|
||||
d['txid'] = make_txid(tx)
|
||||
d['witness_size'] = 0
|
||||
|
||||
if len(tx) - idx != 4:
|
||||
die( 'TxHexParseError', 'TX hex has invalid length: {} extra bytes'.format(len(tx)-idx-4) )
|
||||
die('TxHexParseError', 'TX hex has invalid length: {} extra bytes'.format(len(tx)-idx-4))
|
||||
|
||||
d['locktime'] = bytes2int(bshift(4))
|
||||
d['unsigned_hex'] = raw_tx.hex()
|
||||
|
||||
return namedtuple('deserialized_tx',list(d.keys()))(**d)
|
||||
return namedtuple('deserialized_tx', list(d.keys()))(**d)
|
||||
|
||||
class Base(TxBase.Base):
|
||||
rel_fee_desc = 'satoshis per byte'
|
||||
|
|
@ -160,7 +160,7 @@ class Base(TxBase.Base):
|
|||
def sort_func(a):
|
||||
return (
|
||||
bytes.fromhex(a.txid)
|
||||
+ int.to_bytes(a.vout,4,'big') )
|
||||
+ int.to_bytes(a.vout, 4, 'big'))
|
||||
self.sort(key=sort_func)
|
||||
|
||||
class OutputList(TxBase.Base.OutputList):
|
||||
|
|
@ -168,15 +168,15 @@ class Base(TxBase.Base):
|
|||
def sort_bip69(self):
|
||||
def sort_func(a):
|
||||
return (
|
||||
int.to_bytes(a.amt.to_unit('satoshi'),8,'big')
|
||||
+ bytes.fromhex(addr2scriptPubKey(self.parent.proto,a.addr)) )
|
||||
int.to_bytes(a.amt.to_unit('satoshi'), 8, 'big')
|
||||
+ bytes.fromhex(addr2scriptPubKey(self.parent.proto, a.addr)))
|
||||
self.sort(key=sort_func)
|
||||
|
||||
def has_segwit_inputs(self):
|
||||
return any(i.mmtype in ('S','B') for i in self.inputs)
|
||||
return any(i.mmtype in ('S', 'B') for i in self.inputs)
|
||||
|
||||
def has_segwit_outputs(self):
|
||||
return any(o.mmtype in ('S','B') for o in self.outputs)
|
||||
return any(o.mmtype in ('S', 'B') for o in self.outputs)
|
||||
|
||||
# https://bitcoin.stackexchange.com/questions/1195/how-to-calculate-transaction-size-before-sending
|
||||
# 180: uncompressed, 148: compressed
|
||||
|
|
@ -217,7 +217,7 @@ class Base(TxBase.Base):
|
|||
def get_outputs_size():
|
||||
# output bytes = amt: 8, byte_count: 1+, pk_script
|
||||
# pk_script bytes: p2pkh: 25, p2sh: 23, bech32: 22
|
||||
return sum({'p2pkh':34,'p2sh':32,'bech32':31}[o.addr.addr_fmt] for o in self.outputs)
|
||||
return sum({'p2pkh':34, 'p2sh':32, 'bech32':31}[o.addr.addr_fmt] for o in self.outputs)
|
||||
|
||||
# https://github.com/bitcoin/bips/blob/master/bip-0141.mediawiki
|
||||
# The witness is a serialization of all witness data of the transaction. Each txin is
|
||||
|
|
@ -231,7 +231,7 @@ class Base(TxBase.Base):
|
|||
if not self.has_segwit_inputs():
|
||||
return 0
|
||||
wf_size = 1 + 1 + sig_size + 1 + pubkey_size_compressed # vInt vInt sig vInt pubkey = 108
|
||||
return sum((1,wf_size)[i.mmtype in ('S','B')] for i in self.inputs)
|
||||
return sum((1, wf_size)[i.mmtype in ('S', 'B')] for i in self.inputs)
|
||||
|
||||
isize = get_inputs_size()
|
||||
osize = get_outputs_size()
|
||||
|
|
@ -250,24 +250,24 @@ class Base(TxBase.Base):
|
|||
self.cfg._util.dmsg(
|
||||
'\nData from estimate_size():\n' +
|
||||
f' inputs size: {isize}, outputs size: {osize}, witness size: {wsize}\n' +
|
||||
f' size: {new_size}, vsize: {ret}, old_size: {old_size}' )
|
||||
f' size: {new_size}, vsize: {ret}, old_size: {old_size}')
|
||||
|
||||
return int(ret * (self.cfg.vsize_adj or 1))
|
||||
|
||||
# convert absolute CoinAmt fee to sat/byte using estimated size
|
||||
def fee_abs2rel(self,abs_fee,to_unit='satoshi'):
|
||||
def fee_abs2rel(self, abs_fee, to_unit='satoshi'):
|
||||
return int(
|
||||
abs_fee /
|
||||
getattr( self.proto.coin_amt, to_unit ) /
|
||||
self.estimate_size() )
|
||||
getattr(self.proto.coin_amt, to_unit) /
|
||||
self.estimate_size())
|
||||
|
||||
@property
|
||||
def deserialized(self):
|
||||
if not self._deserialized:
|
||||
self._deserialized = DeserializeTX(self.proto,self.serialized)
|
||||
self._deserialized = DeserializeTX(self.proto, self.serialized)
|
||||
return self._deserialized
|
||||
|
||||
def update_serialized(self,data):
|
||||
def update_serialized(self, data):
|
||||
self.serialized = HexStr(data)
|
||||
self._deserialized = None
|
||||
self.check_serialized_integrity()
|
||||
|
|
@ -286,7 +286,7 @@ class Base(TxBase.Base):
|
|||
def do_error(errmsg):
|
||||
die('TxHexMismatch', errmsg+'\n'+hdr)
|
||||
|
||||
def check_equal(desc,hexio,mmio):
|
||||
def check_equal(desc, hexio, mmio):
|
||||
if mmio != hexio:
|
||||
msg('\nMMGen {d}:\n{m}\nSerialized {d}:\n{h}'.format(
|
||||
d = desc,
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class Bump(Completed,New,TxBase.Bump):
|
|||
ret.hl(),
|
||||
desc,
|
||||
self.min_fee,
|
||||
self.fee_abs2rel(self.min_fee.hl()),
|
||||
self.fee_abs2rel(self.min_fee),
|
||||
self.rel_fee_desc,
|
||||
c = self.coin ))
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -47,15 +47,16 @@ class New(Base,TxBase.New):
|
|||
fe_type = 'estimatesmartfee'
|
||||
except:
|
||||
args = self.rpc.daemon.estimatefee_args(self.rpc)
|
||||
fee_per_kb = await self.rpc.call('estimatefee',*args)
|
||||
fee_per_kb = await self.rpc.call('estimatefee', *args)
|
||||
fe_type = 'estimatefee'
|
||||
|
||||
return fee_per_kb,fe_type
|
||||
return fee_per_kb, fe_type
|
||||
|
||||
# given tx size, rel fee and units, return absolute fee
|
||||
def fee_rel2abs(self,tx_size,units,amt,unit):
|
||||
def fee_rel2abs(self, tx_size, units, amt_in_units, unit):
|
||||
if tx_size:
|
||||
return self.proto.coin_amt(amt * tx_size * getattr(self.proto.coin_amt,units[unit]))
|
||||
return self.proto.coin_amt(
|
||||
amt_in_units * tx_size * getattr(self.proto.coin_amt, units[unit]))
|
||||
else:
|
||||
return None
|
||||
|
||||
|
|
@ -65,7 +66,7 @@ class New(Base,TxBase.New):
|
|||
tx_size = self.estimate_size()
|
||||
ret = self.proto.coin_amt(
|
||||
fee_per_kb * Decimal(self.cfg.fee_adjust) * tx_size / 1024,
|
||||
from_decimal = True )
|
||||
from_decimal = True)
|
||||
if self.cfg.verbose:
|
||||
msg(fmt(f"""
|
||||
{fe_type.upper()} fee for {self.cfg.fee_estimate_confs} confirmations: {fee_per_kb} {self.coin}/kB
|
||||
|
|
@ -100,9 +101,7 @@ class New(Base,TxBase.New):
|
|||
msg(self.no_chg_msg)
|
||||
self.outputs.pop(self.chg_idx)
|
||||
else:
|
||||
self.update_output_amt(
|
||||
self.chg_idx,
|
||||
self.proto.coin_amt(funds_left) )
|
||||
self.update_output_amt(self.chg_idx, self.proto.coin_amt(funds_left))
|
||||
|
||||
def check_fee(self):
|
||||
fee = self.sum_inputs() - self.sum_outputs()
|
||||
|
|
@ -111,10 +110,7 @@ class New(Base,TxBase.New):
|
|||
die( 'MaxFeeExceeded', f'Transaction fee of {fee} {c} too high! (> {self.proto.max_tx_fee} {c})' )
|
||||
|
||||
def final_inputs_ok_msg(self,funds_left):
|
||||
return 'Transaction produces {} {} in change'.format(
|
||||
self.proto.coin_amt(funds_left).hl(),
|
||||
self.coin
|
||||
)
|
||||
return 'Transaction produces {} {} in change'.format(self.proto.coin_amt(funds_left).hl(), self.coin)
|
||||
|
||||
async def create_serialized(self,locktime=None,bump=None):
|
||||
|
||||
|
|
|
|||
|
|
@ -41,7 +41,8 @@ class TokenCommon(MMGenObject):
|
|||
return CoinAddr(self.proto,parse_abi(data)[1][-40:])
|
||||
|
||||
def transferdata2amt(self,data): # online
|
||||
return self.proto.coin_amt(int(parse_abi(data)[-1],16) * self.base_unit)
|
||||
return self.proto.coin_amt(
|
||||
int(parse_abi(data)[-1], 16) * self.base_unit)
|
||||
|
||||
async def do_call(self,method_sig,method_args='',toUnit=False):
|
||||
data = self.create_method_id(method_sig) + method_args
|
||||
|
|
@ -57,7 +58,8 @@ class TokenCommon(MMGenObject):
|
|||
return ret
|
||||
|
||||
async def get_balance(self,acct_addr):
|
||||
return self.proto.coin_amt(await self.do_call('balanceOf(address)',acct_addr.rjust(64,'0'),toUnit=True))
|
||||
return self.proto.coin_amt(
|
||||
await self.do_call('balanceOf(address)', acct_addr.rjust(64, '0'), toUnit=True))
|
||||
|
||||
def strip(self,s):
|
||||
return ''.join([chr(b) for b in s if 32 <= b <= 127]).strip()
|
||||
|
|
|
|||
|
|
@ -82,7 +82,9 @@ class EthereumTwCtl(TwCtl):
|
|||
msg(f'{self.desc} upgraded successfully!')
|
||||
|
||||
async def rpc_get_balance(self,addr):
|
||||
return self.proto.coin_amt(int(await self.rpc.call('eth_getBalance','0x'+addr,'latest'),16),'wei')
|
||||
return self.proto.coin_amt(
|
||||
int(await self.rpc.call('eth_getBalance', '0x' + addr, 'latest'), 16),
|
||||
from_unit = 'wei')
|
||||
|
||||
@write_mode
|
||||
async def batch_import_address(self,args_list):
|
||||
|
|
|
|||
|
|
@ -30,15 +30,15 @@ class Base(TxBase.Base):
|
|||
disable_fee_check = False
|
||||
|
||||
# given absolute fee in ETH, return gas price in Gwei using self.gas
|
||||
def fee_abs2rel(self,abs_fee,to_unit='Gwei'):
|
||||
ret = self.proto.coin_amt(int(abs_fee.toWei() // self.gas.toWei()),'wei')
|
||||
def fee_abs2rel(self, abs_fee, to_unit='Gwei'):
|
||||
ret = self.proto.coin_amt(int(abs_fee.toWei() // self.gas.toWei()), from_unit='wei')
|
||||
self.cfg._util.dmsg(f'fee_abs2rel() ==> {ret} ETH')
|
||||
return ret if to_unit == 'eth' else ret.to_unit(to_unit,show_decimal=True)
|
||||
return ret if to_unit == 'eth' else ret.to_unit(to_unit, show_decimal=True)
|
||||
|
||||
# given rel fee (gasPrice) in wei, return absolute fee using self.gas (Ethereum-only method)
|
||||
def fee_gasPrice2abs(self,rel_fee):
|
||||
assert isinstance(rel_fee,int), f'{rel_fee!r}: incorrect type for fee estimate (not an integer)'
|
||||
return self.proto.coin_amt(rel_fee * self.gas.toWei(),'wei')
|
||||
def fee_gasPrice2abs(self, rel_fee):
|
||||
assert isinstance(rel_fee, int), f'{rel_fee!r}: incorrect type for fee estimate (not an integer)'
|
||||
return self.proto.coin_amt(rel_fee * self.gas.toWei(), from_unit='wei')
|
||||
|
||||
def is_replaceable(self):
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -24,8 +24,8 @@ class Completed(Base,TxBase.Completed):
|
|||
|
||||
super().__init__(*args,**kwargs)
|
||||
|
||||
self.gas = self.proto.coin_amt(self.dfl_gas,'wei')
|
||||
self.start_gas = self.proto.coin_amt(self.dfl_start_gas,'wei')
|
||||
self.gas = self.proto.coin_amt(self.dfl_gas, from_unit='wei')
|
||||
self.start_gas = self.proto.coin_amt(self.dfl_start_gas, from_unit='wei')
|
||||
|
||||
@property
|
||||
def send_amt(self):
|
||||
|
|
|
|||
|
|
@ -33,10 +33,10 @@ class New(Base,TxBase.New):
|
|||
super().__init__(*args,**kwargs)
|
||||
|
||||
if self.cfg.gas:
|
||||
self.gas = self.start_gas = self.proto.coin_amt(int(self.cfg.gas),'wei')
|
||||
self.gas = self.start_gas = self.proto.coin_amt(int(self.cfg.gas), from_unit='wei')
|
||||
else:
|
||||
self.gas = self.proto.coin_amt(self.dfl_gas,'wei')
|
||||
self.start_gas = self.proto.coin_amt(self.dfl_start_gas,'wei')
|
||||
self.gas = self.proto.coin_amt(self.dfl_gas, from_unit='wei')
|
||||
self.start_gas = self.proto.coin_amt(self.dfl_start_gas, from_unit='wei')
|
||||
|
||||
if self.cfg.contract_data:
|
||||
m = "'--contract-data' option may not be used with token transaction"
|
||||
|
|
@ -115,18 +115,17 @@ class New(Base,TxBase.New):
|
|||
|
||||
# get rel_fee (gas price) from network, return in native wei
|
||||
async def get_rel_fee_from_network(self):
|
||||
return Int(await self.rpc.call('eth_gasPrice'),16),'eth_gasPrice' # ==> rel_fee,fe_type
|
||||
return Int(await self.rpc.call('eth_gasPrice'),16), 'eth_gasPrice'
|
||||
|
||||
def check_fee(self):
|
||||
if not self.disable_fee_check:
|
||||
assert self.usr_fee <= self.proto.max_tx_fee
|
||||
|
||||
# given rel fee and units, return absolute fee using self.gas
|
||||
def fee_rel2abs(self,tx_size,units,amt,unit):
|
||||
def fee_rel2abs(self, tx_size, units, amt_in_units, unit):
|
||||
return self.proto.coin_amt(
|
||||
self.proto.coin_amt(amt,units[unit]).toWei() * self.gas.toWei(),
|
||||
from_unit='wei'
|
||||
)
|
||||
self.proto.coin_amt(amt_in_units, units[unit]).toWei() * self.gas.toWei(),
|
||||
from_unit = 'wei')
|
||||
|
||||
# given fee estimate (gas price) in wei, return absolute fee, adjusting by self.cfg.fee_adjust
|
||||
def fee_est2abs(self,rel_fee,fe_type=None):
|
||||
|
|
@ -174,7 +173,7 @@ class New(Base,TxBase.New):
|
|||
die(1,f'{addr!r}: not an MMGen ID or coin address')
|
||||
return ret
|
||||
|
||||
def final_inputs_ok_msg(self,funds_left):
|
||||
def final_inputs_ok_msg(self, funds_left):
|
||||
chg = '0' if (self.outputs and self.outputs[0].is_chg) else funds_left
|
||||
return 'Transaction leaves {} {} in the sender’s account'.format(
|
||||
self.proto.coin_amt(chg).hl(),
|
||||
|
|
@ -207,8 +206,8 @@ class TokenNew(TokenBase,New):
|
|||
return False
|
||||
return await super().precheck_sufficient_funds(inputs_sum,sel_unspent,outputs_sum)
|
||||
|
||||
async def get_funds_left(self,fee,outputs_sum):
|
||||
return ( await self.twctl.get_eth_balance(self.inputs[0].addr) ) - fee
|
||||
async def get_funds_available(self, fee, outputs_sum):
|
||||
return (await self.twctl.get_eth_balance(self.inputs[0].addr)) - fee
|
||||
|
||||
def final_inputs_ok_msg(self,funds_left):
|
||||
token_bal = (
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ class Signed(Completed,TxBase.Signed):
|
|||
'from': CoinAddr(self.proto,d['sender']),
|
||||
# NB: for token, 'to' is token address
|
||||
'to': CoinAddr(self.proto,d['to']) if d['to'] else None,
|
||||
'amt': self.proto.coin_amt(d['value'],'wei'),
|
||||
'gasPrice': self.proto.coin_amt(d['gasprice'],'wei'),
|
||||
'startGas': self.proto.coin_amt(d['startgas'],'wei'),
|
||||
'amt': self.proto.coin_amt(d['value'], from_unit='wei'),
|
||||
'gasPrice': self.proto.coin_amt(d['gasprice'], from_unit='wei'),
|
||||
'startGas': self.proto.coin_amt(d['startgas'], from_unit='wei'),
|
||||
'nonce': ETHNonce(d['nonce']),
|
||||
'data': HexStr(d['data']) }
|
||||
if o['data'] and not o['to']: # token- or contract-creating transaction
|
||||
|
|
|
|||
|
|
@ -378,7 +378,9 @@ class RPCClient(MMGenObject):
|
|||
wallet = wallet )
|
||||
|
||||
def process_http_resp(self,run_ret,batch=False,json_rpc=True):
|
||||
text,status = run_ret
|
||||
|
||||
text, status = run_ret
|
||||
|
||||
if status == 200:
|
||||
dmsg_rpc(' RPC RESPONSE data ==>\n{}\n',text,is_json=True)
|
||||
m = None
|
||||
|
|
|
|||
|
|
@ -22,8 +22,8 @@ tx.file: Transaction file operations for the MMGen suite
|
|||
|
||||
import os, json
|
||||
|
||||
from ..util import ymsg,make_chksum_6,die
|
||||
from ..obj import MMGenObject,HexStr,MMGenTxID,CoinTxID,MMGenTxComment
|
||||
from ..util import ymsg, make_chksum_6, die
|
||||
from ..obj import MMGenObject, HexStr, MMGenTxID, CoinTxID, MMGenTxComment
|
||||
from ..rpc import json_encoder
|
||||
|
||||
def json_dumps(data):
|
||||
|
|
@ -51,7 +51,7 @@ def eval_io_data(tx, data, desc):
|
|||
'inputs': (tx.Input, tx.InputList),
|
||||
'outputs': (tx.Output, tx.OutputList),
|
||||
}[desc]
|
||||
return io_list(parent=tx, data=[io(tx.proto,**d) for d in data])
|
||||
return io_list(parent=tx, data=[io(tx.proto, **d) for d in data])
|
||||
|
||||
class MMGenTxFile(MMGenObject):
|
||||
data_label = 'MMGenTransaction'
|
||||
|
|
@ -70,7 +70,7 @@ class MMGenTxFile(MMGenObject):
|
|||
'sent_timestamp': None,
|
||||
}
|
||||
|
||||
def __init__(self,tx):
|
||||
def __init__(self, tx):
|
||||
self.tx = tx
|
||||
self.fmt_data = None
|
||||
self.filename = None
|
||||
|
|
@ -127,13 +127,13 @@ class MMGenTxFile(MMGenObject):
|
|||
if desc == 'inputs':
|
||||
ymsg('Warning: transaction data appears to be in old format')
|
||||
import re
|
||||
return literal_eval(re.sub(r"[A-Za-z]+?\(('.+?')\)",r'\1', raw_data))
|
||||
return literal_eval(re.sub(r"[A-Za-z]+?\(('.+?')\)", r'\1', raw_data))
|
||||
|
||||
desc = 'data'
|
||||
try:
|
||||
tx_data = data.splitlines()
|
||||
assert len(tx_data) >= 5,'number of lines less than 5'
|
||||
assert len(tx_data[0]) == 6,'invalid length of first line'
|
||||
assert len(tx_data) >= 5, 'number of lines less than 5'
|
||||
assert len(tx_data[0]) == 6, 'invalid length of first line'
|
||||
assert HexStr(tx_data.pop(0)) == make_chksum_6(' '.join(tx_data)), 'file data does not match checksum'
|
||||
|
||||
if len(tx_data) == 7:
|
||||
|
|
@ -142,26 +142,26 @@ class MMGenTxFile(MMGenObject):
|
|||
assert _ == 'Sent', 'invalid sent timestamp line'
|
||||
|
||||
if len(tx_data) == 6:
|
||||
assert len(tx_data[-1]) == 64,'invalid coin TxID length'
|
||||
assert len(tx_data[-1]) == 64, 'invalid coin TxID length'
|
||||
desc = 'coin TxID'
|
||||
tx.coin_txid = CoinTxID(tx_data.pop(-1))
|
||||
|
||||
if len(tx_data) == 5:
|
||||
# rough check: allow for 4-byte utf8 characters + base58 (4 * 11 / 8 = 6 (rounded up))
|
||||
assert len(tx_data[-1]) < MMGenTxComment.max_len*6,'invalid comment length'
|
||||
assert len(tx_data[-1]) < MMGenTxComment.max_len*6, 'invalid comment length'
|
||||
c = tx_data.pop(-1)
|
||||
if c != '-':
|
||||
desc = 'encoded comment (not base58)'
|
||||
from ..baseconv import baseconv
|
||||
comment = baseconv('b58').tobytes(c).decode()
|
||||
assert comment is not False,'invalid comment'
|
||||
assert comment is not False, 'invalid comment'
|
||||
desc = 'comment'
|
||||
tx.comment = MMGenTxComment(comment)
|
||||
|
||||
desc = 'number of lines' # four required lines
|
||||
io_data = {}
|
||||
(metadata, tx.serialized, io_data['inputs'], io_data['outputs']) = tx_data
|
||||
assert len(metadata) < 100,'invalid metadata length' # rough check
|
||||
assert len(metadata) < 100, 'invalid metadata length' # rough check
|
||||
metadata = metadata.split()
|
||||
|
||||
if metadata[-1].startswith('LT='):
|
||||
|
|
@ -203,7 +203,7 @@ class MMGenTxFile(MMGenObject):
|
|||
desc = 'send amount in metadata'
|
||||
assert tx.proto.coin_amt(send_amt) == tx.send_amt, f'{send_amt} != {tx.send_amt}'
|
||||
except Exception as e:
|
||||
die(2,f'Invalid {desc} in transaction file: {e!s}')
|
||||
die(2, f'Invalid {desc} in transaction file: {e!s}')
|
||||
|
||||
def make_filename(self):
|
||||
tx = self.tx
|
||||
|
|
@ -213,7 +213,7 @@ class MMGenTxFile(MMGenObject):
|
|||
yield '-' + tx.dcoin
|
||||
yield f'[{tx.send_amt!s}'
|
||||
if tx.is_replaceable():
|
||||
yield ',{}'.format(tx.fee_abs2rel(tx.fee,to_unit=tx.fn_fee_unit))
|
||||
yield ',{}'.format(tx.fee_abs2rel(tx.fee, to_unit=tx.fn_fee_unit))
|
||||
if tx.get_serialized_locktime():
|
||||
yield f',tl={tx.get_serialized_locktime()}'
|
||||
yield ']'
|
||||
|
|
@ -248,7 +248,7 @@ class MMGenTxFile(MMGenObject):
|
|||
|
||||
if tx.comment:
|
||||
from ..baseconv import baseconv
|
||||
lines.append(baseconv('b58').frombytes(tx.comment.encode(),tostr=True))
|
||||
lines.append(baseconv('b58').frombytes(tx.comment.encode(), tostr=True))
|
||||
|
||||
if tx.coin_txid:
|
||||
if not tx.comment:
|
||||
|
|
@ -276,7 +276,7 @@ class MMGenTxFile(MMGenObject):
|
|||
fmt_data = {'json': format_data_json, 'legacy': format_data_legacy}[tx.file_format]()
|
||||
|
||||
if len(fmt_data) > tx.cfg.max_tx_file_size:
|
||||
die( 'MaxFileSizeExceeded', f'Transaction file size exceeds limit ({tx.cfg.max_tx_file_size} bytes)' )
|
||||
die('MaxFileSizeExceeded', f'Transaction file size exceeds limit ({tx.cfg.max_tx_file_size} bytes)')
|
||||
|
||||
return fmt_data
|
||||
|
||||
|
|
@ -286,7 +286,7 @@ class MMGenTxFile(MMGenObject):
|
|||
ask_write = True,
|
||||
ask_write_default_yes = False,
|
||||
ask_tty = True,
|
||||
ask_overwrite = True ):
|
||||
ask_overwrite = True):
|
||||
|
||||
if ask_write is False:
|
||||
ask_write_default_yes = True
|
||||
|
|
@ -310,8 +310,8 @@ class MMGenTxFile(MMGenObject):
|
|||
ignore_opt_outdir = outdir)
|
||||
|
||||
@classmethod
|
||||
def get_proto(cls,cfg,filename,quiet_open=False):
|
||||
def get_proto(cls, cfg, filename, quiet_open=False):
|
||||
from . import BaseTX
|
||||
tmp_tx = BaseTX(cfg=cfg)
|
||||
cls(tmp_tx).parse(filename,metadata_only=True,quiet_open=quiet_open)
|
||||
cls(tmp_tx).parse(filename, metadata_only=True, quiet_open=quiet_open)
|
||||
return tmp_tx.proto
|
||||
|
|
|
|||
162
mmgen/tx/new.py
162
mmgen/tx/new.py
|
|
@ -16,9 +16,9 @@ from collections import namedtuple
|
|||
|
||||
from .base import Base
|
||||
from ..cfg import gc
|
||||
from ..color import pink,yellow
|
||||
from ..obj import get_obj,MMGenList
|
||||
from ..util import msg,fmt,die,suf,remove_dups,get_extension
|
||||
from ..color import pink, yellow
|
||||
from ..obj import get_obj, MMGenList
|
||||
from ..util import msg, fmt, die, suf, remove_dups, get_extension
|
||||
from ..addr import (
|
||||
is_mmgen_id,
|
||||
MMGenAddrType,
|
||||
|
|
@ -29,7 +29,7 @@ from ..addr import (
|
|||
is_addrlist_id
|
||||
)
|
||||
|
||||
def mmaddr2coinaddr(cfg,mmaddr,ad_w,ad_f,proto):
|
||||
def mmaddr2coinaddr(cfg, mmaddr, ad_w, ad_f, proto):
|
||||
|
||||
def wmsg(k):
|
||||
messages = {
|
||||
|
|
@ -49,7 +49,7 @@ def mmaddr2coinaddr(cfg,mmaddr,ad_w,ad_f,proto):
|
|||
address file for it on the command line.
|
||||
"""
|
||||
}
|
||||
return '\n' + fmt(messages[k],indent=' ')
|
||||
return '\n' + fmt(messages[k], indent=' ')
|
||||
|
||||
# assume mmaddr has already been checked
|
||||
coin_addr = ad_w.mmaddr2coinaddr(mmaddr)
|
||||
|
|
@ -60,15 +60,15 @@ def mmaddr2coinaddr(cfg,mmaddr,ad_w,ad_f,proto):
|
|||
if coin_addr:
|
||||
msg(wmsg('addr_in_addrfile_only'))
|
||||
from ..ui import keypress_confirm
|
||||
if not (cfg.yes or keypress_confirm( cfg, 'Continue anyway?' )):
|
||||
if not (cfg.yes or keypress_confirm(cfg, 'Continue anyway?')):
|
||||
import sys
|
||||
sys.exit(1)
|
||||
else:
|
||||
die(2,wmsg('addr_not_found'))
|
||||
die(2, wmsg('addr_not_found'))
|
||||
else:
|
||||
die(2,wmsg('addr_not_found_no_addrfile'))
|
||||
die(2, wmsg('addr_not_found_no_addrfile'))
|
||||
|
||||
return CoinAddr(proto,coin_addr)
|
||||
return CoinAddr(proto, coin_addr)
|
||||
|
||||
class New(Base):
|
||||
|
||||
|
|
@ -81,32 +81,32 @@ class New(Base):
|
|||
"""
|
||||
chg_autoselected = False
|
||||
|
||||
def update_output_amt(self,idx,amt):
|
||||
def update_output_amt(self, idx, amt):
|
||||
o = self.outputs[idx]._asdict()
|
||||
o['amt'] = amt
|
||||
self.outputs[idx] = self.Output(self.proto,**o)
|
||||
self.outputs[idx] = self.Output(self.proto, **o)
|
||||
|
||||
def add_mmaddrs_to_outputs(self,ad_w,ad_f):
|
||||
def add_mmaddrs_to_outputs(self, ad_w, ad_f):
|
||||
a = [e.addr for e in self.outputs]
|
||||
d = ad_w.make_reverse_dict(a)
|
||||
if ad_f:
|
||||
d.update(ad_f.make_reverse_dict(a))
|
||||
for e in self.outputs:
|
||||
if e.addr and e.addr in d:
|
||||
e.mmid,f = d[e.addr]
|
||||
e.mmid, f = d[e.addr]
|
||||
if f:
|
||||
e.comment = f
|
||||
|
||||
def check_dup_addrs(self,io_str):
|
||||
assert io_str in ('inputs','outputs')
|
||||
addrs = [e.addr for e in getattr(self,io_str)]
|
||||
def check_dup_addrs(self, io_str):
|
||||
assert io_str in ('inputs', 'outputs')
|
||||
addrs = [e.addr for e in getattr(self, io_str)]
|
||||
if len(addrs) != len(set(addrs)):
|
||||
die(2,f'{addrs}: duplicate address in transaction {io_str}')
|
||||
die(2, f'{addrs}: duplicate address in transaction {io_str}')
|
||||
|
||||
# given tx size and absolute fee or fee spec, return absolute fee
|
||||
# relative fee is N+<first letter of unit name>
|
||||
def feespec2abs(self,fee_arg,tx_size):
|
||||
fee = get_obj(self.proto.coin_amt,num=fee_arg,silent=True)
|
||||
def feespec2abs(self, fee_arg, tx_size):
|
||||
fee = get_obj(self.proto.coin_amt, num=fee_arg, silent=True)
|
||||
if fee:
|
||||
return fee
|
||||
else:
|
||||
|
|
@ -114,66 +114,66 @@ class New(Base):
|
|||
units = {u[0]:u for u in self.proto.coin_amt.units}
|
||||
pat = re.compile(r'([1-9][0-9]*)({})'.format('|'.join(units)))
|
||||
if pat.match(fee_arg):
|
||||
amt,unit = pat.match(fee_arg).groups()
|
||||
return self.fee_rel2abs(tx_size,units,int(amt),unit)
|
||||
amt, unit = pat.match(fee_arg).groups()
|
||||
return self.fee_rel2abs(tx_size, units, int(amt), unit)
|
||||
return False
|
||||
|
||||
def get_usr_fee_interactive(self,fee=None,desc='Starting'):
|
||||
def get_usr_fee_interactive(self, fee=None, desc='Starting'):
|
||||
abs_fee = None
|
||||
from ..ui import line_input
|
||||
while True:
|
||||
if fee:
|
||||
abs_fee = self.convert_and_check_fee(fee,desc)
|
||||
abs_fee = self.convert_and_check_fee(fee, desc)
|
||||
if abs_fee:
|
||||
prompt = '{a} TX fee{b}: {c}{d} {e} ({f} {g})\n'.format(
|
||||
a = desc,
|
||||
b = (f' (after {self.cfg.fee_adjust:.2f}X adjustment)'
|
||||
a = desc,
|
||||
b = (f' (after {self.cfg.fee_adjust:.2f}X adjustment)'
|
||||
if self.cfg.fee_adjust != 1 and desc.startswith('Network-estimated')
|
||||
else ''),
|
||||
c = ('','≈')[self.fee_is_approximate],
|
||||
d = abs_fee.hl(),
|
||||
e = self.coin,
|
||||
f = pink(str(self.fee_abs2rel(abs_fee))),
|
||||
g = self.rel_fee_disp)
|
||||
c = ('', '≈')[self.fee_is_approximate],
|
||||
d = abs_fee.hl(),
|
||||
e = self.coin,
|
||||
f = pink(str(self.fee_abs2rel(abs_fee))),
|
||||
g = self.rel_fee_disp)
|
||||
from ..ui import keypress_confirm
|
||||
if self.cfg.yes or keypress_confirm( self.cfg, prompt+'OK?', default_yes=True ):
|
||||
if self.cfg.yes or keypress_confirm(self.cfg, prompt+'OK?', default_yes=True):
|
||||
if self.cfg.yes:
|
||||
msg(prompt)
|
||||
return abs_fee
|
||||
fee = line_input( self.cfg, self.usr_fee_prompt )
|
||||
fee = line_input(self.cfg, self.usr_fee_prompt)
|
||||
desc = 'User-selected'
|
||||
|
||||
# we don't know fee yet, so perform preliminary check with fee == 0
|
||||
async def precheck_sufficient_funds(self,inputs_sum,sel_unspent,outputs_sum):
|
||||
async def precheck_sufficient_funds(self, inputs_sum, sel_unspent, outputs_sum):
|
||||
if self.twuo.total < outputs_sum:
|
||||
msg(self.msg_wallet_low_coin.format(outputs_sum-inputs_sum,self.dcoin))
|
||||
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))
|
||||
msg(self.msg_low_coin.format(outputs_sum-inputs_sum, self.dcoin))
|
||||
return False
|
||||
return True
|
||||
|
||||
async def get_fee_from_user(self,have_estimate_fail=[]):
|
||||
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()
|
||||
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))
|
||||
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)
|
||||
start_fee = self.fee_est2abs(fee_per_kb, fe_type)
|
||||
|
||||
return self.get_usr_fee_interactive(start_fee,desc=desc)
|
||||
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))
|
||||
def add_output(self, coinaddr, amt, is_chg=None):
|
||||
self.outputs.append(self.Output(self.proto, addr=coinaddr, amt=amt, is_chg=is_chg))
|
||||
|
||||
def parse_cmd_arg(self, arg_in, ad_f, ad_w):
|
||||
|
||||
|
|
@ -195,7 +195,7 @@ class New(Base):
|
|||
|
||||
return _pa(arg, mmid, coin_addr, amt)
|
||||
|
||||
async def process_cmd_args(self,cmd_args,ad_f,ad_w):
|
||||
async def process_cmd_args(self, cmd_args, ad_f, ad_w):
|
||||
|
||||
async def get_autochg_addr(arg, parsed_args):
|
||||
from ..tw.addresses import TwAddresses
|
||||
|
|
@ -212,7 +212,7 @@ class New(Base):
|
|||
if res:
|
||||
return res
|
||||
|
||||
die(2,'Tracking wallet contains no {t}addresses {d} {a!r}'.format(
|
||||
die(2, 'Tracking wallet contains no {t}addresses {d} {a!r}'.format(
|
||||
t = '' if res is None else 'unused ',
|
||||
d = desc,
|
||||
a = arg))
|
||||
|
|
@ -245,8 +245,8 @@ class New(Base):
|
|||
if not self.outputs:
|
||||
die(2, 'At least one output must be specified on the command line')
|
||||
|
||||
async def get_outputs_from_cmdline(self,cmd_args):
|
||||
from ..addrdata import AddrData,TwAddrData
|
||||
async def get_outputs_from_cmdline(self, cmd_args):
|
||||
from ..addrdata import AddrData, TwAddrData
|
||||
from ..addrlist import AddrList
|
||||
from ..addrfile import AddrFile
|
||||
addrfiles = remove_dups(
|
||||
|
|
@ -264,13 +264,13 @@ class New(Base):
|
|||
from ..fileutil import check_infile
|
||||
for addrfile in addrfiles:
|
||||
check_infile(addrfile)
|
||||
ad_f.add(AddrList( self.cfg, self.proto, addrfile ))
|
||||
ad_f.add(AddrList(self.cfg, self.proto, addrfile))
|
||||
|
||||
ad_w = await TwAddrData(self.cfg,self.proto,twctl=self.twctl)
|
||||
ad_w = await TwAddrData(self.cfg, self.proto, twctl=self.twctl)
|
||||
|
||||
await self.process_cmd_args(cmd_args,ad_f,ad_w)
|
||||
await self.process_cmd_args(cmd_args, ad_f, ad_w)
|
||||
|
||||
self.add_mmaddrs_to_outputs(ad_w,ad_f)
|
||||
self.add_mmaddrs_to_outputs(ad_w, ad_f)
|
||||
self.check_dup_addrs('outputs')
|
||||
|
||||
if self.chg_output is not None:
|
||||
|
|
@ -279,19 +279,19 @@ class New(Base):
|
|||
elif len(self.outputs) > 1:
|
||||
await self.warn_chg_addr_used(self.chg_output)
|
||||
|
||||
def confirm_autoselected_addr(self,chg):
|
||||
def confirm_autoselected_addr(self, chg):
|
||||
from ..ui import keypress_confirm
|
||||
if not keypress_confirm(
|
||||
self.cfg,
|
||||
'Using {a} as {b} address. OK?'.format(
|
||||
a = chg.mmid.hl(),
|
||||
b = 'single output' if len(self.outputs) == 1 else 'change' ),
|
||||
default_yes = True ):
|
||||
die(1,'Exiting at user request')
|
||||
b = 'single output' if len(self.outputs) == 1 else 'change'),
|
||||
default_yes = True):
|
||||
die(1, 'Exiting at user request')
|
||||
|
||||
async def warn_chg_addr_used(self,chg):
|
||||
async def warn_chg_addr_used(self, chg):
|
||||
from ..tw.addresses import TwAddresses
|
||||
if (await TwAddresses(self.cfg,self.proto,get_data=True)).is_used(chg.addr):
|
||||
if (await TwAddresses(self.cfg, self.proto, get_data=True)).is_used(chg.addr):
|
||||
from ..ui import keypress_confirm
|
||||
if not keypress_confirm(
|
||||
self.cfg,
|
||||
|
|
@ -302,24 +302,24 @@ class New(Base):
|
|||
d = yellow('Address reuse harms your privacy and security. Continue anyway? (y/N): ')
|
||||
),
|
||||
complete_prompt = True,
|
||||
default_yes = False ):
|
||||
die(1,'Exiting at user request')
|
||||
default_yes = False):
|
||||
die(1, 'Exiting at user request')
|
||||
|
||||
# inputs methods
|
||||
def select_unspent(self,unspent):
|
||||
def select_unspent(self, unspent):
|
||||
prompt = 'Enter a range or space-separated list of outputs to spend: '
|
||||
from ..ui import line_input
|
||||
while True:
|
||||
reply = line_input( self.cfg, prompt ).strip()
|
||||
reply = line_input(self.cfg, prompt).strip()
|
||||
if reply:
|
||||
from ..addrlist import AddrIdxList
|
||||
selected = get_obj(AddrIdxList, fmt_str=','.join(reply.split()) )
|
||||
selected = get_obj(AddrIdxList, fmt_str=','.join(reply.split()))
|
||||
if selected:
|
||||
if selected[-1] <= len(unspent):
|
||||
return selected
|
||||
msg(f'Unspent output number must be <= {len(unspent)}')
|
||||
|
||||
def select_unspent_cmdline(self,unspent):
|
||||
def select_unspent_cmdline(self, unspent):
|
||||
|
||||
def idx2num(idx):
|
||||
uo = unspent[idx]
|
||||
|
|
@ -329,42 +329,42 @@ class New(Base):
|
|||
|
||||
def get_uo_nums():
|
||||
for addr in self.cfg.inputs.split(','):
|
||||
if is_mmgen_id(self.proto,addr):
|
||||
if is_mmgen_id(self.proto, addr):
|
||||
attr = 'twmmid'
|
||||
elif is_coin_addr(self.proto,addr):
|
||||
elif is_coin_addr(self.proto, addr):
|
||||
attr = 'addr'
|
||||
else:
|
||||
die(1,f'{addr!r}: not an MMGen ID or {self.coin} address')
|
||||
die(1, f'{addr!r}: not an MMGen ID or {self.coin} address')
|
||||
|
||||
found = False
|
||||
for idx,us in enumerate(unspent):
|
||||
if getattr(us,attr) == addr:
|
||||
for idx, us in enumerate(unspent):
|
||||
if getattr(us, attr) == addr:
|
||||
yield idx2num(idx)
|
||||
found = True
|
||||
|
||||
if not found:
|
||||
die(1,f'{addr!r}: address not found in tracking wallet')
|
||||
die(1, f'{addr!r}: address not found in tracking wallet')
|
||||
|
||||
return set(get_uo_nums()) # silently discard duplicates
|
||||
|
||||
def copy_inputs_from_tw(self,tw_unspent_data):
|
||||
def copy_inputs_from_tw(self, tw_unspent_data):
|
||||
def gen_inputs():
|
||||
for d in tw_unspent_data:
|
||||
i = self.Input(
|
||||
self.proto,
|
||||
**{attr:getattr(d,attr) for attr in d.__dict__ if attr in self.Input.tw_copy_attrs} )
|
||||
**{attr:getattr(d, attr) for attr in d.__dict__ if attr in self.Input.tw_copy_attrs})
|
||||
if d.twmmid.type == 'mmgen':
|
||||
i.mmid = d.twmmid # twmmid -> mmid
|
||||
yield i
|
||||
self.inputs = type(self.inputs)(self,list(gen_inputs()))
|
||||
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))
|
||||
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_left(self,fee,outputs_sum):
|
||||
async def get_funds_available(self, fee, outputs_sum):
|
||||
return self.sum_inputs() - outputs_sum - fee
|
||||
|
||||
async def get_inputs_from_user(self,outputs_sum):
|
||||
async def get_inputs_from_user(self, outputs_sum):
|
||||
|
||||
while True:
|
||||
us_f = self.select_unspent_cmdline if self.cfg.inputs else self.select_unspent
|
||||
|
|
@ -374,28 +374,28 @@ class New(Base):
|
|||
sel_unspent = MMGenList(self.twuo.data[i-1] for i in sel_nums)
|
||||
|
||||
inputs_sum = sum(s.amt for s in sel_unspent)
|
||||
if not await self.precheck_sufficient_funds(inputs_sum,sel_unspent,outputs_sum):
|
||||
if not await self.precheck_sufficient_funds(inputs_sum, sel_unspent, outputs_sum):
|
||||
continue
|
||||
|
||||
self.copy_inputs_from_tw(sel_unspent) # makes self.inputs
|
||||
|
||||
self.usr_fee = await self.get_fee_from_user()
|
||||
|
||||
funds_left = await self.get_funds_left(self.usr_fee,outputs_sum)
|
||||
funds_left = await self.get_funds_available(self.usr_fee,outputs_sum)
|
||||
|
||||
if funds_left >= 0:
|
||||
p = self.final_inputs_ok_msg(funds_left)
|
||||
from ..ui import keypress_confirm
|
||||
if self.cfg.yes or keypress_confirm( self.cfg, p+'. OK?', default_yes=True ):
|
||||
if self.cfg.yes or keypress_confirm(self.cfg, p+'. OK?', default_yes=True):
|
||||
if self.cfg.yes:
|
||||
msg(p)
|
||||
return funds_left
|
||||
else:
|
||||
self.warn_insufficient_funds(funds_left)
|
||||
|
||||
async def create(self,cmd_args,locktime=None,do_info=False,caller='txcreate'):
|
||||
async def create(self, cmd_args, locktime=None, do_info=False, caller='txcreate'):
|
||||
|
||||
assert isinstance( locktime, (int,type(None)) ), 'locktime must be of type int'
|
||||
assert isinstance(locktime, (int, type(None))), 'locktime must be of type int'
|
||||
|
||||
from ..tw.unspent import TwUnspentOutputs
|
||||
|
||||
|
|
@ -404,7 +404,7 @@ class New(Base):
|
|||
|
||||
twuo_addrs = await self.get_input_addrs_from_cmdline()
|
||||
|
||||
self.twuo = await TwUnspentOutputs(self.cfg,self.proto,minconf=self.cfg.minconf,addrs=twuo_addrs)
|
||||
self.twuo = await TwUnspentOutputs(self.cfg, self.proto, minconf=self.cfg.minconf, addrs=twuo_addrs)
|
||||
await self.twuo.get_data()
|
||||
|
||||
if not do_info:
|
||||
|
|
|
|||
|
|
@ -1067,8 +1067,8 @@ class CmdTestEthdev(CmdTestBase,CmdTestShared):
|
|||
usr_addrs[i],
|
||||
amt,
|
||||
dfl_devkey,
|
||||
start_gas = self.proto.coin_amt(60000,'wei'),
|
||||
gasPrice = self.proto.coin_amt(8,'Gwei') )
|
||||
start_gas = self.proto.coin_amt(60000, from_unit='wei'),
|
||||
gasPrice = self.proto.coin_amt(8, from_unit='Gwei'))
|
||||
if (await self.get_tx_receipt(txid)).status == 0:
|
||||
die(2,'Transfer of token funds failed. Aborting')
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue