Browse Source

whitespace: proto.eth (plus cleanup)

The MMGen Project 4 months ago
parent
commit
6346c1d11a

+ 3 - 3
mmgen/proto/eth/addrdata.py

@@ -31,12 +31,12 @@ class EthereumTwAddrData(TwAddrData):
 		"""
 	}
 
-	async def get_tw_data(self,twctl=None):
+	async def get_tw_data(self, twctl=None):
 		from ...tw.ctl import TwCtl
 		self.cfg._util.vmsg('Getting address data from tracking wallet')
-		twctl = (twctl or await TwCtl(self.cfg,self.proto)).mmid_ordered_dict
+		twctl = (twctl or await TwCtl(self.cfg, self.proto)).mmid_ordered_dict
 		# emulate the output of RPC 'listaccounts' and 'getaddressesbyaccount'
-		return [(mmid+' '+d['comment'],[d['addr']]) for mmid,d in list(twctl.items())]
+		return [(mmid+' '+d['comment'], [d['addr']]) for mmid, d in list(twctl.items())]
 
 class EthereumTokenTwAddrData(EthereumTwAddrData):
 	pass

+ 36 - 36
mmgen/proto/eth/contract.py

@@ -24,46 +24,46 @@ from decimal import Decimal
 from . import rlp
 
 from . import erigon_sleep
-from ...util import msg,pp_msg,die
+from ...util import msg, pp_msg, die
 from ...base_obj import AsyncInit
-from ...obj import MMGenObject,CoinTxID
-from ...addr import CoinAddr,TokenAddr
+from ...obj import MMGenObject, CoinTxID
+from ...addr import CoinAddr, TokenAddr
 
 def parse_abi(s):
 	return [s[:8]] + [s[8+x*64:8+(x+1)*64] for x in range(len(s[8:])//64)]
 
 class TokenCommon(MMGenObject):
 
-	def create_method_id(self,sig):
+	def create_method_id(self, sig):
 		return self.keccak_256(sig.encode()).hexdigest()[:8]
 
-	def transferdata2sendaddr(self,data): # online
-		return CoinAddr(self.proto,parse_abi(data)[1][-40:])
+	def transferdata2sendaddr(self, data): # online
+		return CoinAddr(self.proto, parse_abi(data)[1][-40:])
 
-	def transferdata2amt(self,data): # online
+	def transferdata2amt(self, data): # online
 		return self.proto.coin_amt(
 			int(parse_abi(data)[-1], 16) * self.base_unit,
 			from_decimal = True)
 
-	async def do_call(self,method_sig,method_args='',toUnit=False):
+	async def do_call(self, method_sig, method_args='', toUnit=False):
 		data = self.create_method_id(method_sig) + method_args
 		if self.cfg.debug:
 			msg('ETH_CALL {}:  {}'.format(
 				method_sig,
-				'\n  '.join(parse_abi(data)) ))
-		ret = await self.rpc.call('eth_call',{ 'to': '0x'+self.addr, 'data': '0x'+data },'pending')
+				'\n  '.join(parse_abi(data))))
+		ret = await self.rpc.call('eth_call', {'to': '0x'+self.addr, 'data': '0x'+data}, 'pending')
 		await erigon_sleep(self)
 		if toUnit:
-			return int(ret,16) * self.base_unit
+			return int(ret, 16) * self.base_unit
 		else:
 			return ret
 
-	async def get_balance(self,acct_addr):
+	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),
 			from_decimal = True)
 
-	def strip(self,s):
+	def strip(self, s):
 		return ''.join([chr(b) for b in s if 32 <= b <= 127]).strip()
 
 	async def get_name(self):
@@ -76,13 +76,13 @@ class TokenCommon(MMGenObject):
 		ret = await self.do_call('decimals()')
 		try:
 			assert ret[:2] == '0x'
-			return int(ret,16)
+			return int(ret, 16)
 		except:
 			msg(f'RPC call to decimals() failed (returned {ret!r})')
 			return None
 
 	async def get_total_supply(self):
-		return await self.do_call('totalSupply()',toUnit=True)
+		return await self.do_call('totalSupply()', toUnit=True)
 
 	async def info(self):
 		return ('{:15}{}\n' * 5).format(
@@ -90,10 +90,10 @@ class TokenCommon(MMGenObject):
 			'token symbol:',  await self.get_symbol(),
 			'token name:',    await self.get_name(),
 			'decimals:',      self.decimals,
-			'total supply:',  await self.get_total_supply() )
+			'total supply:',  await self.get_total_supply())
 
 	async def code(self):
-		return (await self.rpc.call('eth_getCode','0x'+self.addr))[2:]
+		return (await self.rpc.call('eth_getCode', '0x'+self.addr))[2:]
 
 	def create_data(
 			self,
@@ -101,8 +101,8 @@ class TokenCommon(MMGenObject):
 			amt,
 			method_sig = 'transfer(address,uint256)'):
 		from_arg = ''
-		to_arg = to_addr.rjust(64,'0')
-		amt_arg = '{:064x}'.format( int(amt / self.base_unit) )
+		to_arg = to_addr.rjust(64, '0')
+		amt_arg = '{:064x}'.format(int(amt / self.base_unit))
 		return self.create_method_id(method_sig) + from_arg + to_arg + amt_arg
 
 	def make_tx_in(
@@ -125,24 +125,24 @@ class TokenCommon(MMGenObject):
 			'nonce':    nonce,
 			'data':     bytes.fromhex(data)}
 
-	async def txsign(self,tx_in,key,from_addr,chain_id=None):
+	async def txsign(self, tx_in, key, from_addr, chain_id=None):
 
 		from .pyethereum.transactions import Transaction
 
 		if chain_id is None:
 			res = await self.rpc.call('eth_chainId')
-			chain_id = None if res is None else int(res,16)
+			chain_id = None if res is None else int(res, 16)
 
-		tx = Transaction(**tx_in).sign(key,chain_id)
+		tx = Transaction(**tx_in).sign(key, chain_id)
 
 		if tx.sender.hex() != from_addr:
-			die(3,f'Sender address {from_addr!r} does not match address of key {tx.sender.hex()!r}!')
+			die(3, f'Sender address {from_addr!r} does not match address of key {tx.sender.hex()!r}!')
 
 		if self.cfg.debug:
 			msg('TOKEN DATA:')
 			pp_msg(tx.to_dict())
 			msg('PARSED ABI DATA:\n  {}'.format(
-				'\n  '.join(parse_abi(tx.data.hex())) ))
+				'\n  '.join(parse_abi(tx.data.hex()))))
 
 		return (
 			rlp.encode(tx).hex(),
@@ -151,8 +151,8 @@ class TokenCommon(MMGenObject):
 
 # The following are used for token deployment only:
 
-	async def txsend(self,txhex):
-		return (await self.rpc.call('eth_sendRawTransaction','0x'+txhex)).replace('0x','',1)
+	async def txsend(self, txhex):
+		return (await self.rpc.call('eth_sendRawTransaction', '0x'+txhex)).replace('0x', '', 1)
 
 	async def transfer(
 			self,
@@ -168,35 +168,35 @@ class TokenCommon(MMGenObject):
 				amt,
 				start_gas,
 				gasPrice,
-				nonce = int(await self.rpc.call('eth_getTransactionCount','0x'+from_addr,'pending'),16),
+				nonce = int(await self.rpc.call('eth_getTransactionCount', '0x'+from_addr, 'pending'), 16),
 				method_sig = method_sig)
-		txhex,_ = await self.txsign(tx_in,key,from_addr)
+		txhex, _ = await self.txsign(tx_in, key, from_addr)
 		return await self.txsend(txhex)
 
 class Token(TokenCommon):
 
-	def __init__(self,cfg,proto,addr,decimals,rpc=None):
+	def __init__(self, cfg, proto, addr, decimals, rpc=None):
 		if type(self).__name__ == 'Token':
 			from ...util2 import get_keccak
 			self.keccak_256 = get_keccak(cfg)
 		self.cfg = cfg
 		self.proto = proto
-		self.addr = TokenAddr(proto,addr)
-		assert isinstance(decimals,int),f'decimals param must be int instance, not {type(decimals)}'
+		self.addr = TokenAddr(proto, addr)
+		assert isinstance(decimals, int), f'decimals param must be int instance, not {type(decimals)}'
 		self.decimals = decimals
 		self.base_unit = Decimal('10') ** -self.decimals
 		self.rpc = rpc
 
-class ResolvedToken(TokenCommon,metaclass=AsyncInit):
+class ResolvedToken(TokenCommon, metaclass=AsyncInit):
 
-	async def __init__(self,cfg,proto,rpc,addr):
+	async def __init__(self, cfg, proto, rpc, addr):
 		from ...util2 import get_keccak
 		self.keccak_256 = get_keccak(cfg)
 		self.cfg = cfg
 		self.proto = proto
 		self.rpc = rpc
-		self.addr = TokenAddr(proto,addr)
+		self.addr = TokenAddr(proto, addr)
 		decimals = await self.get_decimals() # requires self.addr!
 		if not decimals:
-			die( 'TokenNotInBlockchain', f'Token {addr!r} not in blockchain' )
-		Token.__init__(self,cfg,proto,addr,decimals,rpc)
+			die('TokenNotInBlockchain', f'Token {addr!r} not in blockchain')
+		Token.__init__(self, cfg, proto, addr, decimals, rpc)

+ 31 - 31
mmgen/proto/eth/daemon.py

@@ -15,29 +15,29 @@ proto.eth.daemon: Ethereum base protocol daemon classes
 import os
 
 from ...cfg import gc
-from ...util import list_gen,get_subclasses
-from ...daemon import CoinDaemon,RPCDaemon,_nw,_dd
+from ...util import list_gen, get_subclasses
+from ...daemon import CoinDaemon, RPCDaemon, _nw, _dd
 
 class ethereum_daemon(CoinDaemon):
-	chain_subdirs = _nw('ethereum','goerli','DevelopmentChain')
+	chain_subdirs = _nw('ethereum', 'goerli', 'DevelopmentChain')
 	base_rpc_port = 8545  # same for all networks!
 	base_authrpc_port = 8551 # same for all networks!
 	base_p2p_port = 30303 # same for all networks!
 	daemon_port_offset = 100
-	network_port_offsets = _nw(0,10,20)
+	network_port_offsets = _nw(0, 10, 20)
 
-	def __init__(self,*args,test_suite=False,**kwargs):
+	def __init__(self, *args, test_suite=False, **kwargs):
 
-		if not hasattr(self,'all_daemons'):
-			ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon,names=True)
+		if not hasattr(self, 'all_daemons'):
+			ethereum_daemon.all_daemons = get_subclasses(ethereum_daemon, names=True)
 
 		daemon_idx_offset = (
 			self.all_daemons.index(self.id+'_daemon') * self.daemon_port_offset
-			if test_suite else 0 )
+			if test_suite else 0)
 
-		self.port_offset = daemon_idx_offset + getattr(self.network_port_offsets,self.network)
+		self.port_offset = daemon_idx_offset + getattr(self.network_port_offsets, self.network)
 
-		super().__init__( *args, test_suite=test_suite, **kwargs )
+		super().__init__(*args, test_suite=test_suite, **kwargs)
 
 	def get_rpc_port(self):
 		return self.base_rpc_port + self.port_offset
@@ -54,7 +54,7 @@ class ethereum_daemon(CoinDaemon):
 		return os.path.join(
 			self.logdir,
 			self.id,
-			getattr(self.chain_subdirs,self.network) )
+			getattr(self.chain_subdirs, self.network))
 
 class openethereum_daemon(ethereum_daemon):
 	daemon_data = _dd('OpenEthereum', 3003005, '3.3.5')
@@ -62,9 +62,9 @@ class openethereum_daemon(ethereum_daemon):
 	exec_fn = 'openethereum'
 	cfg_file = 'parity.conf'
 	datadirs = {
-		'linux': [gc.home_dir,'.local','share','io.parity.ethereum'],
+		'linux': [gc.home_dir, '.local', 'share', 'io.parity.ethereum'],
 		'darwin': [gc.home_dir, 'Library', 'Application Support', 'io.parity.ethereum'],
-		'win32': [os.getenv('LOCALAPPDATA'),'Parity','Ethereum']
+		'win32': [os.getenv('LOCALAPPDATA'), 'Parity', 'Ethereum']
 	}
 
 	def init_subclass(self):
@@ -102,20 +102,20 @@ class geth_daemon(ethereum_daemon):
 	exec_fn = 'geth'
 	use_pidfile = False
 	use_threads = True
-	avail_opts = ('no_daemonize','online')
+	avail_opts = ('no_daemonize', 'online')
 	version_info_arg = 'version'
 	datadirs = {
-		'linux': [gc.home_dir,'.ethereum','geth'],
+		'linux': [gc.home_dir, '.ethereum', 'geth'],
 		'darwin': [gc.home_dir, 'Library', 'Ethereum', 'geth'],
-		'win32': [os.getenv('LOCALAPPDATA'),'Geth'] # FIXME
+		'win32': [os.getenv('LOCALAPPDATA'), 'Geth'] # FIXME
 	}
 
 	def init_subclass(self):
 
 		def have_authrpc():
-			from subprocess import run,PIPE
+			from subprocess import run, PIPE
 			try:
-				return b'authrpc' in run(['geth','help'],check=True,stdout=PIPE).stdout
+				return b'authrpc' in run(['geth', 'help'], check=True, stdout=PIPE).stdout
 			except:
 				return False
 
@@ -138,12 +138,12 @@ class erigon_daemon(geth_daemon):
 	daemon_data = _dd('Erigon', 2022099099, '2022.99.99')
 	version_pat = r'erigon/(\d+)\.(\d+)\.(\d+)'
 	exec_fn = 'erigon'
-	private_ports = _nw(9090,9091,9092) # testnet and regtest are non-standard
-	torrent_ports = _nw(42069,42070,None) # testnet is non-standard
+	private_ports = _nw(9090, 9091, 9092) # testnet and regtest are non-standard
+	torrent_ports = _nw(42069, 42070, None) # testnet is non-standard
 	version_info_arg = '--version'
 	datadirs = {
-		'linux': [gc.home_dir,'.local','share','erigon'],
-		'win32': [os.getenv('LOCALAPPDATA'),'Erigon'] # FIXME
+		'linux': [gc.home_dir, '.local', 'share', 'erigon'],
+		'win32': [os.getenv('LOCALAPPDATA'), 'Erigon'] # FIXME
 	}
 
 	def init_subclass(self):
@@ -169,21 +169,21 @@ class erigon_daemon(geth_daemon):
 			rpc_port     = self.rpc_port,
 			private_port = self.private_port,
 			test_suite   = self.test_suite,
-			datadir      = self.datadir )
+			datadir      = self.datadir)
 
-	def start(self,quiet=False,silent=False):
-		super().start(quiet=quiet,silent=silent)
+	def start(self, quiet=False, silent=False):
+		super().start(quiet=quiet, silent=silent)
 		self.rpc_d.debug = self.debug
-		return self.rpc_d.start(quiet=quiet,silent=silent)
+		return self.rpc_d.start(quiet=quiet, silent=silent)
 
-	def stop(self,quiet=False,silent=False):
+	def stop(self, quiet=False, silent=False):
 		self.rpc_d.debug = self.debug
-		self.rpc_d.stop(quiet=quiet,silent=silent)
-		return super().stop(quiet=quiet,silent=silent)
+		self.rpc_d.stop(quiet=quiet, silent=silent)
+		return super().stop(quiet=quiet, silent=silent)
 
 	@property
 	def start_cmds(self):
-		return [self.start_cmd,self.rpc_d.start_cmd]
+		return [self.start_cmd, self.rpc_d.start_cmd]
 
 class erigon_rpcdaemon(RPCDaemon):
 
@@ -193,7 +193,7 @@ class erigon_rpcdaemon(RPCDaemon):
 	use_pidfile = False
 	use_threads = True
 
-	def __init__(self,cfg,proto,rpc_port,private_port,test_suite,datadir):
+	def __init__(self, cfg, proto, rpc_port, private_port, test_suite, datadir):
 
 		self.proto = proto
 		self.test_suite = test_suite

+ 11 - 10
mmgen/proto/eth/misc.py

@@ -14,7 +14,7 @@ proto.eth.misc: miscellaneous utilities for Ethereum base protocol
 
 from ...util2 import get_keccak
 
-def decrypt_geth_keystore(cfg,wallet_fn,passwd,check_addr=True):
+def decrypt_geth_keystore(cfg, wallet_fn, passwd, check_addr=True):
 	"""
 	Decrypt the encrypted private key in a Geth keystore wallet, returning the decrypted key
 	"""
@@ -33,32 +33,32 @@ def decrypt_geth_keystore(cfg,wallet_fn,passwd,check_addr=True):
 	if check_addr:
 		from ...tool.coin import tool_cmd
 		from ...protocol import init_proto
-		t = tool_cmd( cfg=cfg, proto=init_proto(cfg,'eth') )
+		t = tool_cmd(cfg=cfg, proto=init_proto(cfg, 'eth'))
 		addr = t.wif2addr(key.hex())
 		addr_chk = wallet_data['address']
 		assert addr == addr_chk, f'incorrect address: ({addr} != {addr_chk})'
 
 	return key
 
-def hash_message(cfg,message,msghash_type):
+def hash_message(cfg, message, msghash_type):
 	return get_keccak(cfg)(
 		{
 			'raw': message,
-			'eth_sign': '\x19Ethereum Signed Message:\n{}{}'.format( len(message), message ),
+			'eth_sign': '\x19Ethereum Signed Message:\n{}{}'.format(len(message), message),
 		}[msghash_type].encode()
 	).digest()
 
-def ec_sign_message_with_privkey(cfg,message,key,msghash_type):
+def ec_sign_message_with_privkey(cfg, message, key, msghash_type):
 	"""
 	Sign an arbitrary string with an Ethereum private key, returning the signature
 
 	Conforms to the standard defined by the Geth `eth_sign` JSON-RPC call
 	"""
 	from py_ecc.secp256k1 import ecdsa_raw_sign
-	v,r,s = ecdsa_raw_sign( hash_message(cfg,message,msghash_type), key )
-	return '{:064x}{:064x}{:02x}'.format(r,s,v)
+	v, r, s = ecdsa_raw_sign(hash_message(cfg, message, msghash_type), key)
+	return '{:064x}{:064x}{:02x}'.format(r, s, v)
 
-def ec_recover_pubkey(cfg,message,sig,msghash_type):
+def ec_recover_pubkey(cfg, message, sig, msghash_type):
 	"""
 	Given a message and signature, recover the public key associated with the private key
 	used to make the signature
@@ -66,7 +66,8 @@ def ec_recover_pubkey(cfg,message,sig,msghash_type):
 	Conforms to the standard defined by the Geth `eth_sign` JSON-RPC call
 	"""
 	from py_ecc.secp256k1 import ecdsa_raw_recover
-	r,s,v = ( sig[:64], sig[64:128], sig[128:] )
+	r, s, v = (sig[:64], sig[64:128], sig[128:])
 	return '{:064x}{:064x}'.format(
-		*ecdsa_raw_recover( hash_message(cfg,message,msghash_type), tuple(int(hexstr,16) for hexstr in (v,r,s)) )
+		*ecdsa_raw_recover(
+			hash_message(cfg, message, msghash_type), tuple(int(hexstr, 16) for hexstr in (v, r, s)))
 	)

+ 6 - 6
mmgen/proto/eth/msg.py

@@ -18,23 +18,23 @@ class coin_msg(coin_msg):
 
 	include_pubhash = False
 	sigdata_pfx = '0x'
-	msghash_types = ('eth_sign','raw') # first-listed is the default
+	msghash_types = ('eth_sign', 'raw') # first-listed is the default
 
 	class unsigned(coin_msg.unsigned):
 
-		async def do_sign(self,wif,message,msghash_type):
+		async def do_sign(self, wif, message, msghash_type):
 			from .misc import ec_sign_message_with_privkey
-			return ec_sign_message_with_privkey( self.cfg, message, bytes.fromhex(wif), msghash_type )
+			return ec_sign_message_with_privkey(self.cfg, message, bytes.fromhex(wif), msghash_type)
 
 	class signed_online(coin_msg.signed_online):
 
-		async def do_verify(self,addr,sig,message,msghash_type):
+		async def do_verify(self, addr, sig, message, msghash_type):
 			from ...tool.coin import tool_cmd
 			from .misc import ec_recover_pubkey
 			return tool_cmd(
 				self.cfg,
 				proto = self.proto).pubhex2addr(
-					ec_recover_pubkey( self.cfg, message, sig, msghash_type )) == addr
+					ec_recover_pubkey(self.cfg, message, sig, msghash_type)) == addr
 
-	class exported_sigs(coin_msg.exported_sigs,signed_online):
+	class exported_sigs(coin_msg.exported_sigs, signed_online):
 		pass

+ 8 - 8
mmgen/proto/eth/params.py

@@ -12,13 +12,13 @@
 proto.eth.params: Ethereum protocol
 """
 
-from ...protocol import CoinProtocol,_nw,decoded_addr
+from ...protocol import CoinProtocol, _nw, decoded_addr
 from ...addr import CoinAddr
-from ...util import is_hex_str_lc,Msg
+from ...util import is_hex_str_lc, Msg
 
-class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1):
+class mainnet(CoinProtocol.DummyWIF, CoinProtocol.Secp256k1):
 
-	network_names = _nw('mainnet','testnet','devnet')
+	network_names = _nw('mainnet', 'testnet', 'devnet')
 	addr_len      = 20
 	mmtypes       = ('E',)
 	dfl_mmtype    = 'E'
@@ -27,7 +27,7 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1):
 
 	coin_amt      = 'ETHAmt'
 	max_tx_fee    = '0.005'
-	chain_names   = ['ethereum','foundation']
+	chain_names   = ['ethereum', 'foundation']
 	sign_mode     = 'standalone'
 	caps          = ('token',)
 	mmcaps        = ('rpc', 'rpc_init', 'tw', 'msg')
@@ -63,9 +63,9 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1):
 			Msg(f'Invalid address: {addr}')
 		return False
 
-	def checksummed_addr(self,addr):
+	def checksummed_addr(self, addr):
 		h = self.keccak_256(addr.encode()).digest().hex()
-		return ''.join(addr[i].upper() if int(h[i],16) > 7 else addr[i] for i in range(len(addr)))
+		return ''.join(addr[i].upper() if int(h[i], 16) > 7 else addr[i] for i in range(len(addr)))
 
 	def pubhash2addr(self, pubhash, addr_type):
 		assert len(pubhash) == 20, f'{len(pubhash)}: invalid length for {self.name} pubkey hash'
@@ -74,7 +74,7 @@ class mainnet(CoinProtocol.DummyWIF,CoinProtocol.Secp256k1):
 		return CoinAddr(self, pubhash.hex())
 
 class testnet(mainnet):
-	chain_names = ['kovan','goerli','rinkeby']
+	chain_names = ['kovan', 'goerli', 'rinkeby']
 
 class regtest(testnet):
 	chain_names = ['developmentchain']

+ 19 - 19
mmgen/proto/eth/rpc.py

@@ -16,7 +16,7 @@ import re
 
 from ...base_obj import AsyncInit
 from ...obj import Int
-from ...util import die,fmt,oneshot_warning_group
+from ...util import die, fmt, oneshot_warning_group
 from ...rpc import RPCClient
 
 class daemon_warning(oneshot_warning_group):
@@ -32,7 +32,7 @@ class daemon_warning(oneshot_warning_group):
 class CallSigs:
 	pass
 
-class EthereumRPCClient(RPCClient,metaclass=AsyncInit):
+class EthereumRPCClient(RPCClient, metaclass=AsyncInit):
 
 	async def __init__(
 			self,
@@ -44,50 +44,50 @@ class EthereumRPCClient(RPCClient,metaclass=AsyncInit):
 
 		self.proto = proto
 		self.daemon = daemon
-		self.call_sigs = getattr(CallSigs,daemon.id,None)
+		self.call_sigs = getattr(CallSigs, daemon.id, None)
 
 		super().__init__(
 			cfg  = cfg,
 			host = 'localhost' if cfg.test_suite else (cfg.rpc_host or 'localhost'),
-			port = daemon.rpc_port )
+			port = daemon.rpc_port)
 
 		await self.set_backend_async(backend)
 
-		vi,bh,ci = await self.gathered_call(None, (
-				('web3_clientVersion',()),
-				('eth_getBlockByNumber',('latest',False)),
-				('eth_chainId',()),
+		vi, bh, ci = await self.gathered_call(None, (
+				('web3_clientVersion', ()),
+				('eth_getBlockByNumber', ('latest', False)),
+				('eth_chainId', ()),
 			))
 
-		vip = re.match(self.daemon.version_pat,vi,re.ASCII)
+		vip = re.match(self.daemon.version_pat, vi, re.ASCII)
 		if not vip:
-			die(2,fmt(f"""
+			die(2, fmt(f"""
 			Aborting on daemon mismatch:
 			  Requested daemon: {self.daemon.id}
 			  Running daemon:   {vi}
-			""",strip_char='\t').rstrip())
+			""", strip_char='\t').rstrip())
 
 		self.daemon_version = int('{:d}{:03d}{:03d}'.format(*[int(e) for e in vip.groups()]))
 		self.daemon_version_str = '{}.{}.{}'.format(*vip.groups())
 		self.daemon_version_info = vi
 
-		self.blockcount = int(bh['number'],16)
-		self.cur_date = int(bh['timestamp'],16)
+		self.blockcount = int(bh['number'], 16)
+		self.cur_date = int(bh['timestamp'], 16)
 
 		self.caps = ()
-		if self.daemon.id in ('parity','openethereum'):
+		if self.daemon.id in ('parity', 'openethereum'):
 			if (await self.call('parity_nodeKind'))['capability'] == 'full':
 				self.caps += ('full_node',)
-			self.chainID = None if ci is None else Int(ci,16) # parity/oe return chainID only for dev chain
-			self.chain = (await self.call('parity_chain')).replace(' ','_').replace('_testnet','')
-		elif self.daemon.id in ('geth','erigon'):
+			self.chainID = None if ci is None else Int(ci, 16) # parity/oe return chainID only for dev chain
+			self.chain = (await self.call('parity_chain')).replace(' ', '_').replace('_testnet', '')
+		elif self.daemon.id in ('geth', 'erigon'):
 			if self.daemon.network == 'mainnet':
 				daemon_warning(self.daemon.id)
 			self.caps += ('full_node',)
-			self.chainID = Int(ci,16)
+			self.chainID = Int(ci, 16)
 			self.chain = self.proto.chain_ids[self.chainID]
 
-	def make_host_path(self,wallet):
+	def make_host_path(self, wallet):
 		return ''
 
 	rpcmethods = (

+ 4 - 4
mmgen/proto/eth/tw/addresses.py

@@ -16,7 +16,7 @@ from ....tw.addresses import TwAddresses
 from .view import EthereumTwView
 from .rpc import EthereumTwRPC
 
-class EthereumTwAddresses(TwAddresses,EthereumTwView,EthereumTwRPC):
+class EthereumTwAddresses(TwAddresses, EthereumTwView, EthereumTwRPC):
 
 	has_age = False
 	prompt_fs_in = [
@@ -35,13 +35,13 @@ class EthereumTwAddresses(TwAddresses,EthereumTwView,EthereumTwRPC):
 		'D':'i_addr_delete',
 		'v':'a_view',
 		'w':'a_view_detail',
-		'p':'a_print_detail' }
+		'p':'a_print_detail'}
 
-	def get_column_widths(self,data,wide,interactive):
+	def get_column_widths(self, data, wide, interactive):
 
 		return self.compute_column_widths(
 			widths = { # fixed cols
-				'num':  max(2,len(str(len(data)))+1),
+				'num':  max(2, len(str(len(data)))+1),
 				'mmid': max(len(d.twmmid.disp) for d in data),
 				'used': 0,
 				'amt':  self.amt_widths['amt'],

+ 4 - 4
mmgen/proto/eth/tw/bal.py

@@ -25,14 +25,14 @@ from ....tw.bal import TwGetBalance
 
 class EthereumTwGetBalance(TwGetBalance):
 
-	start_labels = ('TOTAL','Non-MMGen')
+	start_labels = ('TOTAL', 'Non-MMGen')
 	conf_cols = {
 		'ge_minconf': 'Balance',
 	}
 
-	async def __init__(self,cfg,proto,*args,**kwargs):
-		self.twctl = await TwCtl(cfg,proto,mode='w')
-		await super().__init__(cfg,proto,*args,**kwargs)
+	async def __init__(self, cfg, proto, *args, **kwargs):
+		self.twctl = await TwCtl(cfg, proto, mode='w')
+		await super().__init__(cfg, proto, *args, **kwargs)
 
 	async def create_data(self):
 		in_data = self.twctl.mmid_ordered_dict

+ 41 - 41
mmgen/proto/eth/tw/ctl.py

@@ -20,11 +20,11 @@
 proto.eth.tw.ctl: Ethereum tracking wallet control class
 """
 
-from ....util import msg,ymsg,die
+from ....util import msg, ymsg, die
 from ....tw.ctl import TwCtl, write_mode, label_addr_pair
 from ....tw.shared import TwLabel
-from ....addr import is_coin_addr,is_mmgen_id,CoinAddr
-from ..contract import Token,ResolvedToken
+from ....addr import is_coin_addr, is_mmgen_id, CoinAddr
+from ..contract import Token, ResolvedToken
 
 class EthereumTwCtl(TwCtl):
 
@@ -81,43 +81,43 @@ class EthereumTwCtl(TwCtl):
 			self.force_write()
 			msg(f'{self.desc} upgraded successfully!')
 
-	async def rpc_get_balance(self,addr):
+	async def rpc_get_balance(self, addr):
 		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):
+	async def batch_import_address(self, args_list):
 		return [await self.import_address(*a) for a in args_list]
 
-	async def rescan_addresses(self,coin_addrs):
+	async def rescan_addresses(self, coin_addrs):
 		pass
 
 	@write_mode
-	async def import_address(self,addr,label,rescan=False):
+	async def import_address(self, addr, label, rescan=False):
 		r = self.data_root
 		if addr in r:
 			if not r[addr]['mmid'] and label.mmid:
 				msg(f'Warning: MMGen ID {label.mmid!r} was missing in tracking wallet!')
 			elif r[addr]['mmid'] != label.mmid:
-				die(3,'MMGen ID {label.mmid!r} does not match tracking wallet!')
-		r[addr] = { 'mmid': label.mmid, 'comment': label.comment }
+				die(3, 'MMGen ID {label.mmid!r} does not match tracking wallet!')
+		r[addr] = {'mmid': label.mmid, 'comment': label.comment}
 
 	@write_mode
-	async def remove_address(self,addr):
+	async def remove_address(self, addr):
 		r = self.data_root
 
-		if is_coin_addr(self.proto,addr):
+		if is_coin_addr(self.proto, addr):
 			have_match = lambda k: k == addr
-		elif is_mmgen_id(self.proto,addr):
+		elif is_mmgen_id(self.proto, addr):
 			have_match = lambda k: r[k]['mmid'] == addr
 		else:
-			die(1,f'{addr!r} is not an Ethereum address or MMGen ID')
+			die(1, f'{addr!r} is not an Ethereum address or MMGen ID')
 
 		for k in r:
 			if have_match(k):
 				# return the addr resolved to mmid if possible
-				ret = r[k]['mmid'] if is_mmgen_id(self.proto,r[k]['mmid']) else addr
+				ret = r[k]['mmid'] if is_mmgen_id(self.proto, r[k]['mmid']) else addr
 				del r[k]
 				self.write()
 				return ret
@@ -125,8 +125,8 @@ class EthereumTwCtl(TwCtl):
 		return None
 
 	@write_mode
-	async def set_label(self,coinaddr,lbl):
-		for addr,d in list(self.data_root.items()):
+	async def set_label(self, coinaddr, lbl):
+		for addr, d in list(self.data_root.items()):
 			if addr == coinaddr:
 				d['comment'] = lbl.comment
 				self.write()
@@ -134,32 +134,32 @@ class EthereumTwCtl(TwCtl):
 		msg(f'Address {coinaddr!r} not found in {self.data_root_desc!r} section of tracking wallet')
 		return False
 
-	async def addr2sym(self,req_addr):
+	async def addr2sym(self, req_addr):
 		for addr in self.data['tokens']:
 			if addr == req_addr:
 				return self.data['tokens'][addr]['params']['symbol']
 
-	async def sym2addr(self,sym):
+	async def sym2addr(self, sym):
 		for addr in self.data['tokens']:
 			if self.data['tokens'][addr]['params']['symbol'] == sym.upper():
 				return addr
 
-	def get_token_param(self,token,param):
+	def get_token_param(self, token, param):
 		if token in self.data['tokens']:
 			return self.data['tokens'][token]['params'].get(param)
 
 	@property
 	def sorted_list(self):
-		return sorted(
-			[ { 'addr':x[0],
-				'mmid':x[1]['mmid'],
-				'comment':x[1]['comment'] }
-					for x in self.data_root.items() if x[0] not in ('params','coin') ],
-			key=lambda x: x['mmid'].sort_key+x['addr'] )
+		return sorted([{
+				'addr':    x[0],
+				'mmid':    x[1]['mmid'],
+				'comment': x[1]['comment']
+			} for x in self.data_root.items() if x[0] not in ('params', 'coin')],
+			key = lambda x: x['mmid'].sort_key + x['addr'])
 
 	@property
 	def mmid_ordered_dict(self):
-		return dict((x['mmid'],{'addr':x['addr'],'comment':x['comment']}) for x in self.sorted_list)
+		return dict((x['mmid'], {'addr': x['addr'], 'comment': x['comment']}) for x in self.sorted_list)
 
 	async def get_label_addr_pairs(self):
 		return [label_addr_pair(
@@ -182,22 +182,22 @@ class EthereumTokenTwCtl(EthereumTwCtl):
 			self.conv_types(v)
 
 		if self.importing and token_addr:
-			if not is_coin_addr(proto,token_addr):
-				die( 'InvalidTokenAddress', f'{token_addr!r}: invalid token address' )
+			if not is_coin_addr(proto, token_addr):
+				die('InvalidTokenAddress', f'{token_addr!r}: invalid token address')
 		else:
-			assert token_addr is None,'EthereumTokenTwCtl_chk1'
+			assert token_addr is None, 'EthereumTokenTwCtl_chk1'
 			token_addr = await self.sym2addr(proto.tokensym) # returns None on failure
-			if not is_coin_addr(proto,token_addr):
-				die( 'UnrecognizedTokenSymbol', f'Specified token {proto.tokensym!r} could not be resolved!' )
+			if not is_coin_addr(proto, token_addr):
+				die('UnrecognizedTokenSymbol', f'Specified token {proto.tokensym!r} could not be resolved!')
 
 		from ....addr import TokenAddr
-		self.token = TokenAddr(proto,token_addr)
+		self.token = TokenAddr(proto, token_addr)
 
 		if self.token not in self.data['tokens']:
 			if self.importing:
 				await self.import_token(self.token)
 			else:
-				die( 'TokenNotInWallet', f'Specified token {self.token!r} not in wallet!' )
+				die('TokenNotInWallet', f'Specified token {self.token!r} not in wallet!')
 
 		self.decimals = self.get_param('decimals')
 		self.symbol   = self.get_param('symbol')
@@ -212,29 +212,29 @@ class EthereumTokenTwCtl(EthereumTwCtl):
 	def data_root_desc(self):
 		return 'token ' + self.get_param('symbol')
 
-	async def rpc_get_balance(self,addr):
-		return await Token(self.cfg,self.proto,self.token,self.decimals,self.rpc).get_balance(addr)
+	async def rpc_get_balance(self, addr):
+		return await Token(self.cfg, self.proto, self.token, self.decimals, self.rpc).get_balance(addr)
 
-	async def get_eth_balance(self,addr,force_rpc=False):
+	async def get_eth_balance(self, addr, force_rpc=False):
 		cache = self.cur_eth_balances
 		r = self.data['accounts']
-		ret = None if force_rpc else self.get_cached_balance(addr,cache,r)
+		ret = None if force_rpc else self.get_cached_balance(addr, cache, r)
 		if ret is None:
 			ret = await super().rpc_get_balance(addr)
-			self.cache_balance(addr,ret,cache,r)
+			self.cache_balance(addr, ret, cache, r)
 		return ret
 
-	def get_param(self,param):
+	def get_param(self, param):
 		return self.data['tokens'][self.token]['params'][param]
 
 	@write_mode
-	async def import_token(self,tokenaddr):
+	async def import_token(self, tokenaddr):
 		"""
 		Token 'symbol' and 'decimals' values are resolved from the network by the system just
 		once, upon token import.  Thereafter, token address, symbol and decimals are resolved
 		either from the tracking wallet (online operations) or transaction file (when signing).
 		"""
-		t = await ResolvedToken(self.cfg,self.proto,self.rpc,tokenaddr)
+		t = await ResolvedToken(self.cfg, self.proto, self.rpc, tokenaddr)
 		self.data['tokens'][tokenaddr] = {
 			'params': {
 				'symbol': await t.get_symbol(),

+ 28 - 28
mmgen/proto/eth/tw/json.py

@@ -20,30 +20,30 @@ class EthereumTwJSON(TwJSON):
 
 	class Base(TwJSON.Base):
 
-		def __init__(self,proto,*args,**kwargs):
+		def __init__(self, proto, *args, **kwargs):
 
-			self.params_keys = ['symbol','decimals']
-			self.params_tuple = namedtuple('params_tuple',self.params_keys)
+			self.params_keys = ['symbol', 'decimals']
+			self.params_tuple = namedtuple('params_tuple', self.params_keys)
 
-			super().__init__(proto,*args,**kwargs)
+			super().__init__(proto, *args, **kwargs)
 
 		@property
 		def mappings_json(self):
 
 			def gen_mappings(data):
 				for d in data:
-					yield (d.mmgen_id,d.address) if hasattr(d,'mmgen_id') else d
+					yield (d.mmgen_id, d.address) if hasattr(d, 'mmgen_id') else d
 
 			return self.json_dump({
 				'accounts': list(gen_mappings(self.entries['accounts'])),
-				'tokens': {k:list(gen_mappings(v)) for k,v in self.entries['tokens'].items()}
+				'tokens': {k:list(gen_mappings(v)) for k, v in self.entries['tokens'].items()}
 			})
 
 		@property
 		def num_entries(self):
 			return len(self.entries['accounts']) + len(self.entries['tokens'])
 
-	class Import(TwJSON.Import,Base):
+	class Import(TwJSON.Import, Base):
 
 		info_msg = """
 			This utility will recreate a new tracking wallet from the supplied JSON dump.
@@ -68,72 +68,72 @@ class EthereumTwJSON(TwJSON):
 					else:
 						e = self.entry_tuple_in(*d)
 						yield self.entry_tuple(
-							TwMMGenID(self.proto,e.mmgen_id),
+							TwMMGenID(self.proto, e.mmgen_id),
 							e.address,
-							getattr(e,'amount','0'),
-							e.comment )
+							getattr(e, 'amount', '0'),
+							e.comment)
 
 			def gen_token_entries():
-				for token_addr,token_data in edata['tokens'].items():
+				for token_addr, token_data in edata['tokens'].items():
 					yield (
 						token_addr,
 						list(gen_entries(token_data)),
 					)
 
 			return {
-				'accounts': list(gen_entries( edata['accounts'] )),
+				'accounts': list(gen_entries(edata['accounts'])),
 				'tokens': dict(list(gen_token_entries()))
 			}
 
-		async def do_import(self,batch):
+		async def do_import(self, batch):
 
 			from ....obj import TwComment
 
 			def gen_data(data):
 				for d in data:
-					if hasattr(d,'address'):
-						if d.amount is None:
-							yield (d.address, {'mmid':d.mmgen_id,'comment':TwComment(d.comment)})
-						else:
-							yield (d.address, {'mmid':d.mmgen_id,'comment':TwComment(d.comment),'balance':d.amount})
+					if hasattr(d, 'address'):
+						yield (
+							d.address,
+							{'mmid': d.mmgen_id, 'comment': TwComment(d.comment)}
+							| ({} if d.amount is None else {'balance': d.amount}))
 					else:
-						yield ('params', {'symbol':d.symbol,'decimals':d.decimals})
+						yield ('params', {'symbol': d.symbol, 'decimals': d.decimals})
 
 			self.twctl.data = { # keys must be in correct order
 				'coin': self.coin.upper(),
 				'network': self.network.upper(),
 				'accounts': dict(gen_data(self.entries['accounts'])),
-				'tokens': {k:dict(gen_data(v)) for k,v in self.entries['tokens'].items()},
+				'tokens': {k:dict(gen_data(v)) for k, v in self.entries['tokens'].items()},
 			}
 			self.twctl.write(quiet=False)
 
-	class Export(TwJSON.Export,Base):
+	class Export(TwJSON.Export, Base):
 
-		async def get_entries(self,include_amts=True):
+		async def get_entries(self, include_amts=True):
 
 			def gen_data(data):
-				for k,v in data.items():
+				for k, v in data.items():
 					if k == 'params':
 						yield self.params_tuple(**v)
 					elif include_amts:
-						yield self.entry_tuple(TwMMGenID(self.proto,v['mmid']), k, v.get('balance'), v['comment'])
+						yield self.entry_tuple(TwMMGenID(self.proto, v['mmid']), k, v.get('balance'), v['comment'])
 					else:
-						yield self.entry_tuple_in(TwMMGenID(self.proto,v['mmid']), k, v['comment'])
+						yield self.entry_tuple_in(TwMMGenID(self.proto, v['mmid']), k, v['comment'])
 
 			def gen_token_data():
-				for token_addr,token_data in self.twctl.data['tokens'].items():
+				for token_addr, token_data in self.twctl.data['tokens'].items():
 					yield (
 						token_addr,
 						sorted(
 							gen_data(token_data),
-							key = lambda x: x.mmgen_id.sort_key if hasattr(x,'mmgen_id') else '+'
+							key = lambda x: x.mmgen_id.sort_key if hasattr(x, 'mmgen_id') else '+'
 						)
 					)
 
 			return {
 				'accounts': sorted(
 					gen_data(self.twctl.data['accounts']),
-					key = lambda x: x.mmgen_id.sort_key ),
+					key = lambda x: x.mmgen_id.sort_key),
 				'tokens': dict(sorted(gen_token_data()))
 			}
 

+ 15 - 15
mmgen/proto/eth/tw/unspent.py

@@ -25,18 +25,18 @@ from ....tw.unspent import TwUnspentOutputs
 from .view import EthereumTwView
 
 # No unspent outputs with Ethereum, but naming must be consistent
-class EthereumTwUnspentOutputs(EthereumTwView,TwUnspentOutputs):
+class EthereumTwUnspentOutputs(EthereumTwView, TwUnspentOutputs):
 
 	class display_type(TwUnspentOutputs.display_type):
 
 		class squeezed(TwUnspentOutputs.display_type.squeezed):
-			cols = ('num','addr','mmid','comment','amt','amt2')
+			cols = ('num', 'addr', 'mmid', 'comment', 'amt', 'amt2')
 
 		class detail(TwUnspentOutputs.display_type.detail):
-			cols = ('num','addr','mmid','amt','amt2','comment')
+			cols = ('num', 'addr', 'mmid', 'amt', 'amt2', 'comment')
 
 	class MMGenTwUnspentOutput(TwUnspentOutputs.MMGenTwUnspentOutput):
-		valid_attrs = {'txid','vout','amt','amt2','comment','twmmid','addr','confs','skip'}
+		valid_attrs = {'txid', 'vout', 'amt', 'amt2', 'comment', 'twmmid', 'addr', 'confs', 'skip'}
 		invalid_attrs = {'proto'}
 
 	has_age = False
@@ -62,19 +62,19 @@ class EthereumTwUnspentOutputs(EthereumTwView,TwUnspentOutputs):
 		'w':'a_view_detail',
 		'l':'i_comment_add',
 		'D':'i_addr_delete',
-		'R':'i_balance_refresh' }
+		'R':'i_balance_refresh'}
 
 	no_data_errmsg = 'No accounts in tracking wallet!'
 
-	def get_column_widths(self,data,wide,interactive):
+	def get_column_widths(self, data, wide, interactive):
 		# min screen width: 80 cols
 		# num addr [mmid] [comment] amt [amt2]
 		return self.compute_column_widths(
 			widths = { # fixed cols
-				'num': max(2,len(str(len(data)))+1),
+				'num': max(2, len(str(len(data)))+1),
 				'mmid': max(len(d.twmmid.disp) for d in data) if self.show_mmid else 0,
 				'amt': self.amt_widths['amt'],
-				'amt2': self.amt_widths.get('amt2',0),
+				'amt2': self.amt_widths.get('amt2', 0),
 				'spc': (5 if self.show_mmid else 3) + self.has_amt2, # 5(3) spaces in fs
 				'txid': 0,
 				'vout': 0,
@@ -95,17 +95,17 @@ class EthereumTwUnspentOutputs(EthereumTwView,TwUnspentOutputs):
 			interactive = interactive,
 		)
 
-	def do_sort(self,key=None,reverse=False):
+	def do_sort(self, key=None, reverse=False):
 		if key == 'txid':
 			return
-		super().do_sort(key=key,reverse=reverse)
+		super().do_sort(key=key, reverse=reverse)
 
 	async def get_rpc_data(self):
 		wl = self.twctl.sorted_list
 		if self.addrs:
 			wl = [d for d in wl if d['addr'] in self.addrs]
 		return [{
-				'account': TwLabel(self.proto,d['mmid']+' '+d['comment']),
+				'account': TwLabel(self.proto, d['mmid']+' '+d['comment']),
 				'address': d['addr'],
 				'amt': await self.twctl.get_balance(d['addr']),
 				'confirmations': 0, # TODO
@@ -115,11 +115,11 @@ class EthereumTokenTwUnspentOutputs(EthereumTwUnspentOutputs):
 
 	has_amt2 = True
 
-	async def __init__(self,proto,*args,**kwargs):
-		await super().__init__(proto,*args,**kwargs)
+	async def __init__(self, proto, *args, **kwargs):
+		await super().__init__(proto, *args, **kwargs)
 		self.proto.tokensym = self.twctl.symbol
 
-	async def get_data(self,*args,**kwargs):
-		await super().get_data(*args,**kwargs)
+	async def get_data(self, *args, **kwargs):
+		await super().get_data(*args, **kwargs)
 		for e in self.data:
 			e.amt2 = await self.twctl.get_eth_balance(e.addr)

+ 5 - 5
mmgen/proto/eth/tw/view.py

@@ -24,15 +24,15 @@ class EthereumTwView(TwView):
 		'twmmid': lambda i: i.twmmid.sort_key
 	}
 
-	def age_disp(self,o,age_fmt): # TODO
+	def age_disp(self, o, age_fmt): # TODO
 		pass
 
-	def get_disp_prec(self,wide):
+	def get_disp_prec(self, wide):
 		return self.proto.coin_amt.max_prec if wide else 8
 
-	def gen_subheader(self,cw,color):
+	def gen_subheader(self, cw, color):
 		if self.disp_prec == 8:
 			yield 'Balances truncated to 8 decimal points'
 		if self.cfg.cached_balances:
-			from ....color import nocolor,yellow
-			yield (nocolor,yellow)[color]('WARNING: Using cached balances. These may be out of date!')
+			from ....color import nocolor, yellow
+			yield (nocolor, yellow)[color]('WARNING: Using cached balances. These may be out of date!')

+ 10 - 9
mmgen/proto/eth/tx/base.py

@@ -15,7 +15,7 @@ proto.eth.tx.base: Ethereum base transaction class
 from collections import namedtuple
 
 from ....tx import base as TxBase
-from ....obj import HexStr,Int
+from ....obj import HexStr, Int
 
 class Base(TxBase.Base):
 
@@ -52,16 +52,17 @@ class Base(TxBase.Base):
 	def is_replaceable(self):
 		return True
 
-	async def get_receipt(self,txid):
-		rx = await self.rpc.call('eth_getTransactionReceipt','0x'+txid) # -> null if pending
+	async def get_receipt(self, txid):
+		rx = await self.rpc.call('eth_getTransactionReceipt', '0x'+txid) # -> null if pending
 		if not rx:
 			return None
-		tx = await self.rpc.call('eth_getTransactionByHash','0x'+txid)
-		return namedtuple('exec_status',['status','gas_sent','gas_used','gas_price','contract_addr','tx','rx'])(
-			status        = Int(rx['status'],16), # zero is failure, non-zero success
-			gas_sent      = Int(tx['gas'],16),
-			gas_used      = Int(rx['gasUsed'],16),
-			gas_price     = self.proto.coin_amt(int(tx['gasPrice'],16),from_unit='wei'),
+		tx = await self.rpc.call('eth_getTransactionByHash', '0x'+txid)
+		return namedtuple('exec_status',
+				['status', 'gas_sent', 'gas_used', 'gas_price', 'contract_addr', 'tx', 'rx'])(
+			status        = Int(rx['status'], 16), # zero is failure, non-zero success
+			gas_sent      = Int(tx['gas'], 16),
+			gas_used      = Int(rx['gasUsed'], 16),
+			gas_price     = self.proto.coin_amt(int(tx['gasPrice'], 16), from_unit='wei'),
 			contract_addr = self.proto.coin_addr(rx['contractAddress'][2:]) if rx['contractAddress'] else None,
 			tx            = tx,
 			rx            = rx,

+ 5 - 5
mmgen/proto/eth/tx/bump.py

@@ -15,23 +15,23 @@ proto.eth.tx.bump: Ethereum transaction bump class
 from decimal import Decimal
 
 from ....tx import bump as TxBase
-from .completed import Completed,TokenCompleted
-from .new import New,TokenNew
+from .completed import Completed, TokenCompleted
+from .new import New, TokenNew
 
-class Bump(Completed,New,TxBase.Bump):
+class Bump(Completed, New, TxBase.Bump):
 	desc = 'fee-bumped transaction'
 
 	@property
 	def min_fee(self):
 		return self.fee * Decimal('1.101')
 
-	def bump_fee(self,idx,fee):
+	def bump_fee(self, idx, fee):
 		self.txobj['gasPrice'] = self.fee_abs2gas(fee)
 
 	async def get_nonce(self):
 		return self.txobj['nonce']
 
-class TokenBump(TokenCompleted,TokenNew,Bump):
+class TokenBump(TokenCompleted, TokenNew, Bump):
 	desc = 'fee-bumped transaction'
 
 class AutomountBump(Bump):

+ 5 - 5
mmgen/proto/eth/tx/completed.py

@@ -13,16 +13,16 @@ proto.eth.tx.completed: Ethereum completed transaction class
 """
 
 from ....tx import completed as TxBase
-from .base import Base,TokenBase
+from .base import Base, TokenBase
 
-class Completed(Base,TxBase.Completed):
+class Completed(Base, TxBase.Completed):
 	fn_fee_unit = 'Mwei'
 
-	def __init__(self,*args,**kwargs):
+	def __init__(self, *args, **kwargs):
 
 		self.txobj = {}
 
-		super().__init__(*args,**kwargs)
+		super().__init__(*args, **kwargs)
 
 		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')
@@ -54,7 +54,7 @@ class Completed(Base,TxBase.Completed):
 	def get_serialized_locktime(self):
 		return None # TODO
 
-class TokenCompleted(TokenBase,Completed):
+class TokenCompleted(TokenBase, Completed):
 
 	@property
 	def change(self):

+ 10 - 10
mmgen/proto/eth/tx/info.py

@@ -31,9 +31,9 @@ class TxInfo(TxInfo):
 	def format_body(self, blockcount, nonmm_str, max_mmwid, enl, terse, sort):
 		tx = self.tx
 		m = {}
-		for k in ('inputs','outputs'):
-			if len(getattr(tx,k)):
-				m[k] = getattr(tx,k)[0].mmid if len(getattr(tx,k)) else ''
+		for k in ('inputs', 'outputs'):
+			if len(getattr(tx, k)):
+				m[k] = getattr(tx, k)[0].mmid if len(getattr(tx, k)) else ''
 				m[k] = ' ' + m[k].hl() if m[k] else ' ' + MMGenID.hlc(nonmm_str)
 		fs = """
 			From:      {f}{f_mmid}
@@ -43,7 +43,7 @@ class TxInfo(TxInfo):
 			Start gas: {G} Kwei
 			Nonce:     {n}
 			Data:      {d}
-		""".strip().replace('\t','')
+		""".strip().replace('\t', '')
 		t = tx.txobj
 		td = t['data']
 		to_addr = t[self.to_addr_key]
@@ -52,19 +52,19 @@ class TxInfo(TxInfo):
 			t      = to_addr.hl(0) if to_addr else blue('None'),
 			a      = t['amt'].hl(),
 			n      = t['nonce'].hl(),
-			d      = '{}... ({} bytes)'.format(td[:40],len(td)//2) if len(td) else blue('None'),
+			d      = '{}... ({} bytes)'.format(td[:40], len(td)//2) if len(td) else blue('None'),
 			c      = tx.proto.dcoin if len(tx.outputs) else '',
 			g      = yellow(tx.pretty_fmt_fee(t['gasPrice'].to_unit('Gwei'))),
 			G      = yellow(tx.pretty_fmt_fee(t['startGas'].to_unit('Kwei'))),
 			t_mmid = m['outputs'] if len(tx.outputs) else '',
 			f_mmid = m['inputs']) + '\n\n'
 
-	def format_abs_fee(self,color,iwidth):
-		return self.tx.fee.fmt(color=color,iwidth=iwidth) + (' (max)' if self.tx.txobj['data'] else '')
+	def format_abs_fee(self, color, iwidth):
+		return self.tx.fee.fmt(color=color, iwidth=iwidth) + (' (max)' if self.tx.txobj['data'] else '')
 
 	def format_rel_fee(self):
 		return ' ({} of spend amount)'.format(
-			pink('{:0.6f}%'.format( self.tx.fee / self.tx.send_amt * 100 ))
+			pink('{:0.6f}%'.format(self.tx.fee / self.tx.send_amt * 100))
 		)
 
 	def format_verbose_footer(self):
@@ -80,8 +80,8 @@ class TokenTxInfo(TxInfo):
 	def format_rel_fee(self):
 		return ''
 
-	def format_body(self,*args,**kwargs):
+	def format_body(self, *args, **kwargs):
 		return 'Token:     {d} {c}\n{r}'.format(
 			d = self.tx.txobj['token_addr'].hl(0),
 			c = blue('(' + self.tx.proto.dcoin + ')'),
-			r = super().format_body(*args,**kwargs ))
+			r = super().format_body(*args, **kwargs))

+ 32 - 31
mmgen/proto/eth/tx/new.py

@@ -22,16 +22,16 @@ from ....addr import is_mmgen_id, is_coin_addr
 from ..contract import Token
 from .base import Base, TokenBase
 
-class New(Base,TxBase.New):
+class New(Base, TxBase.New):
 	desc = 'transaction'
 	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):
+	def __init__(self, *args, **kwargs):
 
-		super().__init__(*args,**kwargs)
+		super().__init__(*args, **kwargs)
 
 		if self.cfg.gas:
 			self.gas = self.start_gas = self.proto.coin_amt(int(self.cfg.gas), from_unit='wei')
@@ -47,7 +47,8 @@ class New(Base,TxBase.New):
 			self.disable_fee_check = True
 
 	async def get_nonce(self):
-		return ETHNonce(int(await self.rpc.call('eth_getTransactionCount','0x'+self.inputs[0].addr,'pending'),16))
+		return ETHNonce(int(
+			await self.rpc.call('eth_getTransactionCount', '0x'+self.inputs[0].addr, 'pending'), 16))
 
 	async def make_txobj(self): # called by create_serialized()
 		self.txobj = {
@@ -64,22 +65,22 @@ class New(Base,TxBase.New):
 	# Instead of serializing tx data as with BTC, just create a JSON dump.
 	# This complicates things but means we avoid using the rlp library to deserialize the data,
 	# thus removing an attack vector
-	async def create_serialized(self,locktime=None,bump=None):
-		assert len(self.inputs) == 1,'Transaction has more than one input!'
+	async def create_serialized(self, locktime=None, bump=None):
+		assert len(self.inputs) == 1, 'Transaction has more than one input!'
 		o_num = len(self.outputs)
 		o_ok = 0 if self.usr_contract_data else 1
 		assert o_num == o_ok, f'Transaction has {o_num} output{suf(o_num)} (should have {o_ok})'
 		await self.make_txobj()
-		odict = {k:v if v is None else str(v) for k,v in self.txobj.items() if k != 'token_to'}
+		odict = {k:v if v is None else str(v) for k, v in self.txobj.items() if k != 'token_to'}
 		self.serialized = json.dumps(odict)
 		self.update_txid()
 
 	def update_txid(self):
 		assert not is_hex_str(self.serialized), (
-			'update_txid() must be called only when self.serialized is not hex data' )
+			'update_txid() must be called only when self.serialized is not hex data')
 		self.txid = MMGenTxID(make_chksum_6(self.serialized).upper())
 
-	async def process_cmd_args(self,cmd_args,ad_f,ad_w):
+	async def process_cmd_args(self, cmd_args, ad_f, ad_w):
 
 		lc = len(cmd_args)
 
@@ -96,10 +97,10 @@ class New(Base,TxBase.New):
 			amt      = self.proto.coin_amt(arg.amt or '0'),
 			is_chg   = not arg.amt)
 
-	def select_unspent(self,unspent):
+	def select_unspent(self, unspent):
 		from ....ui import line_input
 		while True:
-			reply = line_input( self.cfg, 'Enter an account to spend from: ' ).strip()
+			reply = line_input(self.cfg, 'Enter an account to spend from: ').strip()
 			if reply:
 				if not is_int(reply):
 					msg('Account number must be an integer')
@@ -116,7 +117,7 @@ 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'
+		return Int(await self.rpc.call('eth_gasPrice'), 16), 'eth_gasPrice'
 
 	def check_fee(self):
 		if not self.disable_fee_check:
@@ -127,14 +128,14 @@ class New(Base,TxBase.New):
 		return self.proto.coin_amt(amt_in_units, from_unit=units[unit]) * self.gas.toWei()
 
 	# 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):
+	def fee_est2abs(self, rel_fee, fe_type=None):
 		ret = self.fee_gasPrice2abs(rel_fee) * self.cfg.fee_adjust
 		if self.cfg.verbose:
 			msg(f'Estimated fee: {ret} ETH')
 		return ret
 
-	def convert_and_check_fee(self,fee,desc):
-		abs_fee = self.feespec2abs(fee,None)
+	def convert_and_check_fee(self, fee, desc):
+		abs_fee = self.feespec2abs(fee, None)
 		if abs_fee is False:
 			return False
 		elif not self.disable_fee_check and (abs_fee > self.proto.max_tx_fee):
@@ -142,71 +143,71 @@ class New(Base,TxBase.New):
 				abs_fee.hl(),
 				desc,
 				self.proto.max_tx_fee.hl(),
-				c = self.proto.coin ))
+				c = self.proto.coin))
 			return False
 		else:
 			return abs_fee
 
-	def update_change_output(self,funds_left):
+	def update_change_output(self, funds_left):
 		if self.outputs and self.outputs[0].is_chg:
 			self.update_output_amt(0, funds_left)
 
 	async def get_input_addrs_from_cmdline(self):
 		ret = []
 		if self.cfg.inputs:
-			data_root = (await TwCtl(self.cfg,self.proto)).data_root # must create new instance here
+			data_root = (await TwCtl(self.cfg, self.proto)).data_root # must create new instance here
 			errmsg = 'Address {!r} not in tracking wallet'
 			for addr in self.cfg.inputs.split(','):
-				if is_mmgen_id(self.proto,addr):
+				if is_mmgen_id(self.proto, addr):
 					for waddr in data_root:
 						if data_root[waddr]['mmid'] == addr:
 							ret.append(waddr)
 							break
 					else:
-						die( 'UserAddressNotInWallet', errmsg.format(addr) )
-				elif is_coin_addr(self.proto,addr):
+						die('UserAddressNotInWallet', errmsg.format(addr))
+				elif is_coin_addr(self.proto, addr):
 					if not addr in data_root:
-						die( 'UserAddressNotInWallet', errmsg.format(addr) )
+						die('UserAddressNotInWallet', errmsg.format(addr))
 					ret.append(addr)
 				else:
-					die(1,f'{addr!r}: not an MMGen ID or coin address')
+					die(1, f'{addr!r}: not an MMGen ID or coin address')
 		return ret
 
 	def final_inputs_ok_msg(self, funds_left):
 		chg = self.proto.coin_amt('0') if (self.outputs and self.outputs[0].is_chg) else funds_left
 		return 'Transaction leaves {} {} in the sender’s account'.format(chg.hl(), self.proto.coin)
 
-class TokenNew(TokenBase,New):
+class TokenNew(TokenBase, New):
 	desc = 'transaction'
 	fee_is_approximate = True
 
 	async def make_txobj(self): # called by create_serialized()
 		await super().make_txobj()
-		t = Token(self.cfg,self.proto,self.twctl.token,self.twctl.decimals)
+		t = Token(self.cfg, self.proto, self.twctl.token, self.twctl.decimals)
 		o = self.txobj
 		o['token_addr'] = t.addr
 		o['decimals'] = t.decimals
 		o['token_to'] = o['to']
-		o['data'] = t.create_data(o['token_to'],o['amt'])
+		o['data'] = t.create_data(o['token_to'], o['amt'])
 
-	def update_change_output(self,funds_left):
+	def update_change_output(self, funds_left):
 		if self.outputs[0].is_chg:
-			self.update_output_amt(0,self.inputs[0].amt)
+			self.update_output_amt(0, self.inputs[0].amt)
 
 	# token transaction, so check both eth and token balances
 	# TODO: add test with insufficient funds
-	async def precheck_sufficient_funds(self,inputs_sum,sel_unspent,outputs_sum):
+	async def precheck_sufficient_funds(self, inputs_sum, sel_unspent, outputs_sum):
 		eth_bal = await self.twctl.get_eth_balance(sel_unspent[0].addr)
 		if eth_bal == 0: # we don't know the fee yet
 			msg('This account has no ether to pay for the transaction fee!')
 			return False
-		return await super().precheck_sufficient_funds(inputs_sum,sel_unspent,outputs_sum)
+		return await super().precheck_sufficient_funds(inputs_sum, sel_unspent, outputs_sum)
 
 	async def get_funds_available(self, fee, outputs_sum):
 		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):
+	def final_inputs_ok_msg(self, funds_left):
 		token_bal = (
 			self.proto.coin_amt('0') if self.outputs[0].is_chg
 			else self.inputs[0].amt - self.outputs[0].amt

+ 11 - 11
mmgen/proto/eth/tx/online.py

@@ -12,24 +12,24 @@
 proto.eth.tx.online: Ethereum online signed transaction class
 """
 
-from ....util import msg,die
+from ....util import msg, die
 from ....color import orange
 from ....tx import online as TxBase
 from .. import erigon_sleep
-from .signed import Signed,TokenSigned
+from .signed import Signed, TokenSigned
 
-class OnlineSigned(Signed,TxBase.OnlineSigned):
+class OnlineSigned(Signed, TxBase.OnlineSigned):
 
-	async def send(self,prompt_user=True):
+	async def send(self, prompt_user=True):
 
 		self.check_correct_chain()
 
 		if not self.disable_fee_check and (self.fee > self.proto.max_tx_fee):
-			die(2,'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format(
+			die(2, 'Transaction fee ({}) greater than {} max_tx_fee ({} {})!'.format(
 				self.fee,
 				self.proto.name,
 				self.proto.max_tx_fee,
-				self.proto.coin ))
+				self.proto.coin))
 
 		await self.status.display()
 
@@ -40,12 +40,12 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
 			m = 'BOGUS transaction NOT sent: {}'
 		else:
 			try:
-				ret = await self.rpc.call('eth_sendRawTransaction','0x'+self.serialized)
+				ret = await self.rpc.call('eth_sendRawTransaction', '0x'+self.serialized)
 			except Exception as e:
 				msg(orange('\n'+str(e)))
 				die(2, f'Send of MMGen transaction {self.txid} failed')
 			m = 'Transaction sent: {}'
-			assert ret == '0x'+self.coin_txid,'txid mismatch (after sending)'
+			assert ret == '0x'+self.coin_txid, 'txid mismatch (after sending)'
 			await erigon_sleep(self)
 
 		msg(m.format(self.coin_txid.hl()))
@@ -58,7 +58,7 @@ class OnlineSigned(Signed,TxBase.OnlineSigned):
 		if 'token_addr' in self.txobj:
 			msg('Contract address: {}'.format(self.txobj['token_addr'].hl(0)))
 
-class TokenOnlineSigned(TokenSigned,OnlineSigned):
+class TokenOnlineSigned(TokenSigned, OnlineSigned):
 
 	def parse_txfile_serialized_data(self):
 		from ....addr import TokenAddr
@@ -66,9 +66,9 @@ class TokenOnlineSigned(TokenSigned,OnlineSigned):
 		OnlineSigned.parse_txfile_serialized_data(self)
 		o = self.txobj
 		assert self.twctl.token == o['to']
-		o['token_addr'] = TokenAddr(self.proto,o['to'])
+		o['token_addr'] = TokenAddr(self.proto, o['to'])
 		o['decimals']   = self.twctl.decimals
-		t = Token(self.cfg,self.proto,o['token_addr'],o['decimals'])
+		t = Token(self.cfg, self.proto, o['token_addr'], o['decimals'])
 		o['amt'] = t.transferdata2amt(o['data'])
 		o['token_to'] = t.transferdata2sendaddr(o['data'])
 

+ 10 - 10
mmgen/proto/eth/tx/signed.py

@@ -17,22 +17,22 @@ from ....obj import CoinTxID, ETHNonce, HexStr
 from ....addr import CoinAddr, TokenAddr
 from .completed import Completed, TokenCompleted
 
-class Signed(Completed,TxBase.Signed):
+class Signed(Completed, TxBase.Signed):
 
 	desc = 'signed transaction'
 
 	def parse_txfile_serialized_data(self):
 		from ..pyethereum.transactions import Transaction
 		from .. import rlp
-		etx = rlp.decode(bytes.fromhex(self.serialized),Transaction)
+		etx = rlp.decode(bytes.fromhex(self.serialized), Transaction)
 		d = etx.to_dict() # ==> hex values have '0x' prefix, 0 is '0x'
-		for k in ('sender','to','data'):
+		for k in ('sender', 'to', 'data'):
 			if k in d:
-				d[k] = d[k].replace('0x','',1)
+				d[k] = d[k].replace('0x', '', 1)
 		o = {
-			'from':     CoinAddr(self.proto,d['sender']),
+			'from':     CoinAddr(self.proto, d['sender']),
 			# NB: for token, 'to' is token address
-			'to':       CoinAddr(self.proto,d['to']) if d['to'] else None,
+			'to':       CoinAddr(self.proto, d['to']) if d['to'] else None,
 			'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'),
@@ -40,15 +40,15 @@ class Signed(Completed,TxBase.Signed):
 			'data':     HexStr(d['data']) }
 		if o['data'] and not o['to']: # token- or contract-creating transaction
 			# NB: could be a non-token contract address:
-			o['token_addr'] = TokenAddr(self.proto,etx.creates.hex())
+			o['token_addr'] = TokenAddr(self.proto, etx.creates.hex())
 			self.disable_fee_check = True
 		txid = CoinTxID(etx.hash.hex())
-		assert txid == self.coin_txid,"txid in tx.serialized doesn't match value in MMGen transaction file"
+		assert txid == self.coin_txid, "txid in tx.serialized doesn't match value in MMGen transaction file"
 		self.gas = o['startGas'] # approximate, but better than nothing
 		self.txobj = o
-		return d # 'token_addr','decimals' required by Token subclass
+		return d # 'token_addr', 'decimals' required by Token subclass
 
-class TokenSigned(TokenCompleted,Signed):
+class TokenSigned(TokenCompleted, Signed):
 	desc = 'signed transaction'
 
 	def parse_txfile_serialized_data(self):

+ 11 - 11
mmgen/proto/eth/tx/status.py

@@ -13,38 +13,38 @@ proto.eth.tx.status: Ethereum transaction status class
 """
 
 from ....tx import status as TxBase
-from ....util import msg,die,suf,capfirst
+from ....util import msg, die, suf, capfirst
 
 class Status(TxBase.Status):
 
-	async def display(self,usr_req=False):
+	async def display(self, usr_req=False):
 
 		tx = self.tx
 
 		async def is_in_mempool():
 			if not 'full_node' in tx.rpc.caps:
 				return False
-			if tx.rpc.daemon.id in ('parity','openethereum'):
+			if tx.rpc.daemon.id in ('parity', 'openethereum'):
 				pool = [x['hash'] for x in await tx.rpc.call('parity_pendingTransactions')]
-			elif tx.rpc.daemon.id in ('geth','erigon'):
+			elif tx.rpc.daemon.id in ('geth', 'erigon'):
 				res = await tx.rpc.call('txpool_content')
 				pool = list(res['pending']) + list(res['queued'])
 			return '0x'+tx.coin_txid in pool
 
 		async def is_in_wallet():
-			d = await tx.rpc.call('eth_getTransactionReceipt','0x'+tx.coin_txid)
+			d = await tx.rpc.call('eth_getTransactionReceipt', '0x'+tx.coin_txid)
 			if d and 'blockNumber' in d and d['blockNumber'] is not None:
 				from collections import namedtuple
-				receipt_info = namedtuple('receipt_info',['confs','exec_status'])
+				receipt_info = namedtuple('receipt_info', ['confs', 'exec_status'])
 				return receipt_info(
-					confs       = 1 + int(await tx.rpc.call('eth_blockNumber'),16) - int(d['blockNumber'],16),
-					exec_status = int(d['status'],16)
+					confs       = 1 + int(await tx.rpc.call('eth_blockNumber'), 16) - int(d['blockNumber'], 16),
+					exec_status = int(d['status'], 16)
 				)
 
 		if await is_in_mempool():
 			msg(
 				'Transaction is in mempool' if usr_req else
-				'Warning: transaction is in mempool!' )
+				'Warning: transaction is in mempool!')
 			return
 
 		if usr_req:
@@ -56,8 +56,8 @@ class Status(TxBase.Status):
 						msg(f'{cd} failed to execute!')
 					else:
 						msg(f'{cd} successfully executed with status {ret.exec_status}')
-				die(0,f'Transaction has {ret.confs} confirmation{suf(ret.confs)}')
-			die(1,'Transaction is neither in mempool nor blockchain!')
+				die(0, f'Transaction has {ret.confs} confirmation{suf(ret.confs)}')
+			die(1, 'Transaction is neither in mempool nor blockchain!')
 
 class TokenStatus(Status):
 	pass

+ 16 - 16
mmgen/proto/eth/tx/unsigned.py

@@ -21,26 +21,26 @@ from ....addr import CoinAddr, TokenAddr
 from ..contract import Token
 from .completed import Completed, TokenCompleted
 
-class Unsigned(Completed,TxBase.Unsigned):
+class Unsigned(Completed, TxBase.Unsigned):
 	desc = 'unsigned transaction'
 
 	def parse_txfile_serialized_data(self):
 		d = json.loads(self.serialized)
 		o = {
-			'from':     CoinAddr(self.proto,d['from']),
+			'from':     CoinAddr(self.proto, d['from']),
 			# NB: for token, 'to' is sendto address
-			'to':       CoinAddr(self.proto,d['to']) if d['to'] else None,
+			'to':       CoinAddr(self.proto, d['to']) if d['to'] else None,
 			'amt':      self.proto.coin_amt(d['amt']),
 			'gasPrice': self.proto.coin_amt(d['gasPrice']),
 			'startGas': self.proto.coin_amt(d['startGas']),
 			'nonce':    ETHNonce(d['nonce']),
 			'chainId':  None if d['chainId'] == 'None' else Int(d['chainId']),
-			'data':     HexStr(d['data']) }
+			'data':     HexStr(d['data'])}
 		self.gas = o['startGas'] # approximate, but better than nothing
 		self.txobj = o
-		return d # 'token_addr','decimals' required by Token subclass
+		return d # 'token_addr', 'decimals' required by Token subclass
 
-	async def do_sign(self,wif):
+	async def do_sign(self, wif):
 		o = self.txobj
 		o_conv = {
 			'to':       bytes.fromhex(o['to'] or ''),
@@ -61,11 +61,11 @@ class Unsigned(Completed,TxBase.Unsigned):
 
 		if o['data']:
 			if o['to']:
-				assert self.txobj['token_addr'] == TokenAddr(self.proto,etx.creates.hex()),'Token address mismatch'
+				assert self.txobj['token_addr'] == TokenAddr(self.proto, etx.creates.hex()), 'Token address mismatch'
 			else: # token- or contract-creating transaction
-				self.txobj['token_addr'] = TokenAddr(self.proto,etx.creates.hex())
+				self.txobj['token_addr'] = TokenAddr(self.proto, etx.creates.hex())
 
-	async def sign(self,tx_num_str,keys): # return TX object or False; don't exit or raise exception
+	async def sign(self, tx_num_str, keys): # return TX object or False; don't exit or raise exception
 
 		from ....exception import TransactionChainMismatch
 		try:
@@ -84,28 +84,28 @@ class Unsigned(Completed,TxBase.Unsigned):
 			msg(f'{e}: transaction signing failed!')
 			return False
 
-class TokenUnsigned(TokenCompleted,Unsigned):
+class TokenUnsigned(TokenCompleted, Unsigned):
 	desc = 'unsigned transaction'
 
 	def parse_txfile_serialized_data(self):
 		d = Unsigned.parse_txfile_serialized_data(self)
 		o = self.txobj
-		o['token_addr'] = TokenAddr(self.proto,d['token_addr'])
+		o['token_addr'] = TokenAddr(self.proto, d['token_addr'])
 		o['decimals'] = Int(d['decimals'])
-		t = Token(self.cfg,self.proto,o['token_addr'],o['decimals'])
-		o['data'] = t.create_data(o['to'],o['amt'])
+		t = Token(self.cfg, self.proto, o['token_addr'], o['decimals'])
+		o['data'] = t.create_data(o['to'], o['amt'])
 		o['token_to'] = t.transferdata2sendaddr(o['data'])
 
-	async def do_sign(self,wif):
+	async def do_sign(self, wif):
 		o = self.txobj
-		t = Token(self.cfg,self.proto,o['token_addr'],o['decimals'])
+		t = Token(self.cfg, self.proto, o['token_addr'], o['decimals'])
 		tx_in = t.make_tx_in(
 				to_addr   = o['to'],
 				amt       = o['amt'],
 				start_gas = self.start_gas,
 				gasPrice  = o['gasPrice'],
 				nonce     = o['nonce'])
-		(self.serialized,self.coin_txid) = await t.txsign(tx_in,wif,o['from'],chain_id=o['chainId'])
+		(self.serialized, self.coin_txid) = await t.txsign(tx_in, wif, o['from'], chain_id=o['chainId'])
 
 class AutomountUnsigned(TxBase.AutomountUnsigned, Unsigned):
 	pass