def sign_and_broadcast_transaction(self, transaction_dict, transaction_name: str = "", confirmations: int = 0) -> dict: # # Setup # # TODO # 1754 - Move this to singleton - I do not approve... nor does Bogdan? if GlobalLoggerSettings._json_ipc: emitter = JSONRPCStdoutEmitter() else: emitter = StdoutEmitter() if self.transacting_power is READ_ONLY_INTERFACE: raise self.InterfaceError(str(READ_ONLY_INTERFACE)) # # Sign # # TODO: Show the USD Price: https://api.coinmarketcap.com/v1/ticker/ethereum/ price = transaction_dict['gasPrice'] cost_wei = price * transaction_dict['gas'] cost = Web3.fromWei(cost_wei, 'gwei') if self.transacting_power.is_device: emitter.message( f'Confirm transaction {transaction_name} on hardware wallet... ({cost} gwei @ {price})', color='yellow') signed_raw_transaction = self.transacting_power.sign_transaction( transaction_dict) # # Broadcast # emitter.message( f'Broadcasting {transaction_name} Transaction ({cost} gwei @ {price})...', color='yellow') try: txhash = self.client.send_raw_transaction( signed_raw_transaction) # <--- BROADCAST except (TestTransactionFailed, ValueError) as error: raise # TODO: Unify with Transaction failed handling # # Receipt # try: # TODO: Handle block confirmation exceptions receipt = self.client.wait_for_receipt(txhash, timeout=self.TIMEOUT, confirmations=confirmations) except TimeExhausted: # TODO: #1504 - Handle transaction timeout raise else: self.log.debug( f"[RECEIPT-{transaction_name}] | txhash: {receipt['transactionHash'].hex()}" ) # # Confirmations # # Primary check transaction_status = receipt.get('status', UNKNOWN_TX_STATUS) if transaction_status == 0: failure = f"Transaction transmitted, but receipt returned status code 0. " \ f"Full receipt: \n {pprint.pformat(receipt, indent=2)}" raise self.InterfaceError(failure) if transaction_status is UNKNOWN_TX_STATUS: self.log.info( f"Unknown transaction status for {txhash} (receipt did not contain a status field)" ) # Secondary check tx = self.client.get_transaction(txhash) if tx["gas"] == receipt["gasUsed"]: raise self.InterfaceError( f"Transaction consumed 100% of transaction gas." f"Full receipt: \n {pprint.pformat(receipt, indent=2)}") return receipt
def sign_and_broadcast_transaction( self, transaction_dict, transaction_name: str = "", confirmations: int = 0, fire_and_forget: bool = False) -> Union[TxReceipt, HexBytes]: """ Takes a transaction dictionary, signs it with the configured signer, then broadcasts the signed transaction using the ethereum provider's eth_sendRawTransaction RPC endpoint. Optionally blocks for receipt and confirmation with 'confirmations', and 'fire_and_forget' flags. If 'fire and forget' is True this method returns the transaction hash only, without waiting for a receipt - otherwise return the transaction receipt. """ # # Setup # # TODO # 1754 - Move this to singleton - I do not approve... nor does Bogdan? if GlobalLoggerSettings._json_ipc: emitter = JSONRPCStdoutEmitter() else: emitter = StdoutEmitter() if self.transacting_power is READ_ONLY_INTERFACE: raise self.InterfaceError(str(READ_ONLY_INTERFACE)) # # Sign # # TODO: Show the USD Price: https://api.coinmarketcap.com/v1/ticker/ethereum/ price = transaction_dict['gasPrice'] price_gwei = Web3.fromWei(price, 'gwei') cost_wei = price * transaction_dict['gas'] cost = Web3.fromWei(cost_wei, 'ether') if self.transacting_power.is_device: emitter.message( f'Confirm transaction {transaction_name} on hardware wallet... ' f'({cost} ETH @ {price_gwei} gwei)', color='yellow') signed_raw_transaction = self.transacting_power.sign_transaction( transaction_dict) # # Broadcast # emitter.message( f'Broadcasting {transaction_name} Transaction ({cost} ETH @ {price_gwei} gwei)...', color='yellow') try: txhash = self.client.send_raw_transaction( signed_raw_transaction) # <--- BROADCAST except (TestTransactionFailed, ValueError): raise # TODO: Unify with Transaction failed handling -- Entry point for _handle_failed_transaction else: if fire_and_forget: return txhash # # Receipt # try: # TODO: Handle block confirmation exceptions receipt = self.client.wait_for_receipt(txhash, timeout=self.TIMEOUT, confirmations=confirmations) except TimeExhausted: # TODO: #1504 - Handle transaction timeout raise else: self.log.debug( f"[RECEIPT-{transaction_name}] | txhash: {receipt['transactionHash'].hex()}" ) # # Confirmations # # Primary check transaction_status = receipt.get('status', UNKNOWN_TX_STATUS) if transaction_status == 0: failure = f"Transaction transmitted, but receipt returned status code 0. " \ f"Full receipt: \n {pprint.pformat(receipt, indent=2)}" raise self.InterfaceError(failure) if transaction_status is UNKNOWN_TX_STATUS: self.log.info( f"Unknown transaction status for {txhash} (receipt did not contain a status field)" ) # Secondary check tx = self.client.get_transaction(txhash) if tx["gas"] == receipt["gasUsed"]: raise self.InterfaceError( f"Transaction consumed 100% of transaction gas." f"Full receipt: \n {pprint.pformat(receipt, indent=2)}") return receipt
def sign_and_broadcast_transaction(self, unsigned_transaction, transaction_name: str = "", confirmations: int = 0) -> dict: # # Setup # # TODO # 1754 # TODO: Move this to singleton - I do not approve... nor does Bogdan? if GlobalLoggerSettings._json_ipc: emitter = JSONRPCStdoutEmitter() else: emitter = StdoutEmitter() if self.transacting_power is READ_ONLY_INTERFACE: raise self.InterfaceError(str(READ_ONLY_INTERFACE)) # # Sign # # TODO: Show the USD Price # Price Oracle # https://api.coinmarketcap.com/v1/ticker/ethereum/ price = unsigned_transaction['gasPrice'] cost_wei = price * unsigned_transaction['gas'] cost = Web3.fromWei(cost_wei, 'gwei') if self.transacting_power.device: emitter.message( f'Confirm transaction {transaction_name} on hardware wallet... ({cost} gwei @ {price})', color='yellow') signed_raw_transaction = self.transacting_power.sign_transaction( unsigned_transaction) # # Broadcast # emitter.message( f'Broadcasting {transaction_name} Transaction ({cost} gwei @ {price})...', color='yellow') txhash = self.client.send_raw_transaction(signed_raw_transaction) try: receipt = self.client.wait_for_receipt(txhash, timeout=self.TIMEOUT) except TimeExhausted: # TODO: #1504 - Handle transaction timeout raise else: self.log.debug( f"[RECEIPT-{transaction_name}] | txhash: {receipt['transactionHash'].hex()}" ) # # Confirm # # Primary check deployment_status = receipt.get('status', UNKNOWN_TX_STATUS) if deployment_status == 0: failure = f"Transaction transmitted, but receipt returned status code 0. " \ f"Full receipt: \n {pprint.pformat(receipt, indent=2)}" raise self.InterfaceError(failure) if deployment_status is UNKNOWN_TX_STATUS: self.log.info( f"Unknown transaction status for {txhash} (receipt did not contain a status field)" ) # Secondary check tx = self.client.get_transaction(txhash) if tx["gas"] == receipt["gasUsed"]: raise self.InterfaceError( f"Transaction consumed 100% of transaction gas." f"Full receipt: \n {pprint.pformat(receipt, indent=2)}") # Block confirmations if confirmations: start = maya.now() confirmations_so_far = self.get_confirmations(receipt) while confirmations_so_far < confirmations: self.log.info( f"So far, we've only got {confirmations_so_far} confirmations. " f"Waiting for {confirmations - confirmations_so_far} more." ) time.sleep(3) confirmations_so_far = self.get_confirmations(receipt) if (maya.now() - start).seconds > self.TIMEOUT: raise self.NotEnoughConfirmations return receipt