def cli(ctx, config_path, broadcast: bool): config = parse_config(ctx, config_path, Config) # address -> private_key accounts = { eth_account.private_to_address(private_key): private_key for private_key in config.private_keys } tx_fee = 21_000 * config.gas_price low_eth_balance: list[str] = [] signed_tx: list[SignedTx] = [] for address, private_key in accounts.items(): res = eth_rpc.eth_get_balance(config.random_node, address) if res.is_error(): return fatal(f"can't get balance: {res.error}") balance = res.ok if balance <= tx_fee: low_eth_balance.append(address) else: res = eth_rpc.eth_get_transaction_count(config.random_node, address) if res.is_error(): return fatal(f"can't get nonce: {res.error}") nonce = res.ok value = balance - tx_fee tx = eth_tx.sign_tx( nonce=nonce, gas_price=config.gas_price, gas=21_000, private_key=private_key, chain_id=config.chain_id, value=value, to=config.recipient, ) signed_tx.append(tx) if broadcast: result = [] for tx in signed_tx: res = eth_rpc.eth_send_raw_transaction(config.random_node, tx.raw_tx) result.append(res.ok_or_error) print_json(md(low_eth_balance, result)) else: txs = [] for tx in signed_tx: decoded = eth_tx.decode_raw_tx(tx.raw_tx) decoded = eth_utils.to_human_readable_tx(decoded) # type:ignore txs.append(tx.dict() | md(decoded)) print_json(md(low_eth_balance, signed_tx=txs))
def cli(path): path = Path(path) addresses: list = [] if path.is_file(): _parse_file(path, addresses) elif path.is_dir(): _parse_dir(path, addresses) else: fatal("can't open path") if addresses: for a in pydash.uniq(addresses): click.echo(a) else: click.echo("nothing found")
def sign_txs(config: Config) -> list[SignedTx]: result = [] for acc in config.accounts: for tx in acc.txs: nonce = _get_nonce(config, acc, tx) private_key = _get_private_key(config, acc) data = _get_data(config, tx) value = tx.value if tx.value is not None else config.value gas = (tx.gas if tx.gas is not None else config.gas) or 21_000 gas_price = tx.gas_price if tx.gas_price else config.gas_price if not gas_price: return fatal("Set global gas_price or tx.gas_price") raw_tx = eth_tx.sign_tx( private_key=private_key, nonce=nonce, to=tx.to, data=data, value=value, gas_price=gas_price, gas=gas, chain_id=config.chain_id, ) result.append(raw_tx) return result
def _get_token_info(self, address: str) -> TokenInfo: res = eth_erc20.get_symbol(self.config.random_node, address, timeout=TIMEOUT) if res.is_error(): return fatal(f"can't get symbol for token {address}: {res.error}") symbol = res.ok res = eth_erc20.get_decimals(self.config.random_node, address, timeout=TIMEOUT) if res.is_error(): return fatal( f"can't get decimals for token {address}: {res.error}") decimals = res.ok return TokenInfo(address=address, symbol=symbol, decimals=decimals)
def _get_private_key(config: Config, acc: Config.Account) -> str: private_keys: dict[str, str] = {} # address -> private_key for private_key in config.private_keys: private_keys[eth_account.private_to_address( private_key).lower()] = private_key.lower() private_key = private_keys.get(acc.from_.lower()) if not private_key: return fatal("There is no private_key for " + acc.from_) return private_key
def _get_data(config: Config, tx: Config.Account.Tx) -> Optional[str]: if tx.erc20_transfer: if not eth_account.is_address(tx.to): return fatal( "tx.to must be a valid address for a erc20_transfer tx") if not eth_account.is_address(tx.erc20_transfer.recipient): return fatal(f"{tx.erc20_transfer.recipient} is not valid address") if tx.data is not None: return fatal("tx.data must be null for a erc_transfer tx") if tx.erc20_transfer.value.isdigit(): value = int(tx.erc20_transfer.value) else: token_address = cast(str, tx.to) token_address = token_address.lower() token_info = pydash.find(_token_infos, lambda t: t.address == token_address) if not token_info: res = eth_erc20.get_symbol(config.random_node, token_address) if res.is_error(): return fatal( f"can't get symbol for token {token_address}: {res.error}" ) symbol = res.ok res = eth_erc20.get_decimals(config.random_node, token_address) if res.is_error(): return fatal( f"can't get decimals for token {tx.to}: {res.error}") decimals = res.ok token_info = TokenInfo(symbol=symbol, decimal=decimals, address=token_address) _token_infos.append(token_info) try: value = eth_utils.to_wei_token(tx.erc20_transfer.value, token_info.symbol, token_info.decimal) except ValueError as e: return fatal( f"can't parse token value: '{tx.erc20_transfer.value}', symbol: {token_info.symbol}, decimals: {token_info.decimal}, error: {str(e)}" # noqa ) return eth_erc20.encode_transfer_input_data( tx.erc20_transfer.recipient, value) return tx.data
def _get_gas_price(config: Config, tx: Config.Account.Tx) -> int: gas_price = tx.gas_price if tx.gas_price else config.gas_price if not gas_price: return fatal("Use global gas_price or for each tx") return gas_price