class FetchAIApi(LedgerApi, FetchAIHelper): """Class to interact with the Fetch ledger APIs.""" identifier = _FETCHAI def __init__(self, **kwargs): """ Initialize the Fetch.AI ledger APIs. :param kwargs: key word arguments (expects either a pair of 'host' and 'port' or a 'network') """ if not ("host" in kwargs and "port" in kwargs): network = kwargs.pop("network", DEFAULT_NETWORK) kwargs["network"] = network with warnings.catch_warnings(): warnings.simplefilter("ignore") self._api = FetchaiLedgerApi(**kwargs) @property def api(self) -> FetchaiLedgerApi: """Get the underlying API object.""" return self._api def get_balance(self, address: Address) -> Optional[int]: """ Get the balance of a given account. :param address: the address for which to retrieve the balance. :return: the balance, if retrivable, otherwise None """ balance = self._try_get_balance(address) return balance @try_decorator("Unable to retrieve balance: {}", logger_method="debug") def _try_get_balance(self, address: Address) -> Optional[int]: """Try get the balance.""" return self._api.tokens.balance(FetchaiAddress(address)) def get_transfer_transaction( # pylint: disable=arguments-differ self, sender_address: Address, destination_address: Address, amount: int, tx_fee: int, tx_nonce: str, **kwargs, ) -> Optional[Any]: """ Submit a transfer transaction to the ledger. :param sender_address: the sender address of the payer. :param destination_address: the destination address of the payee. :param amount: the amount of wealth to be transferred. :param tx_fee: the transaction fee. :param tx_nonce: verifies the authenticity of the tx :return: the transfer transaction """ tx = TokenTxFactory.transfer( FetchaiAddress(sender_address), FetchaiAddress(destination_address), amount, tx_fee, [], # we don't add signer here as we would need the public key for this ) self._api.set_validity_period(tx) return tx def send_signed_transaction(self, tx_signed: Any) -> Optional[str]: """ Send a signed transaction and wait for confirmation. :param tx_signed: the signed transaction """ encoded_tx = transaction.encode_transaction(tx_signed) endpoint = "transfer" if tx_signed.transfers is not None else "create" return self.api.tokens._post_tx_json( # pylint: disable=protected-access encoded_tx, endpoint) def get_transaction_receipt(self, tx_digest: str) -> Optional[Any]: """ Get the transaction receipt for a transaction digest (non-blocking). :param tx_digest: the digest associated to the transaction. :return: the tx receipt, if present """ tx_receipt = self._try_get_transaction_receipt(tx_digest) return tx_receipt @try_decorator("Error when attempting getting tx receipt: {}", logger_method="debug") def _try_get_transaction_receipt(self, tx_digest: str) -> Optional[Any]: """ Get the transaction receipt (non-blocking). :param tx_digest: the transaction digest. :return: the transaction receipt, if found """ return self._api.tx.status(tx_digest) def get_transaction(self, tx_digest: str) -> Optional[Any]: """ Get the transaction for a transaction digest. :param tx_digest: the digest associated to the transaction. :return: the tx, if present """ tx = self._try_get_transaction(tx_digest) return tx @try_decorator("Error when attempting getting tx: {}", logger_method="debug") def _try_get_transaction(self, tx_digest: str) -> Optional[TxContents]: """ Try get the transaction (non-blocking). :param tx_digest: the transaction digest. :return: the tx, if found """ return cast(TxContents, self._api.tx.contents(tx_digest)) def get_contract_instance(self, contract_interface: Dict[str, str], contract_address: Optional[str] = None) -> Any: """ Get the instance of a contract. :param contract_interface: the contract interface. :param contract_address: the contract address. :return: the contract instance """ raise NotImplementedError def get_deploy_transaction( self, contract_interface: Dict[str, str], deployer_address: Address, **kwargs, ) -> Dict[str, Any]: """ Get the transaction to deploy the smart contract. :param contract_interface: the contract interface. :param deployer_address: The address that will deploy the contract. :returns tx: the transaction dictionary. """ raise NotImplementedError
def main(): # create the APIs api = LedgerApi(HOST, PORT) # we generate an identity from a known key, which contains funds. multi_sig_identity = Entity.from_hex( "6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b") # generate a board to control multi-sig account, with variable voting weights board = [ Entity.from_hex( "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8" ), Entity.from_hex( "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860" ), Entity.from_hex( "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3" ), Entity.from_hex( "d5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb" ), ] voting_weights = { board[0]: 1, board[1]: 1, board[2]: 1, board[3]: 2, } # generate another entity as a target for transfers other_identity = Entity.from_hex( "7da0e3fa62a916238decd4f54d43301c809595d66dd469f82f29e076752b155c") # submit deed print("\nCreating deed...") deed = Deed() for sig, weight in voting_weights.items(): deed.set_signee(sig, weight) deed.set_operation(Operation.amend, 4) deed.set_operation(Operation.transfer, 3) api.sync(api.tokens.deed(multi_sig_identity, deed, 500)) # display balance before print("\nBefore remote-multisig transfer") print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) print() # scatter/gather example print("Generating transaction and distributing to signers...") # add intended signers to transaction ref_tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, signatories=board) api.set_validity_period(ref_tx) # make a reference payload that can be used in this script for validation reference_payload = ref_tx.encode_payload() # have signers individually sign transaction signed_txs = [] for signer in board: # signer builds their own transaction to compare to note that each of the signers will need to agree on all # parts of the message including the validity period and the counter signer_tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, signatories=board) signer_tx.counter = ref_tx.counter signer_tx.valid_until = ref_tx.valid_until signer_tx.valid_from = ref_tx.valid_from # sanity check each of the signers payload should match the reference payload assert signer_tx.encode_payload() == reference_payload # signers locally sign there version of the transaction signer_tx.sign(signer) # simulate distribution of signed partial transactions signed_txs.append(signer_tx.encode_partial()) # gather and encode final transaction - this step in theory can be done by all the signers provided they are # received all the signature shares print("Gathering and combining signed transactions...") partial_txs = [Transaction.decode_partial(s)[1] for s in signed_txs] # merge them together into one fully signed transaction success, tx = Transaction.merge(partial_txs) assert success # this indicates that all the signatures have been merged and that the transaction now validates # submit the transaction api.sync(api.submit_signed_tx(tx)) print("\nAfter remote multisig-transfer") print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # round-robin example print("\nGenerating transaction and sending down the line of signers...") # create the basis for the transaction tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, signatories=board) api.set_validity_period(tx) # serialize and send to be signed tx_payload = tx.encode_payload() # have signers individually sign transaction and pass on to next signer for signer in board: # build the target transaction signer_tx = Transaction.decode_payload(tx_payload) # Signer decodes payload to inspect transaction signer_tx.sign(signer) # ensure that when we merge the signers signature into the payload that it is correct assert tx.merge_signatures(signer_tx) # once all the partial signatures have been merged then it makes sense print("Collecting final signed transaction...") assert tx.is_valid() api.sync(api.submit_signed_tx(tx)) print("\nAfter remote multisig-transfer") print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity))