def main(): # In examples we use addresses which already have funds entity1 = Entity.from_hex( '6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b') entity2 = Entity.from_hex( 'e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8') address1 = Address(entity1) # create a second private key pair address2 = Address(entity2) # build the ledger API api = LedgerApi('127.0.0.1', 8000) # create the smart contract contract = Contract(CONTRACT_TEXT, entity1) with track_cost(api.tokens, entity1, "Cost of creation: "): api.sync(contract.create(api, entity1, 4000)) # print the current status of all the tokens print('-- BEFORE --') print_address_balances(api, contract, [address1, address2]) # transfer from one to the other using our newly deployed contract tok_transfer_amount = 200 fet_tx_fee = 160 with track_cost(api.tokens, entity1, "Cost of transfer: "): api.sync( contract.action(api, 'transfer', fet_tx_fee, entity1, address1, address2, tok_transfer_amount)) print('-- AFTER --') print_address_balances(api, contract, [address1, address2])
def run(options): ENDPOINT = 'fetch/token/transfer' HOST = options['host'] PORT = options['port'] # create the APIs api = LedgerApi(HOST, PORT) # Hardcoded private keys id1PrivateKey = "d222867ac019aa7e5b5946ee23100b4437d54ec08db8e89f6e459d497b579a03" id2PrivateKey = "499f40c1bf13e7716e62431b17ab668fa2688b7c94a011a3aab595477bc68347" # Create identities from the private keys id1 = Entity.from_hex(id1PrivateKey) id2 = Entity.from_hex(id2PrivateKey) # Load 1000 tokens to id1 api.sync(api.tokens.wealth(id1, 1000)) # signed transaction that transfers 250 FET from id1 to id2. Signed with (r,s) orig_tx = 'a1440000c5ab20e3ab845cb4a1d2c3e4c3b08f5ff42a6ff2a71d7697ba8f32c415b77c7f8d850b3ef025b189a2d9bb4a515a84c3673db6d3ef25385d2c8d1e34b06e2de1c0fac08501140000000000000000040bafbc61a08524372f495d9dee08adbc39824e980506947091395cece16636ddc094b9d409d5b34ef0bbd9c99c5caf21fc373802472cf96a8a280f84e833f992402192a0a125d551b60800d441cb3483cd36573c22a73f22563d6dd7b27e677b98ba4e2f77596888839a3c6f2439c97949ea28923f168d360a6d155c2be79570af' # signed Malicious transaction of the previous transaction. Signed with (r,n-s) mal_tx = 'a1440000c5ab20e3ab845cb4a1d2c3e4c3b08f5ff42a6ff2a71d7697ba8f32c415b77c7f8d850b3ef025b189a2d9bb4a515a84c3673db6d3ef25385d2c8d1e34b06e2de1c0fac08501140000000000000000040bafbc61a08524372f495d9dee08adbc39824e980506947091395cece16636ddc094b9d409d5b34ef0bbd9c99c5caf21fc373802472cf96a8a280f84e833f99240f1a0311cc9b42edaa41d537e3d1f9d54ac159504276c27be2973cea85c8e8a17148a2b1dc3e188f911c6b05f163d95b964ae594a347870973e59b3c93d35b895' # Creating jsons for the above mentioned transactions legit_trans = submit_json_transaction( host=HOST, port=PORT, tx_data=dict(ver="1.2", data=base64.b64encode( binascii.unhexlify(orig_tx)).decode()), endpoint=ENDPOINT) mal_trans = submit_json_transaction( host=HOST, port=PORT, tx_data=dict(ver="1.2", data=base64.b64encode( binascii.unhexlify(mal_tx)).decode()), endpoint=ENDPOINT) # Sending the transactions to the ledger assert legit_trans == mal_trans, "Malleable transactions have different transaction hash" api.sync([legit_trans, mal_trans]) # If transaction malleability is feasible, id2 should have 500 FET. # If balance of id2 is more than 250 raise an exception assert api.tokens.balance( id2) == 250, "Vulnerable to transaction malleability attack"
def run(options, benefactor): ENDPOINT = 'fetch/token/transfer' HOST = options['host'] PORT = options['port'] # create the APIs api = LedgerApi(HOST, PORT) # Hardcoded private keys id1PrivateKey = "d222867ac019aa7e5b5946ee23100b4437d54ec08db8e89f6e459d497b579a03" id2PrivateKey = "499f40c1bf13e7716e62431b17ab668fa2688b7c94a011a3aab595477bc68347" # Create identities from the private keys id1 = Entity.from_hex(id1PrivateKey) id2 = Entity.from_hex(id2PrivateKey) # Load 100000 tokens to id1 api.sync(api.tokens.transfer(benefactor, id1, 100000, 1000)) # signed transaction that transfers 2500 FET from id1 to id2. Signed with (r,s) Fees are 1000 orig_tx = 'a1640000c5ab20e3ab845cb4a1d2c3e4c3b08f5ff42a6ff2a71d7697ba8f32c415b77c7f8d850b3ef025b189a2d9bb4a515a84c3673db6d3ef25385d2c8d1e34b06e2de1c109c46501c103e8565596cd793442c5040bafbc61a08524372f495d9dee08adbc39824e980506947091395cece16636ddc094b9d409d5b34ef0bbd9c99c5caf21fc373802472cf96a8a280f84e833f99240f1c72a58927153d6fdc19f178f69c5b02db29f33541e2b946c78a54ce693c7c81082f22531b85a1707cf290f73fddd2df88681084b0cc7aff38e977215ae5899' # signed Malicious transaction of the previous transaction. Signed with (r,n-s) mal_tx = 'a1640000c5ab20e3ab845cb4a1d2c3e4c3b08f5ff42a6ff2a71d7697ba8f32c415b77c7f8d850b3ef025b189a2d9bb4a515a84c3673db6d3ef25385d2c8d1e34b06e2de1c109c46501c103e8565596cd793442c5040bafbc61a08524372f495d9dee08adbc39824e980506947091395cece16636ddc094b9d409d5b34ef0bbd9c99c5caf21fc373802472cf96a8a280f84e833f99240cb13a3a05600cd45c7abe860515801941d6eef439e697ab31015543b515e5b85495f2a3f95a352747eda205cc70e0bca7a4d8b1240dfbd43f3a047a1faad30bd' # Creating jsons for the above mentioned transactions legit_trans = submit_json_transaction( host=HOST, port=PORT, tx_data=dict(ver="1.2", data=base64.b64encode( binascii.unhexlify(orig_tx)).decode()), endpoint=ENDPOINT) mal_trans = submit_json_transaction( host=HOST, port=PORT, tx_data=dict(ver="1.2", data=base64.b64encode( binascii.unhexlify(mal_tx)).decode()), endpoint=ENDPOINT) # Sending the transactions to the ledger assert legit_trans == mal_trans, "Malleable transactions have different transaction hash" api.sync([legit_trans, mal_trans]) # If transaction malleability is feasible, id2 should have 500 FET. # If balance of id2 is more than 250 raise an exception assert api.tokens.balance( id2) == 2500, "Vulnerable to transaction malleability attack"
def generate_fetchai_wealth(arguments: argparse.Namespace) -> None: """ Generate tokens to be able to make a transaction. :param arguments: the arguments :return: None """ try: api = LedgerApi(arguments.addr, arguments.port) except Exception: logging.info("Couldn't connect! Please check your add and port.") sys.exit(1) try: if arguments.private_key is None or arguments.private_key == "": raise ValueError except ValueError: logging.error("Please provide a private key. --private-key .... ") sys.exit(1) logging.info("Waiting for token wealth generation...") entity_to_generate_wealth = Entity.from_hex( Path(arguments.private_key).read_text()) api.sync(api.tokens.wealth(entity_to_generate_wealth, arguments.amount)) address = Address(entity_to_generate_wealth) balance = api.tokens.balance(address) logging.info('The new balance of the address {} is : {} FET'.format( address, balance))
def main(): # create the API api = LedgerApi('127.0.0.1', 8000) # create an entity from a private key stored in hex entity = Entity.from_hex( '6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b') # create the contract on the ledger synergetic_contract = Contract(CONTRACT_TEXT, entity) print('Creating contract..') api.sync(api.contracts.create(entity, synergetic_contract, 4096)) # create a whole series of random data to submit to the DAG random_ints = [random.randint(0, 200) for _ in range(10)] fee = 100000000 api.sync( [api.contracts.submit_data(entity, synergetic_contract.digest, synergetic_contract.address, fee, value=value) \ for value in random_ints]) print('Data submitted.') print('Waiting...') api.wait_for_blocks(10) print('Issuing query...') result = synergetic_contract.query(api, 'query_result') print('Query result:', result)
def main(): entity = Entity.from_hex( '6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b') address = Address(entity) print('Address:', address) # create the APIs api = LedgerApi(HOST, PORT) # Display balance before print('Balance:', api.tokens.balance(entity)) print('Stake..:', api.tokens.stake(entity)) # submit and wait for the transfer to be complete print('Submitting stake request...') api.sync(api.tokens.add_stake(entity, 1000, 50)) while True: print('Balance............:', api.tokens.balance(entity)) print('Stake..............:', api.tokens.stake(entity)) print('Stake on cooldown..:', api.tokens.stake_cooldown(entity)) # De-stake half of the staked balance to_destake = int(api.tokens.stake(entity) / 2) api.sync(api.tokens.de_stake(entity, to_destake, 500)) # Collect cooled down stakes api.sync(api.tokens.collect_stake(entity, 500)) time.sleep(1)
def main(): # create the APIs api = LedgerApi(HOST, PORT) # generate identities from hex private keys, identity1 = Entity.from_hex('6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b') identity2 = Entity.from_hex('e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8') print('Balance 1 Before:', api.tokens.balance(identity1)) print('Balance 2 Before:', api.tokens.balance(identity2)) # submit and wait for the transfer to be complete print('Submitting transfer...') api.sync(api.tokens.transfer(identity1, identity2, 250, 20)) print('Balance 1:', api.tokens.balance(identity1)) print('Balance 2:', api.tokens.balance(identity2))
def _try_validate_fet_private_key_path(private_key_path: str) -> None: """ Try to validate a private key. :param private_key_path: the path to the private key. :return: None :raises: an exception if the private key is invalid. """ try: with open(private_key_path, "r") as key: data = key.read() Entity.from_hex(data) except Exception as e: logger.error( "This is not a valid private key file: '{}'\n Exception: '{}'". format(private_key_path, e)) sys.exit(1)
def main(): api = LedgerApi(HOST, PORT) # in our examples we use Addresses with funds, which we load from hex-encoded private keys. identity1 = Entity.from_hex('6e8339a0c6d51fc58b4365bf2ce18ff2698d2b8c40bb13fcef7e1ba05df18e4b') identity2 = Entity.from_hex('e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8') # we make a Transfer which returns a transaction id. tx = api.tokens.transfer(identity1, identity2, 2500, 20) # wait for the transaction to complete so that the information is 100% present api.sync(tx) # we Verify that the transaction is the submitted transaction is the sent transaction # TxContents object (below contents variable) contains all properties sent to ledger in transaction API call contents = api.tx.contents(tx) # below we access a subset of the properties of our TxContents object valid_until = contents.valid_until valid_from = contents.valid_from from_address = contents.from_address transfers = contents.transfers # iterate over the transfers in the transaction, which is singular in this instance for to_address, amount in transfers.items(): print( "\nThe submitted transaction is from Address: {}\nto Address: {} \nof amount: {}\nand is valid from (block number): {} \nand valid until (block number): {}".format( str(from_address), to_address, amount, valid_from, valid_until)) nonexistent_entity = Entity() # check the amount being transferred to a particular address; zero in the below instance amount = contents.transfers_to(nonexistent_entity) if amount == 0: print("\nAs expected, nothing is being transferred to Address: ", str(Address(nonexistent_entity))) else: print("\nExample failure: nothing should be transferred to Address: ", str(Address(nonexistent_entity))) # check the status of a transaction. This is an alternative to calling the LedgerApi's sync method, which itself polls the below status endpoint status = api.tx.status(tx) print('\nCurrent Status is :', status.status)
def __init__(self, *args, **kwargs): super(RiderAgent, self).__init__(*args, **kwargs) riders = [ '904f302f980617d5f40219337ef826cdf64992e577676cdd83295f189af82ff4', '03f807bbf02cf849145fc51d7dd4438559dc4756a3b2321bbdf42e8f7910a3df', 'dfe06a3baa93ad1d2f248317c601f710821cc1916e09d7c6e261f432563e50f1', 'a92e7c9a1c091bb7bc70874da36fdc44a8217577758a061e008344bd402a6118', '672ebe1ef50a3c49532fe2118686d7025048de51e4f77ed8d0880cd52efe80a7'] self._entity = Entity.from_hex(riders[int(sys.argv[1])-1]) self._address = Address(self._entity) self._api = LedgerApi('127.0.0.1', 8000) self._api.sync(self._api.tokens.wealth(self._entity, 5000000))
def load_private_key_from_path(cls, file_name: str) -> Entity: """ Load a private key in hex format from a file. :param file_name: the path to the hex file. :return: the Entity. """ path = Path(file_name) with path.open() as key: data = key.read() entity = Entity.from_hex(data) return entity
def main(): # create our first private key pair entity1 = Entity.from_hex( 'd5f10ad865fff147ae7fcfdc98b755452a27a345975c8b9b3433ff16f23495fb') # build the ledger API api = LedgerApi('127.0.0.1', 8100) # create the smart contract contract = Contract(CONTRACT_TEXT, entity1) # deploy the contract to the network api.sync(api.contracts.create(entity1, contract, 1000000000)) # update the graph with a new model fet_tx_fee = 100000000 with open(GRAPH_FILE_NAME, mode='rb') as file: print("reading in graph file...") rfile = file.read() print("encoding to base64 string...") b64obj = base64.b64encode(rfile) obj = b64obj.decode() print("updating smart contract graph...") api.sync( contract.action(api, 'updateGraph', fet_tx_fee, [entity1], obj)) print("finished updating smart contract graph") # set one real example input data set fet_tx_fee = 100000000 api.sync( contract.action(api, 'setHistorics', fet_tx_fee, [entity1], EXAMPLE_INPUT_HISTORICS)) current_historics = contract.query(api, 'getHistorics') print("current historics: " + current_historics) # make a prediction current_prediction = contract.query(api, 'makePrediction') print("current prediction: " + current_prediction)
def _load_private_key_from_path(self, file_name) -> Entity: """ Load a private key in hex format from a file. :param file_name: the path to the hex file. :return: the Entity. """ path = Path(file_name) try: if path.is_file(): with open(path, "r") as key: data = key.read() entity = Entity.from_hex(data) else: entity = self._generate_private_key() return entity except IOError as e: # pragma: no cover logger.exception(str(e))
def main(): # create the APIs api = LedgerApi(HOST, PORT) # 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. # we use keys for accounts which already have funds. board = [] board.append( Entity.from_hex( "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8") ) board.append( Entity.from_hex( "4083a476c4872f25cb40839ac8d994924bcef12d83e2ba4bd3ed6c9705959860") ) board.append( Entity.from_hex( "20293422c4b5faefba3422ed436427f2d37f310673681e98ac8637b04e756de3") ) board.append( 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( "e833c747ee0aeae29e6823e7c825d3001638bc30ffe50363f8adf2693c3286f8") print('Original balance of multi_sig_identity:', api.tokens.balance(multi_sig_identity)) # transfers can happen normally without a deed print('\nSubmitting pre-deed transfer with original signature...') api.sync(api.tokens.transfer(multi_sig_identity, other_identity, 250, 20)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # submit the original deed print("\nCreating deed...") deed = Deed() for sig, weight in voting_weights.items(): deed.set_signee(sig, weight) # set our initial voting thresholds deed.set_operation(Operation.transfer, 2) deed.set_operation(Operation.amend, 4) api.sync(api.tokens.deed(multi_sig_identity, deed, 6000)) # original address can no longer validate transfers print("\nTransfer with original signature should fail...") try: api.sync( api.tokens.transfer(multi_sig_identity, other_identity, 250, 20)) except RuntimeError as e: print("Transaction failed as expected") else: print("Transaction succeeded, it shouldn't have") # sufficient voting power required to sign transfers print("\nSubmitting transfer with two signatures with total 2 votes...") print_signing_votes(voting_weights, board[:2]) # since we now want to create a transaction which has only been signed by a subset of the board, we must use # the factory interface in order to build out the transaction we are after tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, board[:2]) tx.valid_until = api.tokens.current_block_number() + 100 # the signatories to sign the transaction for signatory in board[:2]: tx.sign(signatory) api.sync(api.submit_signed_tx(tx)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # some entities may have more voting power print("\nSubmitting transfer with single signature with 2 votes...") print_signing_votes(voting_weights, board[3]) tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, [board[3]]) tx.valid_until = api.tokens.current_block_number() + 100 tx.sign(board[3]) api.sync(api.submit_signed_tx(tx)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # amend the deed print("\nAmending deed to increase transfer threshold to 3 votes...") deed.set_operation(Operation.transfer, 3) tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board) tx.valid_until = api.tokens.current_block_number() + 100 for member in board: tx.sign(member) api.sync(api.submit_signed_tx(tx)) # single member no longer has enough voting power print("\nSingle member transfer with 2 votes should no longer succeed...") try: print_signing_votes(voting_weights, board[3]) tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, [board[3]]) tx.valid_until = api.tokens.current_block_number() + 100 tx.sign(board[3]) api.sync(api.submit_signed_tx(tx)) except RuntimeError as e: print("Transaction failed as expected") else: print("Transaction succeeded, it shouldn't have") # correct number of signatory votes print("\nSuccesful transaction with sufficient voting weight...") print_signing_votes(voting_weights, board[1:]) tx = TokenTxFactory.transfer(multi_sig_identity, other_identity, 250, 20, board[1:]) tx.valid_until = api.tokens.current_block_number() + 100 for member in board[1:]: tx.sign(member) api.sync(api.submit_signed_tx(tx)) print('Balance 1:', api.tokens.balance(multi_sig_identity)) print('Balance 2:', api.tokens.balance(other_identity)) # warning: if no amend threshold is set, future amendments are impossible print("\nAmending deed to remove threshold...") deed.remove_operation(Operation.amend) deed.require_amend = False tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board) tx.valid_until = api.tokens.current_block_number() + 100 for member in board: tx.sign(member) api.sync(api.submit_signed_tx(tx)) deed.set_operation(Operation.amend, 1) print("\nExpecting further amendment to fail...") try: tx = TokenTxFactory.deed(multi_sig_identity, deed, 400, board) tx.valid_until = api.tokens.current_block_number() + 100 for member in board: tx.sign(member) api.sync(api.submit_signed_tx(tx)) except RuntimeError as e: print("Transaction failed as expected") else: print("Transaction succeeded, it shouldn't have")
from fetchai.ledger.api import LedgerApi from fetchai.ledger.contract import SmartContract from fetchai.ledger.crypto import Entity, Address import time contract_owner = Entity.from_hex( 'c25ace8a7a485b396f30e4a0332d0d18fd2e462b3f1404f85a1b7bcac4b4b19d') contract_owner_address = Address( contract_owner) # atsREugsanXS828FnTvmLM9vCkBsWnDgushDH9YjEgsdBuRGv with open("./cbns_token.etch", "r") as fb: contract_source = fb.read() api = LedgerApi('127.0.0.1', 8000) api.sync(api.tokens.wealth(contract_owner, 5000000)) contract = SmartContract(contract_source) api.sync(api.contracts.create(contract_owner, contract, 2456766)) time.sleep(10) print('Deployed contract address:', Address(contract.digest)) print('my balance:', contract.query(api, 'balanceOf', owner=contract_owner_address))
def __init__(self, *args, **kwargs): super(ChargerAgent, self).__init__(*args, **kwargs) self._entity = Entity.from_hex(sys.argv[5]) self._address = Address(self._entity)
def on_message(self, msg_id: int, dialogue_id: int, origin: str, content: bytes): """Extract and print data from incoming (simple) messages.""" # PLACE HOLDER TO SIGN AND SUBMIT TRANSACTION transaction = json.loads(content.decode("utf-8")) charge_station_address = Address(binascii.unhexlify(transaction['address'])) print("Received contract from {0}".format(origin)) print("READY TO SUBMIT:", origin, "(", charge_station_address, ") price:", transaction['value'], "bonus:", transaction['bonus']) #self._api.sync(self._contract.action(self._api, 'transfer', 40, [self._entity], self._address, charge_station_address, transaction['value'])) self._api.sync(self._api.contracts.action(contract_addr, contract_owner, 'transfer', 40, [self._entity], self._address, charge_station_address, transaction['value'])) #time.sleep(10) #print(self._contract.query(self._api, 'balanceOf', owner=charge_station_address)) #print(self._api.contracts.query(contract_addr, contract_owner, 'balanceOf', owner=charge_station_address)) #print(self._api.contracts.query(contract_addr, contract_owner, 'balanceOf', owner=self._address)) self._api.sync(self._api.contracts.action(Address('QguxAD9FTj2MTnvNbdr15MGPMftXW8qdcewa4X96JEx2SU6hg'), contract_owner, 'transfer', 40, [Entity.from_hex('c25ace8a7a485b396f30e4a0332d0d18fd2e462b3f1404f85a1b7bcac4b4b19d')], contract_owner, self._address, transaction['value'] + transaction['bonus'])) self.stop()
from fetchai.ledger.serialisation.transaction import encode_payload from fetchai.ledger.bitvector import BitVector from fetchai.ledger.crypto import Entity, Identity from fetchai.ledger.serialisation import encode_transaction, decode_transaction, bytearray, sha256_hash from fetchai.ledger.transaction import Transaction _PRIVATE_KEYS = ( '1411d53f88e736eac7872430dbe5b55ac28c17a3e648c388e0bd1b161ab04427', '3436c184890d498b25bc2b5cb0afb6bad67379ebd778eae1de40b6e0f0763825', '4a56a19355f934174f6388b3c80598abb151af79c23d5a7af45a13357fb71253', 'f9d67ec139eb7a1cb1f627357995847392035c1e633e8530de5ab5d04c6e9c33', '80f0e1c69e5f1216f32647c20d744c358e0894ebc855998159017a5acda208ba', ) ENTITIES = [Entity.from_hex(x) for x in _PRIVATE_KEYS] IDENTITIES = [Identity(x) for x in ENTITIES] def _calculate_integer_stream_size(length: int) -> int: if length < 0x80: return 1 elif length < 0x100: return 2 elif length < 0x1000: return 4 else: return 8 class TransactionSerialisation(unittest.TestCase):
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))