def public_key(self, value): if value is None: self._public_key = None return if not isinstance(value, PublicKey): raise TypeError("cannot assign value of type {} as BotRegistration's public key (expected type: PublicKey)".format(type(value))) self._public_key = PublicKey(specifier=value.specifier, hash=value.hash)
def record_get(self, identifier): """ Get a 3Bot record registered on a TFchain network @param identifier: unique 3Bot id, public key or (bot) name to search a 3Bot record for """ endpoint = "/explorer/3bot" if isinstance(identifier, int): identifier = str(identifier) elif isinstance(identifier, BotName): endpoint = "/explorer/whois/3bot" identifier = str(identifier) elif isinstance(identifier, PublicKey): identifier = str(identifier) elif isinstance(identifier, str): if BotName.REGEXP.match(identifier) is not None: endpoint = "/explorer/whois/3bot" else: try: PublicKey.from_json(identifier) except ValueError as exc: raise ValueError( "a 3Bot identifier in string format has to be either a valid BotName or PublicKey, '{}' is neither" .format(identifier)) from exc else: raise TypeError("identifier of type {} is invalid".format( type(identifier))) # identifier is a str at this point # and endpoint is configured # fetch the data endpoint += "/{}".format(identifier) try: resp = self._client.explorer_get(endpoint=endpoint) except tfchain.errors.ExplorerNoContent as exc: raise tfchain.errors.ThreeBotNotFound(identifier) from exc resp = json_loads(resp) try: # return the fetched record as a named tuple, for easy semi-typed access record = resp['record'] return ThreeBotRecord( identifier=int(record['id']), names=[ BotName.from_json(name) for name in record.get('names', []) or [] ], addresses=[ NetworkAddress.from_json(address) for address in record.get('addresses', []) or [] ], public_key=PublicKey.from_json(record['publickey']), expiration=int(record['expiration']), ) except KeyError as exc: # return a KeyError as an invalid Explorer Response raise tfchain.errors.ExplorerInvalidResponse( str(exc), endpoint, resp) from exc
def parent_public_key(self, value): if value is None: self._parent_public_key = None return if not isinstance(value, PublicKey): raise TypeError("cannot assign value of type {} as BotRecordUpdateTransactions's parent public key (expected type: PublicKey)".format(type(value))) self._parent_public_key = PublicKey(specifier=value.specifier, hash=value.hash)
def receiver_parent_public_key(self, value): if value is None: self._receiver_parent_public_key = None return if not isinstance(value, PublicKey): raise TypeError("cannot assign value of type {} as BotNameTransferTransaction's receiver parent public key (expected type: PublicKey)".format(type(value))) self._receiver_parent_public_key = PublicKey(specifier=value.specifier, hash=value.hash)
def threebot_record_get(self, identifier, endpoint): """ Add a block response to the stub explorer at the given height. """ assert isinstance(identifier, str) if identifier.isdigit(): try: return self._threebot_records[int(identifier)] except KeyError as exc: raise tfchain.errors.ExplorerNoContent( "no 3Bot record could be found for identifier {}".format(identifier), endpoint=endpoint) from exc if BotName.REGEXP.match(identifier) is not None: for record in self._threebot_records.values(): for name in record.names: if name.value == identifier: return record raise tfchain.errors.ExplorerNoContent( "no content found for 3Bot identifier {}".format(identifier), endpoint=endpoint) # must be a public key pk = PublicKey.from_json(identifier) for record in self._threebot_records.values(): if record.public_key.unlockhash == pk.unlockhash: return record raise tfchain.errors.ExplorerNoContent( "no content found for 3Bot identifier {}".format(identifier), endpoint=endpoint)
def _from_json_data_object(self, data): # decode public key if 'pubkey' in data: self._public_key = PublicKey.from_json(data['pubkey']) else: self._public_key = None # decode signature if 'signature' in data: self._signature = ED25519Signature.from_json(data['signature']) else: self._signature = None # decode registration fee if 'regfee' in data: self.registration_fee = Currency.from_json(data['regfee']) else: self.registration_fee = None # decode transaction fee if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None # decode coin inputs self._coin_inputs = [ CoinInput.from_json(ci) for ci in data.get('coininputs', []) or [] ] # decode refund coin output (if it exists) if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json( data['refundcoinoutput']) else: self._refund_coin_output = None
def _from_json_data_object(self, data): self._addresses = [ NetworkAddress.from_json(address) for address in data.get('addresses', []) or [] ] self._names = [ BotName.from_json(name) for name in data.get('names', []) or [] ] self._number_of_months = int(data.get('nrofmonths', 0) or 0) if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None self._coin_inputs = [ CoinInput.from_json(ci) for ci in data.get('coininputs', []) or [] ] if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json( data['refundcoinoutput']) else: self._refund_coin_output = None if 'identification' not in data or data['identification'] in (None, {}): self._public_key = None self._signature = None else: identification = data['identification'] self._public_key = PublicKey.from_json(identification['publickey']) self._signature = ED25519Signature.from_json( identification['signature'], as_array=True)
def test(): # create a tfchain client for devnet c = tfchain.TFChainClient.TFChainClient(network_type="devnet") # (we replace internal client logic with custom logic as to ensure we can test without requiring an active network) explorer_client = TFChainExplorerGetClientStub() # add threebot record: explorer_client.threebot_record_add( ThreeBotRecord( identifier=3, names=[BotName(value="chatbot.example")], addresses=[NetworkAddress(address="example.org")], public_key=PublicKey.from_json( "ed25519:e4f55bc46b5feb37c03a0faa2d624a9ee1d0deb5059aaa9625d8b4f60f29bcab" ), expiration=1552581420, )) # overwrite the external get logic c.explorer_get = explorer_client.explorer_get # all the following will allow you to get the same 3Bot record for identifier in [ 3, BotName(value="chatbot.example"), "chatbot.example", "ed25519:e4f55bc46b5feb37c03a0faa2d624a9ee1d0deb5059aaa9625d8b4f60f29bcab", PublicKey.from_json( "ed25519:e4f55bc46b5feb37c03a0faa2d624a9ee1d0deb5059aaa9625d8b4f60f29bcab" ) ]: record = c.threebot.record_get(identifier) # the unique 3Bot identifier can be accessed assert record.identifier == 3 # all names (aliases of the 3Bot) can be accessed assert len(record.names) == 1 assert record.names[0].value == "chatbot.example" # all network addresses that point can be accessed assert len(record.addresses) == 1 assert record.addresses[0].value == "example.org" # public key can be accessed assert record.public_key.unlockhash == "01b73c4e869b6167abe6180ebe7a907f56e0357b4a2f65eb53d22baad84650eb62fce66ba036d0" # timestamp on which the 3Bot expires, unless more months are paid (see Record Update) assert record.expiration == 1552581420 # if the 3Bot cannot be found, the tfchain.errors.ThreeBotNotFound exception will be raised with pytest.raises(tfchain.errors.ThreeBotNotFound): c.threebot.record_get(1)
def public_key(self, value): if value == None: self._pub_key = None return if not isinstance(value, PublicKey): raise TypeError( "cannot assign value of type {} as FulfillmentAtomicSwap's public key (expected type: PublicKey)" .format(type(value))) self._pub_key = PublicKey(specifier=value.specifier, hash=value.hash)
def public_key(self, pk): if pk == None: self._public_key = None return if not isinstance(pk, PublicKey): raise TypeError( "cannot assign value of type {} as PublicKeySignaturePair's public key (expected type: PublicKey)" .format(type(pk))) self._public_key = PublicKey(specifier=pk.specifier, hash=pk.hash)
def parent_public_key(self): if self._parent_public_key is None: return PublicKey() return self._parent_public_key
def receiver_parent_public_key(self): if self._receiver_parent_public_key is None: return PublicKey() return self._receiver_parent_public_key
class TransactionV144(BotTransactionBaseClass): _SPECIFIER = b'bot register tx\0' def __init__(self): self._addresses = [] self._names = [] self._number_of_months = 0 self._transaction_fee = None self._coin_inputs = [] self._refund_coin_output = None self._public_key = None self._signature = None super().__init__() @property def version(self): return TransactionVersion.THREEBOT_REGISTRATION @property def required_bot_fees(self): """ The fees required to pay for this 3Bot Registration Transaction. """ # a static registration fee has to be paid fees = 0 + BotTransactionBaseClass.BOT_FEE_REGISTRATION # the amount of desired months also has to be paid fees += BotTransactionBaseClass.compute_monthly_bot_fees(self._number_of_months) # if more than one name is defined it also has to be paid lnames = len(self._names) if lnames > 1: fees += BotTransactionBaseClass.BOT_FEE_ADDITIONAL_NAME * (lnames-1) # no fee has to be paid for the used network addresses during registration # return the total fees return fees @property def coin_inputs(self): """ Coin inputs of this Transaction, used as funding for coin outputs, fees and any other kind of coin output. """ return self._coin_inputs @coin_inputs.setter def coin_inputs(self, value): self._coin_inputs = [] if not value: return for ci in value: self.coin_input_add(ci.parentid, ci.fulfillment, parent_output=ci.parent_output) @property def refund_coin_output(self): if self._refund_coin_output is None: return CoinOutput() return self._refund_coin_output @property def coin_outputs(self): """ Empty list, or a singleton with the refund coin output if that one exists. """ if self._refund_coin_output is None: return [] return [self._refund_coin_output] @coin_outputs.setter def coin_outputs(self, value): if isinstance(value, list): lvalue = len(value) if lvalue == 0: value = None elif lvalue == 1: value = value[0] else: raise ValueError("ThreeBot only can have one coin output, a refund coin output") if value is None: self._refund_coin_output = None elif isinstance(value, CoinOutput): self._refund_coin_output = CoinOutput(value=value.value, condition=value.condition) self._refund_coin_output.id = value.id else: raise TypeError("cannot assign a value of type {} to coin outputs".format(type(value))) def coin_input_add(self, parentid, fulfillment, parent_output=None): ci = CoinInput(parentid=parentid, fulfillment=fulfillment) ci.parent_output = parent_output self._coin_inputs.append(ci) def refund_coin_output_set(self, value, condition, id=None): co = CoinOutput(value=value, condition=condition) co.id = id self._refund_coin_output = co @property def addresses(self): """ Network addresses that will be part of the 3Bot Registration. """ return self._addresses @addresses.setter def addresses(self, value): self._addresses = [] if not value: return for address in value: self.address_add(address) def address_add(self, address): """ Add a NetworkAddress that will be added as part of the 3Bot Registration. """ if len(self._addresses) == BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT: raise Exception("a 3Bot can have a maximum of {} addresses, there is no more space for {} ({})".format( BotTransactionBaseClass.MAX_ADDRESSES_PER_BOT, address, type(address))) self._addresses.append(NetworkAddress(address=address)) @property def names(self): """ Bot names that will be part of the 3Bot Registration. """ return self._names @names.setter def names(self, value): self._names = [] if not value: return for name in value: self.name_add(name) def name_add(self, name): """ Add a BotName that will be added as part of the 3Bot Registration. """ if len(self._names) == BotTransactionBaseClass.MAX_NAMES_PER_BOT: raise Exception("a 3Bot can have a maximum of {} names, there is no more space for {} ({})".format( BotTransactionBaseClass.MAX_NAMES_PER_BOT, name, type(name))) self._names.append(BotName(value=name)) @property def transaction_fee(self): if self._transaction_fee is None: return Currency() return self._transaction_fee @transaction_fee.setter def transaction_fee(self, txfee): if txfee is None: self._transaction_fee = None else: self._transaction_fee = Currency(value=txfee) @property def miner_fees(self): if self._transaction_fee is None: return [] return [self._transaction_fee] @property def number_of_months(self): return self._number_of_months @number_of_months.setter def number_of_months(self, n): if n < 1 or n > 24: raise ValueError("number of months for a 3Bot Registration Transaction has to be in the inclusive range [1,24]") self._number_of_months = n @property def signature(self): if self._signature is None: return ED25519Signature(as_array=True) return self._signature @signature.setter def signature(self, value): if value is None: self._signature = None return self._signature = ED25519Signature(value=value, as_array=True) @property def public_key(self): if self._public_key is None: return PublicKey() return self._public_key @public_key.setter def public_key(self, value): if value is None: self._public_key = None return if not isinstance(value, PublicKey): raise TypeError("cannot assign value of type {} as BotRegistration's public key (expected type: PublicKey)".format(type(value))) self._public_key = PublicKey(specifier=value.specifier, hash=value.hash) def signature_add(self, public_key, signature): """ Implements SignatureCallbackBase. """ if self._public_key.unlockhash != public_key.unlockhash: raise ValueError("given public key ({}) does not equal public key ({})".format( str(self._public_key.unlockhash), str(public_key.unlockhash))) self.signature = signature def _signature_hash_input_get(self, *extra_objects): e = encoder_rivine_get() # encode the transaction version e.add_int8(self.version) # encode the specifier e.add_array(TransactionV144._SPECIFIER) # extra objects if any if extra_objects: e.add_all(*extra_objects) # encode addresses, names and number of months e.add_all(self.addresses, self.names) e.add_int8(self.number_of_months) # encode coin inputs e.add(len(self.coin_inputs)) for ci in self.coin_inputs: e.add(ci.parentid) # encode transaction fee e.add_all(self.transaction_fee) # encode refund coin output if self._refund_coin_output is None: e.add_int8(0) else: e.add_int8(1) e.add(self._refund_coin_output) # encode public key e.add(self.public_key) # return data return e.data def _id_input_compute(self): return bytearray(TransactionV144._SPECIFIER) + self._binary_encode_data() def _binary_encode_data(self): e = encoder_rivine_get() # get addresses and names addresses = self.addresses names = self.names addresses_length = len(addresses) names_length = len(names) # encode bot binary encoding prefix (containing length and refund info) maf = BotMonthsAndFlagsData( number_of_months=self.number_of_months, has_addresses=(addresses_length>0), has_names=(names_length>0), has_refund=(self._refund_coin_output is not None), ) e.add(maf) # encode the address and name length e.add_int8(addresses_length | (names_length<< 4)) # encode all addresses and names e.add_array(addresses) e.add_array(names) # encode transaction fee and coin inputs e.add_all(self.transaction_fee, self.coin_inputs) # encode refund coin output, if defined if maf.has_refund: e.add(self._refund_coin_output) # encode the identification at the end e.add_all(self.public_key, self.signature) # return encoded data return e.data def _from_json_data_object(self, data): self._addresses = [NetworkAddress.from_json(address) for address in data.get('addresses', []) or []] self._names = [BotName.from_json(name) for name in data.get('names', []) or []] self._number_of_months = int(data.get('nrofmonths', 0) or 0) if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None self._coin_inputs = [CoinInput.from_json(ci) for ci in data.get('coininputs', []) or []] if 'refundcoinoutput' in data: self._refund_coin_output = CoinOutput.from_json(data['refundcoinoutput']) else: self._refund_coin_output = None if 'identification' not in data or data['identification'] in (None, {}): self._public_key = None self._signature = None else: identification = data['identification'] self._public_key = PublicKey.from_json(identification['publickey']) self._signature = ED25519Signature.from_json(identification['signature'], as_array=True) def _json_data_object(self): output = { 'nrofmonths': self.number_of_months, 'txfee': self.transaction_fee.json(), 'coininputs': [ci.json() for ci in self.coin_inputs], 'identification': { 'publickey': self._public_key.json(), 'signature': self._signature.json(), }, } addresses = self.addresses if len(addresses) > 0: output['addresses'] = [address.json() for address in addresses] names = self.names if len(names) > 0: output['names'] = [name.json() for name in names] if self._refund_coin_output is not None: output['refundcoinoutput'] = self._refund_coin_output.json() return output def _extra_signature_requests_new(self): if self._public_key is None: # if no parent public key is defined, cannot do anything return [] if self._signature is not None: return [] # nothing to do # generate the input hash func input_hash_func = InputSignatureHashFactory(self, BotTransactionBaseClass.SPECIFIER_SENDER).signature_hash_new # define the input_hash_new generator function, # used to create the input hash for creating the signature unlockhash = self._public_key.unlockhash def input_hash_gen(public_key): return input_hash_func() # create the only signature request return [SignatureRequest(unlockhash=unlockhash, input_hash_gen=input_hash_gen, callback=self)] def _extra_is_fulfilled(self): return self._signature is not None
def test(): # create a tfchain client for devnet c = tfchain.TFChainClient.TFChainClient(network_type="devnet") # (we replace internal client logic with custom logic as to ensure we can test without requiring an active network) explorer_client = TFChainExplorerGetClientStub() explorer_client.hash_add( '014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a', '{"hashtype":"unlockhash","block":{"minerpayoutids":null,"transactions":null,"rawblock":{"parentid":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":0,"pobsindexes":{"BlockHeight":0,"TransactionIndex":0,"OutputIndex":0},"minerpayouts":null,"transactions":null},"blockid":"0000000000000000000000000000000000000000000000000000000000000000","difficulty":"0","estimatedactivebs":"0","height":0,"maturitytimestamp":0,"target":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"totalcoins":"0","arbitrarydatatotalsize":0,"minerpayoutcount":0,"transactioncount":0,"coininputcount":0,"coinoutputcount":0,"blockstakeinputcount":0,"blockstakeoutputcount":0,"minerfeecount":0,"arbitrarydatacount":0},"blocks":null,"transaction":{"id":"0000000000000000000000000000000000000000000000000000000000000000","height":0,"parent":"0000000000000000000000000000000000000000000000000000000000000000","rawtransaction":{"version":0,"data":{"coininputs":[],"minerfees":null}},"coininputoutputs":null,"coinoutputids":null,"coinoutputunlockhashes":null,"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},"transactions":[{"id":"066c03b5cf6db18edd6013591ae6292e8a04f1cc18fc6f0a0f3946d40bb087b3","height":3628,"parent":"761af2728b6f07bbccf4ddaf330c0bb1465246f6fe25fbfb9989129b69fbd899","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"670045cf43421b21577c27613569b65ba0cec613ae5437ee15dc0eea95241cbc","fulfillment":{"type":1,"data":{"publickey":"ed25519:bdea9aff09bcdc66529f6e2ef1bb763a3bab83ce542e8673d97aeaed0581ad97","signature":"e5a47b166eb6724ca7a72b5ddaeff287ebdde09751ed10e8850d320744afc5f45e59aa66e2cc4dc45ff593a0eea696c4e780b81636a1ce748c7149a9ee0ba807"}}},{"parentid":"1d686b8dfe44dfbfea55f327a52d9701a52938cb2c829f709dfb8059bc0e5f87","fulfillment":{"type":1,"data":{"publickey":"ed25519:64ae81a176302ea9ea47ec673f105da7a25e52bdf0cbb5b63d49fc2c69ed2eaa","signature":"5ce7a10373214d269837e9c176ec806469d2b80f200f9ee961c63553b6ee200f88958293a571889f26b2b52664f3c9ff2441eaf8ed61830b51d640003b06fb00"}}}],"coinoutputs":[{"value":"501000000000","condition":{"type":1,"data":{"unlockhash":"0186cea43fa0d303a6379ae76dd79f014698956fb982751549e3ff3844b23fa9551c1725470f55"}}},{"value":"198000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}}}],"minerfees":["1000000000"]}},"coininputoutputs":[{"value":"200000000000","condition":{"type":1,"data":{"unlockhash":"018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd"}},"unlockhash":"018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd"},{"value":"500000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}},"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}],"coinoutputids":["4b43906584620e8a13d98a917d424fb154dd1222a72b886a887c416b2a9120f5","19d4e81d057b4c93a7763f3dfe878f6a37d6111a3808b93afff4b369de0f5376"],"coinoutputunlockhashes":["0186cea43fa0d303a6379ae76dd79f014698956fb982751549e3ff3844b23fa9551c1725470f55","014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},{"id":"0b5d7509e467b943af12db2d73ddb78a5ec8cfdf64c3c6ecacb2de4af68bdc4d","height":33,"parent":"40e281daee6f113b788d64b8247381c36fe4885233a7475c9be2cce852e1696e","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"a3c8f44d64c0636018a929d2caeec09fb9698bfdcbfa3a8225585a51e09ee563","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"e5b69be3b385f0a33f7978e0d6af30cf37c975bbb99d0f3d06f199cd579d3e6854f6cb398c88082e438c89d11d04de0e7d3b2876e250d4ba8ae457f88547f605"}}}],"coinoutputs":[{"value":"1000000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}}},{"value":"99998999000000000","condition":{"type":1,"data":{"unlockhash":"01f68299b26a89efdb4351a61c3a062321d23edbc1399c8499947c1313375609adbbcd3977363c"}}}],"minerfees":["1000000000"]}},"coininputoutputs":[{"value":"100000000000000000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}},"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}],"coinoutputids":["75f297550acfa48490c21490f82c3c308326c16f950e17ef3a286486065a51b8","6d157a1eb23cadde122192869bc51e2a0fe44a2d8aba898cbdda8b7218a0f8cb"],"coinoutputunlockhashes":["014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a","01f68299b26a89efdb4351a61c3a062321d23edbc1399c8499947c1313375609adbbcd3977363c"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},{"id":"1b144b89c4b65cc67416e27286e802c50f78bc7326b4b0e5f6f967f99cc39419","height":200,"parent":"68e3886c9a4c59dea8c7a0eb138ede0e83716b995fd8ebfa5dfc766bfb349565","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"6d157a1eb23cadde122192869bc51e2a0fe44a2d8aba898cbdda8b7218a0f8cb","fulfillment":{"type":1,"data":{"publickey":"ed25519:00bde9571b30e1742c41fcca8c730183402d967df5b17b5f4ced22c677806614","signature":"05c72055d4cca6d8620fd7453c89ac8deaf92c38e366604dc6405cb097321b74e2e5f7ece15b440f4af5721b8aa6ea1f57f4fce44d2a2c6c0fb7a1e068a0480d"}}}],"coinoutputs":[{"value":"500000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}}},{"value":"99998498000000000","condition":{"type":1,"data":{"unlockhash":"0161fbcf58efaeba8813150e88fc33405b3a77d51277a2cdf3f4d2ab770de287c7af9d456c4e68"}}}],"minerfees":["1000000000"]}},"coininputoutputs":[{"value":"99998999000000000","condition":{"type":1,"data":{"unlockhash":"01f68299b26a89efdb4351a61c3a062321d23edbc1399c8499947c1313375609adbbcd3977363c"}},"unlockhash":"01f68299b26a89efdb4351a61c3a062321d23edbc1399c8499947c1313375609adbbcd3977363c"}],"coinoutputids":["1d686b8dfe44dfbfea55f327a52d9701a52938cb2c829f709dfb8059bc0e5f87","e27ed8bbe45177fafdfd7d21394ff483b8bde24f551dd927a046a0b7c37ec228"],"coinoutputunlockhashes":["014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a","0161fbcf58efaeba8813150e88fc33405b3a77d51277a2cdf3f4d2ab770de287c7af9d456c4e68"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},{"id":"5ab2604c7d54dd9ea39d829724e46deb8bbf2889d8627a3bb3b57907c6f4dff4","height":1433,"parent":"6b9fdad35096c5a84dcdb6e3e9ec2a8c7562f567e05b1207a7f9330ed51582bb","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"e27ed8bbe45177fafdfd7d21394ff483b8bde24f551dd927a046a0b7c37ec228","fulfillment":{"type":1,"data":{"publickey":"ed25519:41e84f3b0f6a06dd7e45ded4d0e227869725355b73906b82d9e3ffc0b6b01416","signature":"47a4d1a6aeb7403ea5c2cbcc26f84bb1964aadcb8765696db4a623694991f794663b860898db53401dd4324477c43f9fff177b74909201a03e131c2cd885cc0d"}}}],"coinoutputs":[{"value":"500000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}}},{"value":"99997997000000000","condition":{"type":1,"data":{"unlockhash":"0137f6f647a3c8019f6ae215ed09902c308efbf555b3520d2112d2b18cb577e40804d6fc5fafd2"}}}],"minerfees":["1000000000"]}},"coininputoutputs":[{"value":"99998498000000000","condition":{"type":1,"data":{"unlockhash":"0161fbcf58efaeba8813150e88fc33405b3a77d51277a2cdf3f4d2ab770de287c7af9d456c4e68"}},"unlockhash":"0161fbcf58efaeba8813150e88fc33405b3a77d51277a2cdf3f4d2ab770de287c7af9d456c4e68"}],"coinoutputids":["b90422bad2dffde79f0a46bd0a41055cf7974b080e115d76f69891ca31d31f11","f386312779f382f16fa836038b3d25536b928ba88ae1afa1f8c0e8dc25c0ba16"],"coinoutputunlockhashes":["014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a","0137f6f647a3c8019f6ae215ed09902c308efbf555b3520d2112d2b18cb577e40804d6fc5fafd2"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},{"id":"81d81b413fc8af3e528478e639d5a6e999e5e96611593cd2458b4d2d7abc3880","height":1433,"parent":"6b9fdad35096c5a84dcdb6e3e9ec2a8c7562f567e05b1207a7f9330ed51582bb","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"f386312779f382f16fa836038b3d25536b928ba88ae1afa1f8c0e8dc25c0ba16","fulfillment":{"type":1,"data":{"publickey":"ed25519:4e42a2fcfc0963d6fa7bb718fd088d9b6544331e8562d2743e730cdfbedeb55a","signature":"4abae694cf6a6408eca6f034ea2ec6930b9a73303c3e70d3444cf36a9966b0c4cc1b65168c23640affb5bdeae539ea583d42da52c58d21f59b71c288b3faef0e"}}}],"coinoutputs":[{"value":"2000000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}}},{"value":"99995996000000000","condition":{"type":1,"data":{"unlockhash":"0173f82c3ee74286c33fee8d883a7e9e759c6230b9e4e956ef233d7202bde69da45054270eef99"}}}],"minerfees":["1000000000"],"arbitrarydata":"Zm9vYmFy"}},"coininputoutputs":[{"value":"99997997000000000","condition":{"type":1,"data":{"unlockhash":"0137f6f647a3c8019f6ae215ed09902c308efbf555b3520d2112d2b18cb577e40804d6fc5fafd2"}},"unlockhash":"0137f6f647a3c8019f6ae215ed09902c308efbf555b3520d2112d2b18cb577e40804d6fc5fafd2"}],"coinoutputids":["d1f74e90eba8095e78f08a6284c7b76d4cda86b06ac742062d6e0e02dc4607eb","1edccdb04100ffd05d97431b6798533fa57dc7ed1ea58c75f0f445fb64442d6e"],"coinoutputunlockhashes":["014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a","0173f82c3ee74286c33fee8d883a7e9e759c6230b9e4e956ef233d7202bde69da45054270eef99"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false}],"multisigaddresses":["039e16ed27b2dfa3a5bbb1fa2b5f240ba7ff694b34a52bfc5bed6d4c3b14b763c011d7503ccb3a"],"unconfirmed":false}' ) explorer_client.hash_add( '018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd', '{"hashtype":"unlockhash","block":{"minerpayoutids":null,"transactions":null,"rawblock":{"parentid":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":0,"pobsindexes":{"BlockHeight":0,"TransactionIndex":0,"OutputIndex":0},"minerpayouts":null,"transactions":null},"blockid":"0000000000000000000000000000000000000000000000000000000000000000","difficulty":"0","estimatedactivebs":"0","height":0,"maturitytimestamp":0,"target":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"totalcoins":"0","arbitrarydatatotalsize":0,"minerpayoutcount":0,"transactioncount":0,"coininputcount":0,"coinoutputcount":0,"blockstakeinputcount":0,"blockstakeoutputcount":0,"minerfeecount":0,"arbitrarydatacount":0},"blocks":null,"transaction":{"id":"0000000000000000000000000000000000000000000000000000000000000000","height":0,"parent":"0000000000000000000000000000000000000000000000000000000000000000","rawtransaction":{"version":0,"data":{"coininputs":[],"minerfees":null}},"coininputoutputs":null,"coinoutputids":null,"coinoutputunlockhashes":null,"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},"transactions":[{"id":"066c03b5cf6db18edd6013591ae6292e8a04f1cc18fc6f0a0f3946d40bb087b3","height":3628,"parent":"761af2728b6f07bbccf4ddaf330c0bb1465246f6fe25fbfb9989129b69fbd899","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"670045cf43421b21577c27613569b65ba0cec613ae5437ee15dc0eea95241cbc","fulfillment":{"type":1,"data":{"publickey":"ed25519:bdea9aff09bcdc66529f6e2ef1bb763a3bab83ce542e8673d97aeaed0581ad97","signature":"e5a47b166eb6724ca7a72b5ddaeff287ebdde09751ed10e8850d320744afc5f45e59aa66e2cc4dc45ff593a0eea696c4e780b81636a1ce748c7149a9ee0ba807"}}},{"parentid":"1d686b8dfe44dfbfea55f327a52d9701a52938cb2c829f709dfb8059bc0e5f87","fulfillment":{"type":1,"data":{"publickey":"ed25519:64ae81a176302ea9ea47ec673f105da7a25e52bdf0cbb5b63d49fc2c69ed2eaa","signature":"5ce7a10373214d269837e9c176ec806469d2b80f200f9ee961c63553b6ee200f88958293a571889f26b2b52664f3c9ff2441eaf8ed61830b51d640003b06fb00"}}}],"coinoutputs":[{"value":"501000000000","condition":{"type":1,"data":{"unlockhash":"0186cea43fa0d303a6379ae76dd79f014698956fb982751549e3ff3844b23fa9551c1725470f55"}}},{"value":"198000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}}}],"minerfees":["1000000000"]}},"coininputoutputs":[{"value":"200000000000","condition":{"type":1,"data":{"unlockhash":"018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd"}},"unlockhash":"018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd"},{"value":"500000000000","condition":{"type":1,"data":{"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}},"unlockhash":"014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"}],"coinoutputids":["4b43906584620e8a13d98a917d424fb154dd1222a72b886a887c416b2a9120f5","19d4e81d057b4c93a7763f3dfe878f6a37d6111a3808b93afff4b369de0f5376"],"coinoutputunlockhashes":["0186cea43fa0d303a6379ae76dd79f014698956fb982751549e3ff3844b23fa9551c1725470f55","014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},{"id":"d0d290dccddaf086708e4163eacd420bfbaa7acb77deea97c6a8792a139f6e7e","height":3583,"parent":"ca746d80740ff45f6e7f0d6c8b6278b35b1040b4e21baaf41bd03cd3a7a176be","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"1edccdb04100ffd05d97431b6798533fa57dc7ed1ea58c75f0f445fb64442d6e","fulfillment":{"type":1,"data":{"publickey":"ed25519:7469d51063cdb690cc8025db7d28faadc71ff69f7c372779bf3a1e801a923e02","signature":"6f38467bb4ca23900e424d5ac2066a09b61fe9d9ae84a4231755063f591d04711f0a5ab11b60b42c2c32a8e7f73fc1cc1cb0073b036bc417ed504a8ef5f25402"}}}],"coinoutputs":[{"value":"200000000000","condition":{"type":1,"data":{"unlockhash":"018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd"}}},{"value":"99995795000000000","condition":{"type":1,"data":{"unlockhash":"01f706dcbb9f0cb1e97d891ada3a133f68612ebff948c5bbae7851108a65dab7782907eecd86be"}}}],"minerfees":["1000000000"]}},"coininputoutputs":[{"value":"99995996000000000","condition":{"type":1,"data":{"unlockhash":"0173f82c3ee74286c33fee8d883a7e9e759c6230b9e4e956ef233d7202bde69da45054270eef99"}},"unlockhash":"0173f82c3ee74286c33fee8d883a7e9e759c6230b9e4e956ef233d7202bde69da45054270eef99"}],"coinoutputids":["670045cf43421b21577c27613569b65ba0cec613ae5437ee15dc0eea95241cbc","984555e190d58dc752aad81b0a6cf6194fa5bf89b8614efcd9604d138a8953bc"],"coinoutputunlockhashes":["018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd","01f706dcbb9f0cb1e97d891ada3a133f68612ebff948c5bbae7851108a65dab7782907eecd86be"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false}],"multisigaddresses":null,"unconfirmed":false}' ) explorer_client.chain_info = '{"blockid":"552e410481cce1358ffcd4687f4199dd2181c799d55da26178e55643355bbd2e","difficulty":"27801","estimatedactivebs":"59","height":3644,"maturitytimestamp":1549012510,"target":[0,2,91,116,78,165,130,72,116,162,127,4,125,67,108,16,140,247,132,198,107,159,114,177,44,25,18,162,38,157,169,245],"totalcoins":"0","arbitrarydatatotalsize":6,"minerpayoutcount":3650,"transactioncount":3652,"coininputcount":12,"coinoutputcount":15,"blockstakeinputcount":3644,"blockstakeoutputcount":3645,"minerfeecount":7,"arbitrarydatacount":1}' explorer_client.hash_add( '552e410481cce1358ffcd4687f4199dd2181c799d55da26178e55643355bbd2e', '{"hashtype":"blockid","block":{"minerpayoutids":["468db689f752414702ef3a5aa06238f03a4539434a61624b3b8a0fb5dc38a211"],"transactions":[{"id":"2396f8e57bbb9b22bd1d749d5de3fd532ea6886e9660a556a13571d701d83e27","height":3644,"parent":"552e410481cce1358ffcd4687f4199dd2181c799d55da26178e55643355bbd2e","rawtransaction":{"version":1,"data":{"coininputs":null,"blockstakeinputs":[{"parentid":"ff5a002ec356b7cb24fbee9f076f239fb8c72d5a8a448cee92ee6d29a87aef52","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"7bec94dfb87640726c6a14de2110599db0f81cf9fa456249e7bf79b0c74b79517edde25c4ee87f181880af44fe6ee054ff20b74eda2144fe07fa5bfb9d884208"}}}],"blockstakeoutputs":[{"value":"3000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}}}],"minerfees":null}},"coininputoutputs":null,"coinoutputids":null,"coinoutputunlockhashes":null,"blockstakeinputoutputs":[{"value":"3000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}},"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}],"blockstakeoutputids":["f683e7319659c61f54e93546bc41b57c5bffe79de26c06ec7371034465804c81"],"blockstakeunlockhashes":["015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"],"unconfirmed":false}],"rawblock":{"parentid":"47db4274551b0372564f8d1ab89c596428f00e460c0b416327e53983c8765198","timestamp":1549012665,"pobsindexes":{"BlockHeight":3643,"TransactionIndex":0,"OutputIndex":0},"minerpayouts":[{"value":"10000000000","unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}],"transactions":[{"version":1,"data":{"coininputs":null,"blockstakeinputs":[{"parentid":"ff5a002ec356b7cb24fbee9f076f239fb8c72d5a8a448cee92ee6d29a87aef52","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"7bec94dfb87640726c6a14de2110599db0f81cf9fa456249e7bf79b0c74b79517edde25c4ee87f181880af44fe6ee054ff20b74eda2144fe07fa5bfb9d884208"}}}],"blockstakeoutputs":[{"value":"3000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}}}],"minerfees":null}}]},"blockid":"552e410481cce1358ffcd4687f4199dd2181c799d55da26178e55643355bbd2e","difficulty":"27801","estimatedactivebs":"59","height":3644,"maturitytimestamp":1549012510,"target":[0,2,91,116,78,165,130,72,116,162,127,4,125,67,108,16,140,247,132,198,107,159,114,177,44,25,18,162,38,157,169,245],"totalcoins":"0","arbitrarydatatotalsize":6,"minerpayoutcount":3650,"transactioncount":3652,"coininputcount":12,"coinoutputcount":15,"blockstakeinputcount":3644,"blockstakeoutputcount":3645,"minerfeecount":7,"arbitrarydatacount":1},"blocks":null,"transaction":{"id":"0000000000000000000000000000000000000000000000000000000000000000","height":0,"parent":"0000000000000000000000000000000000000000000000000000000000000000","rawtransaction":{"version":0,"data":{"coininputs":[],"minerfees":null}},"coininputoutputs":null,"coinoutputids":null,"coinoutputunlockhashes":null,"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},"transactions":null,"multisigaddresses":null,"unconfirmed":false}' ) explorer_client.hash_add( '039e16ed27b2dfa3a5bbb1fa2b5f240ba7ff694b34a52bfc5bed6d4c3b14b763c011d7503ccb3a', '{"hashtype":"unlockhash","block":{"minerpayoutids":null,"transactions":null,"rawblock":{"parentid":"0000000000000000000000000000000000000000000000000000000000000000","timestamp":0,"pobsindexes":{"BlockHeight":0,"TransactionIndex":0,"OutputIndex":0},"minerpayouts":null,"transactions":null},"blockid":"0000000000000000000000000000000000000000000000000000000000000000","difficulty":"0","estimatedactivebs":"0","height":0,"maturitytimestamp":0,"target":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"totalcoins":"0","arbitrarydatatotalsize":0,"minerpayoutcount":0,"transactioncount":0,"coininputcount":0,"coinoutputcount":0,"blockstakeinputcount":0,"blockstakeoutputcount":0,"minerfeecount":0,"arbitrarydatacount":0},"blocks":null,"transaction":{"id":"0000000000000000000000000000000000000000000000000000000000000000","height":0,"parent":"0000000000000000000000000000000000000000000000000000000000000000","rawtransaction":{"version":0,"data":{"coininputs":[],"minerfees":null}},"coininputoutputs":null,"coinoutputids":null,"coinoutputunlockhashes":null,"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false},"transactions":[{"id":"4c70a0406f36cf354edf87642df3f34568fd0a89c052a81d11cc6e4f8fbf685e","height":45,"parent":"f7b78b17d581ff9e58ffbcce1701d4dcadb0781590ca68e839def0dc98b0360a","rawtransaction":{"version":1,"data":{"coininputs":[{"parentid":"7d4a100fc3bc08b2bdd1284c17260dd2bd6b55fd6c1429dbbd683bf362d92b50","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"c34b8ca1ab08930bc68d61026af504d62d8a8bbda9b79ae01a387560fba22d39b12021e16566732b742ea686f997b3c19c807523797cdc0d74a4d25123691004"}}},{"parentid":"83503f9cea00d562e0460eace93159a4c4dd00df4703c96947e81885b46da04c","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"f6eea681a259baf14433ac55b4293b22ca2056810ee8fed2129039224d14558f54ca58c6d96e9885cb20ecdf7e64ba81d1a83c6e9a42bf9464287fa6359d360c"}}},{"parentid":"578aa43de72b42b4f4547c5ddc7f61736b1cac206e1789bc89fcd9333cf3d1f3","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"a0521d14dfe4a0c9b8b57ed361d738b48b6a8346097246effe0b4ee67b6fecbc3a90e4671ddc0b164f6c2839df249bb5998f10216a4a674ba8d24b8ad6bdf808"}}},{"parentid":"5a1454762e6895431e1b9e4e435e4d0ad60a3881843ac46b88e220771055ca87","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"900e7868780e67bcb68af3ec6976e84289850d0db59210d4689b1c0e2deb3164b9e93eb9ee5a38850f2319463b0845163e1eee443d7b645c59485c2aa0837707"}}},{"parentid":"c04ebebe17a1759457eecaf4d5d33f5ddbe8d154b0be1606f05bc8fd02ab9cd4","fulfillment":{"type":1,"data":{"publickey":"ed25519:d285f92d6d449d9abb27f4c6cf82713cec0696d62b8c123f1627e054dc6d7780","signature":"e992b1cd3347b5362e820166d5929de7c682130c7143fc4c9ff3156f5d44110753687697a0154a2043290b3f022e2537f3e3a6807caf9150f8c255d74e386d0a"}}}],"coinoutputs":[{"value":"42000000000","condition":{"type":4,"data":{"unlockhashes":["01ffd7c884aa869056bfb832d957bb71a0005fee13c19046cebec84b3a5047ee8829eab070374b","014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a"],"minimumsignaturecount":1}}},{"value":"7000000000","condition":{"type":1,"data":{"unlockhash":"01972837ee396f22f96846a0c700f9cf7c8fa83ab4110da91a1c7d02f94f28ff03e45f1470df82"}}}],"minerfees":["1000000000"]}},"coininputoutputs":[{"value":"10000000000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}},"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"},{"value":"10000000000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}},"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"},{"value":"10000000000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}},"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"},{"value":"10000000000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}},"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"},{"value":"10000000000","condition":{"type":1,"data":{"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}},"unlockhash":"015a080a9259b9d4aaa550e2156f49b1a79a64c7ea463d810d4493e8242e6791584fbdac553e6f"}],"coinoutputids":["29152fe03a2c8782fcbd670579686088c52be83fa3870f5f0788073d97fb5fb2","0fc9b16bb180cd8f8a7144d65e6c8fca66994a4ccaee42e324289d4039ab2841"],"coinoutputunlockhashes":["039e16ed27b2dfa3a5bbb1fa2b5f240ba7ff694b34a52bfc5bed6d4c3b14b763c011d7503ccb3a","01972837ee396f22f96846a0c700f9cf7c8fa83ab4110da91a1c7d02f94f28ff03e45f1470df82"],"blockstakeinputoutputs":null,"blockstakeoutputids":null,"blockstakeunlockhashes":null,"unconfirmed":false}],"multisigaddresses":null,"unconfirmed":false}' ) # add threebot records: explorer_client.threebot_record_add( ThreeBotRecord( identifier=3, names=[BotName(value=s) for s in ["foorbar", "chatbot.example"]], addresses=[ NetworkAddress(address=s) for s in ["example.org", "127.0.0.1"] ], public_key=PublicKey.from_json( "ed25519:64ae81a176302ea9ea47ec673f105da7a25e52bdf0cbb5b63d49fc2c69ed2eaa" ), expiration=1552581420, )) explorer_client.threebot_record_add( ThreeBotRecord( identifier=5, names=[], addresses=[NetworkAddress(address=s) for s in ["bot.example.org"]], public_key=PublicKey.from_json( "ed25519:bdea9aff09bcdc66529f6e2ef1bb763a3bab83ce542e8673d97aeaed0581ad97" ), expiration=1552585420, )) c.explorer_get = explorer_client.explorer_get c.explorer_post = explorer_client.explorer_post # the devnet genesis seed is the seed of the wallet, # which receives all block stakes and coins in the genesis block of the tfchain devnet DEVNET_GENESIS_SEED = "image orchard airport business cost work mountain obscure flee alpha alert salmon damage engage trumpet route marble subway immune short tide young cycle attract" # create a new devnet wallet w = tfchain.TFChainWallet.TFChainWallet(client=c, seed=DEVNET_GENESIS_SEED) # we create a new wallet using an existing seed, # such that our seed is used and not a new randomly generated seed # a tfchain (JS) wallet uses the underlying tfchain client for all its # interaction with the tfchain network assert w.network_type == tfchain.TFChainClient.NetworkType.DEVNET # getting the balance of a wallet is as easy as getting the 'balance' property balance = w.balance # the available and locked tokens can be easily checked assert str(balance.available) == '3698' assert str(balance.locked) == '0' # assert the addresses are correct assert w.addresses == [ "014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a", "018f5a43327fb865843808ddf549f1b1c06376e07195423778751056be626841f42dcf25a593fd" ] # transfering a name is easy as well: result = w.threebot.name_transfer( sender=3, # identifier of sender 3Bot receiver=5, # identifier of receiver 3Bot names=["foobar", "chatbot.example" ], # names to be transfered from sender to receiver 3Bot ) assert result.submitted # we expect the transaction to be submitted expected_transaction = { 'version': 146, 'data': { 'sender': { 'id': 3, 'signature': 'e1b1bb0f6f9493b17f8959d2ff67741eee0642737c277e46644e2902e805866036af2ebf8ece2c4eb02539f2f0de49679231959d677f86ff05c19bc1f6c2eb00' }, 'receiver': { 'id': 5, 'signature': 'be7f8a47a1de99a5aaff1684c5973d50d3e1777c271309b08175b993802d4b19980a38c7d6d59362bff542cced12818d72745405f15ed9170319020a27aae30a' }, 'names': ['foobar', 'chatbot.example'], 'txfee': '1000000000', 'coininputs': [{ 'parentid': '19d4e81d057b4c93a7763f3dfe878f6a37d6111a3808b93afff4b369de0f5376', 'fulfillment': { 'type': 1, 'data': { 'publickey': 'ed25519:64ae81a176302ea9ea47ec673f105da7a25e52bdf0cbb5b63d49fc2c69ed2eaa', 'signature': '47d76c8cc278c1077574d0de6aaca6c13414bbf1c13f4f97cecb7ac1a19763eb9e0cf7c2584a8ade4bc7a8f4e220aa3b5d021ffd092af6dc8a07760a21e2e101' } } }], 'refundcoinoutput': { 'value': '97000000000', 'condition': { 'type': 1, 'data': { 'unlockhash': '014ad318772a09de75fb62f084a33188a7f6fb5e7b68c0ed85a5f90fe11246386b7e6fe97a5a6a' } } } } } # ensure our transaction is as expected assert result.transaction.json() == expected_transaction # ensure the transaction is posted and as expected there as well txn = explorer_client.posted_transaction_get(result.transaction.id) assert txn.json() == expected_transaction # if no names to be transfered are defined, a ValueError is raised with pytest.raises(ValueError): w.threebot.name_transfer(sender=3, receiver=5, names=[]) # if names are defined to be transfered but one of the two 3Bots do no exist, # an tfchain.errors.ThreeBotNotFound will be raised with pytest.raises(tfchain.errors.ThreeBotNotFound): w.threebot.name_transfer(sender=2, receiver=5, names=["foobar"]) with pytest.raises(tfchain.errors.ThreeBotNotFound): w.threebot.name_transfer(sender=3, receiver=4, names=["foobar"]) # make our receiver 3Bot expired explorer_client.threebot_record_add(ThreeBotRecord( identifier=5, names=[], addresses=[NetworkAddress(address=s) for s in ["bot.example.org"]], public_key=PublicKey.from_json( "ed25519:bdea9aff09bcdc66529f6e2ef1bb763a3bab83ce542e8673d97aeaed0581ad97" ), expiration=1549012664, ), force=True) # if the receiver 3Bot is inactive, an error will be raised with pytest.raises(tfchain.errors.ThreeBotInactive): w.threebot.name_transfer(sender=3, receiver=5, names=["foobar"]) # make our sender 3Bot expired explorer_client.threebot_record_add(ThreeBotRecord( identifier=3, names=[BotName(value=s) for s in ["foorbar", "chatbot.example"]], addresses=[ NetworkAddress(address=s) for s in ["example.org", "127.0.0.1"] ], public_key=PublicKey.from_json( "ed25519:64ae81a176302ea9ea47ec673f105da7a25e52bdf0cbb5b63d49fc2c69ed2eaa" ), expiration=1549012664, ), force=True) # if the sender and receiver 3Bots are inactive, an error will be raised with pytest.raises(tfchain.errors.ThreeBotInactive): w.threebot.name_transfer(sender=3, receiver=5, names=["foobar"]) # make our receiver 3Bot active again explorer_client.threebot_record_add(ThreeBotRecord( identifier=5, names=[], addresses=[NetworkAddress(address=s) for s in ["bot.example.org"]], public_key=PublicKey.from_json( "ed25519:bdea9aff09bcdc66529f6e2ef1bb763a3bab83ce542e8673d97aeaed0581ad97" ), expiration=1552585420, ), force=True) # if the sender 3Bot is inactive, an error will be raised with pytest.raises(tfchain.errors.ThreeBotInactive): w.threebot.name_transfer(sender=3, receiver=5, names=["foobar"])
def from_json_data_object(self, data): self._pub_key = PublicKey.from_json(data['publickey']) self._signature = ED25519Signature.from_json(data['signature']) self._secret = None if 'secret' in data: self._secret = AtomicSwapSecret.from_json(data['secret'])
def public_key(self): if self._pub_key == None: return PublicKey() return self._pub_key
def from_json(cls, obj): return cls(public_key=PublicKey.from_json(obj['publickey']), signature=ED25519Signature.from_json(obj['signature']))
def from_json_data_object(self, data): self._pub_key = PublicKey.from_json(data['publickey']) self._signature = ED25519Signature.from_json(data['signature'])