def transfer_funds(self, account: Account, recipient_id: str, percentage: float, payload: str = "", tx_ttl: int = defaults.TX_TTL, include_fee=True): """ Create and execute a spend transaction """ if percentage < 0 or percentage > 1: raise ValueError(f"Percentage should be a number between 0 and 1, got {percentage}") account_on_chain = self.get_account_by_pubkey(pubkey=account.get_address()) request_transfer_amount = int(account_on_chain.balance * percentage) # retrieve the nonce account.nonce = account_on_chain.nonce + 1 # retrieve ttl tx_ttl = self.compute_absolute_ttl(tx_ttl) # build the transaction tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, request_transfer_amount, payload, defaults.FEE, tx_ttl.absolute_ttl, account.nonce) # if the request_transfer_amount should include the fee keep calculating the fee if include_fee: amount = request_transfer_amount while (amount + tx.data.fee) > request_transfer_amount: amount = request_transfer_amount - tx.data.fee tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, defaults.FEE, tx_ttl.absolute_ttl, account.nonce) # execute the transaction tx = self.sign_transaction(account, tx.tx) # post the transaction self.broadcast_transaction(tx.tx, tx_hash=tx.hash) return tx
def account_basic_to_ga(self, account: Account, ga_contract: str, calldata: str, auth_fun: str = defaults.GA_AUTH_FUNCTION, fee: int = defaults.FEE, tx_ttl: int = defaults.TX_TTL, gas: int = defaults.CONTRACT_GAS, gas_price: int = defaults.CONTRACT_GAS_PRICE): """ Transform a POA (Plain Old Account) to a GA (Generalized Account) :param account: the account to transform :param contract: the compiled contract associated to the GA :param auth_fun: the name of the contract function to use for authorization (default: authorize) """ # check the auth_fun name if auth_fun is None or len(auth_fun) == 0: raise TypeError("The parameter auth_fun is required") # decode the contract and search for the authorization function auth_fun_hash = None contract_data = contract.CompilerClient.decode_bytecode(ga_contract) if len(contract_data.type_info) == 0: # TODO: we assume is a FATE env, but is a bit weak auth_fun_hash = hashing.hash(auth_fun.encode('utf-8')) for ti in contract_data.type_info: if ti.fun_name == auth_fun: auth_fun_hash = ti.fun_hash # if the function is not found then raise an error if auth_fun_hash is None: raise TypeError(f"Authorization function not found: '{auth_fun}'") # get the nonce nonce = self.get_next_nonce(account.get_address()) # compute the ttl ttl = self.compute_absolute_ttl(tx_ttl).absolute_ttl # get abi and vm version vm_version, abi_version = self.get_vm_abi_versions() # build the transaction tx = self.tx_builder.tx_ga_attach( account.get_address(), nonce, ga_contract, auth_fun_hash, vm_version, abi_version, fee, ttl, gas, gas_price, calldata ) # sign the transaction tx = self.sign_transaction(account, tx) # broadcast the transaction self.broadcast_transaction(tx) return tx
def sign_transaction(self, account: Account, tx: transactions.TxObject, metadata: dict = {}, **kwargs) -> tuple: """ Sign a transaction :return: the transaction for the transaction """ # first retrieve the account from the node # so we can check if it is generalized or not on_chain_account = self.get_account(account.get_address()) # if the account is not generalized sign and return the transaction if not on_chain_account.is_generalized(): s = transactions.TxSigner(account, self.config.network_id) signature = s.sign_transaction(tx, metadata) return self.tx_builder.tx_signed([signature], tx, metadata=metadata) # if the account is generalized then prepare the ga_meta_tx # 1. wrap the tx into a signed tx (without signatures) sg_tx = self.tx_builder.tx_signed([], tx) # 2. wrap the tx into a ga_meta_tx # get the absolute ttl ttl = self.compute_absolute_ttl(kwargs.get( "ttl", defaults.TX_TTL)).absolute_ttl # get abi version _, abi = self.get_vm_abi_versions() # check that the parameter auth_data is provided auth_data = kwargs.get("auth_data") if auth_data is None: raise TypeError( "the auth_data parameter is required for ga accounts") # verify the gas amount TODO: add a tx verification gas = kwargs.get("gas", defaults.GA_MAX_AUTH_FUN_GAS) if gas > defaults.GA_MAX_AUTH_FUN_GAS: raise TypeError( f"the maximum gas value for ga auth_fun is {defaults.GA_MAX_AUTH_FUN_GAS}, got {gas}" ) # build the ga_sg_tx = self.tx_builder.tx_ga_meta( account.get_address(), auth_data, kwargs.get("abi_version", abi), kwargs.get("fee", defaults.FEE), gas, kwargs.get("gas_price", defaults.CONTRACT_GAS_PRICE), ttl, sg_tx) # 3. wrap the the ga into a signed transaction sg_ga_sg_tx = self.tx_builder.tx_signed([], ga_sg_tx, metadata=metadata) return sg_ga_sg_tx
def spend(self, account: Account, recipient_id: str, amount: int, payload: str = "", fee: int = defaults.FEE, tx_ttl: int = defaults.TX_TTL): """ Create and execute a spend transaction """ # retrieve the nonce account.nonce = self.get_next_nonce(account.get_address()) if account.nonce == 0 else account.nonce + 1 # retrieve ttl tx_ttl = self.compute_absolute_ttl(tx_ttl) # build the transaction tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl.absolute_ttl, account.nonce) # execute the transaction tx = self.sign_transaction(account, tx.tx) # post the transaction self.broadcast_transaction(tx.tx, tx_hash=tx.hash) return tx
def spend(self, account: Account, recipient_id: str, amount, payload: str = "", fee: int = defaults.FEE, tx_ttl: int = defaults.TX_TTL) -> transactions.TxObject: """ Create and execute a spend transaction, automatically retrieve the nonce for the siging account and calculate the absolut ttl. :param account: the account signing the spend transaction (sender) :param recipient_id: the recipient address or name_id :param amount: the amount to spend :param payload: the payload for the transaction :param fee: the fee for the transaction (automatically calculated if not provided) :param tx_ttl: the transaction ttl expressed in relative number of blocks :return: the TxObject of the transaction :raises TypeError: if the recipient_id is not a valid name_id or address """ if utils.is_valid_aens_name(recipient_id): recipient_id = hashing.name_id(recipient_id) elif not utils.is_valid_hash(recipient_id, prefix="ak"): raise TypeError("Invalid recipient_id. Please provide a valid AENS name or account pub_key.") # parse amount and fee amount, fee = utils._amounts_to_aettos(amount, fee) # retrieve the nonce account.nonce = self.get_next_nonce(account.get_address()) if account.nonce == 0 else account.nonce + 1 # retrieve ttl tx_ttl = self.compute_absolute_ttl(tx_ttl) # build the transaction tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl.absolute_ttl, account.nonce) # get the signature tx = self.sign_transaction(account, tx) # post the signed transaction transaction self.broadcast_transaction(tx) return tx
def transfer_funds(self, account: Account, recipient_id: str, percentage: float, payload: str = "", tx_ttl: int = defaults.TX_TTL, fee: int = defaults.FEE, include_fee=True): """ Create and execute a spend transaction """ if utils.is_valid_aens_name(recipient_id): recipient_id = hashing.name_id(recipient_id) elif not utils.is_valid_hash(recipient_id, prefix="ak"): raise TypeError("Invalid recipient_id. Please provide a valid AENS name or account pub_key.") if percentage < 0 or percentage > 1: raise ValueError(f"Percentage should be a number between 0 and 1, got {percentage}") # parse amounts fee = utils.amount_to_aettos(fee) # retrieve the balance account_on_chain = self.get_account_by_pubkey(pubkey=account.get_address()) request_transfer_amount = int(account_on_chain.balance * percentage) # retrieve the nonce account.nonce = account_on_chain.nonce + 1 # retrieve ttl tx_ttl = self.compute_absolute_ttl(tx_ttl) # build the transaction tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, request_transfer_amount, payload, fee, tx_ttl.absolute_ttl, account.nonce) # if the request_transfer_amount should include the fee keep calculating the fee if include_fee: amount = request_transfer_amount while (amount + tx.data.fee) > request_transfer_amount: amount = request_transfer_amount - tx.data.fee tx = self.tx_builder.tx_spend(account.get_address(), recipient_id, amount, payload, fee, tx_ttl.absolute_ttl, account.nonce) # execute the transaction tx = self.sign_transaction(account, tx) # post the transaction self.broadcast_transaction(tx) return tx
def _delegate_common(self, account: Account, *kwargs): """ Utility method to create a delegate signature for a contract Args: account: the account authorizing the transaction kwargs: the list of additional entity ids to be added to the data to be signed Returns: the signature to use for delegation """ sig_data = self.config.network_id.encode("utf8") + hashing.decode(account.get_address()) for _id in kwargs: sig_data += hashing.decode(_id) # sign the data sig = account.sign(sig_data) return sig
def sign_transaction(self, account: Account, tx: transactions.TxObject, metadata: dict = {}, **kwargs) -> transactions.TxObject: """ The function sign a transaction to be broadcast to the chain. It automatically detect if the account is Basic or GA and return the correct transaction to be broadcast. :param account: the account signing the transaction :param tx: the transaction to be signed :param metadata: additional metadata to maintain in the TxObject :param `**kwargs`: for GA accounts, see below Kwargs: :auth_data (str): the encoded calldata for the GA auth function :gas (int): the gas limit for the GA auth function [optional] :gas_price (int): the gas price for the GA auth function [optional] :fee (int): the fee for the GA transaction [optional, automatically calculated] :abi_version (int): the abi_version to select FATE or AEVM [optional, default 3/FATE] :ttl (str): the transaction ttl expressed in relative number of blocks [optional, default 0/max_ttl]: :return: a TxObject of the signed transaction or metat transaction for GA :raises TypeError: if the auth_data is missing and the account is GA :raises TypeError: if the gas for auth_func is gt defaults.GA_MAX_AUTH_FUN_GAS """ # first retrieve the account from the node # so we can check if it is generalized or not on_chain_account = self.get_account(account.get_address()) # if the account is not generalized sign and return the transaction if not on_chain_account.is_generalized(): s = transactions.TxSigner(account, self.config.network_id) signature = s.sign_transaction(tx, metadata) return self.tx_builder.tx_signed([signature], tx, metadata=metadata) # if the account is generalized then prepare the ga_meta_tx # 1. wrap the tx into a signed tx (without signatures) sg_tx = self.tx_builder.tx_signed([], tx) # 2. wrap the tx into a ga_meta_tx # get the absolute ttl ttl = self.compute_absolute_ttl(kwargs.get("ttl", defaults.TX_TTL)).absolute_ttl # get abi version _, abi = self.get_vm_abi_versions() # check that the parameter auth_data is provided auth_data = kwargs.get("auth_data") if auth_data is None: raise TypeError("the auth_data parameter is required for ga accounts") # verify the gas amount TODO: add a tx verification gas = kwargs.get("gas", defaults.GA_MAX_AUTH_FUN_GAS) if gas > defaults.GA_MAX_AUTH_FUN_GAS: raise TypeError(f"the maximum gas value for ga auth_fun is {defaults.GA_MAX_AUTH_FUN_GAS}, got {gas}") # build the ga_sg_tx = self.tx_builder.tx_ga_meta( account.get_address(), auth_data, kwargs.get("abi_version", abi), kwargs.get("fee", defaults.FEE), gas, kwargs.get("gas_price", defaults.CONTRACT_GAS_PRICE), ttl, sg_tx ) # 3. wrap the the ga into a signed transaction sg_ga_sg_tx = self.tx_builder.tx_signed([], ga_sg_tx, metadata=metadata) return sg_ga_sg_tx