tx, tw: whitespace, cleanups

This commit is contained in:
The MMGen Project 2024-10-18 10:32:03 +00:00
commit fc55750cd2
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
15 changed files with 219 additions and 220 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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