def fund(self, amount, source=None): """ Fund the specified amount with the available outputs of this wallet's balance. """ # collect addresses and multisig addresses addresses = set() refund = None if source == None: for co in self.outputs_available: addresses.add(co.condition.unlockhash.__str__()) for co in self.outputs_unconfirmed_available: addresses.add(co.condition.unlockhash.__str__()) else: # if only one address is given, transform it into an acceptable list if not isinstance(source, list) and not jsobj.is_js_arr(source): if isinstance(source, str): source = UnlockHash.from_json(source) elif not isinstance(source, UnlockHash): raise TypeError( "cannot add source address from type {}".format( type(source))) source = [source] # add one or multiple personal/multisig addresses for value in source: if isinstance(value, str): value = UnlockHash.from_json(value) elif not isinstance(value, UnlockHash): raise TypeError( "cannot add source address from type {}".format( type(value))) elif value.uhtype.__eq__(UnlockHashType.PUBLIC_KEY): addresses.add(value) else: raise TypeError( "cannot add source address with unsupported UnlockHashType {}" .format(value.uhtype)) if len(source) == 1: if source[0].uhtype.__eq__(UnlockHashType.PUBLIC_KEY): refund = ConditionTypes.unlockhash_new( unlockhash=source[0]) # ensure at least one address is defined if len(addresses) == 0: raise tferrors.InsufficientFunds( "insufficient funds in this wallet") # if personal addresses are given, try to use these first # as these are the easiest kind to deal with if len(addresses) == 0: outputs, collected = ([], Currency()) # start with nothing else: outputs, collected = self._fund_individual(amount, addresses) if collected.greater_than_or_equal_to(amount): # if we already have sufficient, we stop now return ([CoinInput.from_coin_output(co) for co in outputs], collected.minus(amount), refund) raise tferrors.InsufficientFunds( "not enough funds available in the wallet to fund the requested amount" )
def _from_json_data_object(self, data): # decode address if 'address' in data: self._address = UnlockHash.from_json(data['address']) else: self._address = None # decode value if 'value' in data: self._value = Currency.from_json(data['value']) else: self._value = None # decode transaction fee if 'txfee' in data: self._transaction_fee = Currency.from_json(data['txfee']) else: self._transaction_fee = None # decode blockid if 'blockid' in data: self._blockid = ERC20Hash.from_json(data['blockid']) else: self._blockid = None # decode transactionid if 'txid' in data: self._transactionid = ERC20Hash.from_json(data['txid']) else: self._transactionid = None
def address(self, value): if value is None: self._address = None return if isinstance(value, UnlockHash): self._address = value return self._address = UnlockHash.from_json(value)
def address_get(self, unlockhash): """ Get the ERC20 (withdraw) address for the given unlock hash, ExplorerNoContent error is raised when no address could be found for the given unlock hash. Only type 01 addresses can be looked up for this method (personal wallet addresses), as there can be no MultiSignature (wallet) address registered as an ERC20 withdraw address. @param unlockhash: the str or wallet address to be looked up """ if isinstance(unlockhash, str): unlockhash = UnlockHash.from_json(unlockhash) elif not isinstance(unlockhash, UnlockHash): raise TypeError( "{} is not a valid type and cannot be used as unlock hash". format(type(unlockhash))) if unlockhash.type != UnlockHashType.PUBLIC_KEY: raise TypeError( "only person wallet addresses cannot be registered as withdrawel addresses: {} is an invalid unlock hash type" .format(unlockhash.type)) endpoint = "/explorer/hashes/" + str(unlockhash) resp = self._client.explorer_get(endpoint=endpoint) resp = json_loads(resp) try: if resp['hashtype'] != 'unlockhash': raise tfchain.errors.ExplorerInvalidResponse( "expected hash type 'unlockhash' not '{}'".format( resp['hashtype']), endpoint, resp) # parse the ERC20 Info if not 'erc20info' in resp: raise tfchain.errors.ExplorerNoContent( "{} could be found but is not registered as an ERC20 withdraw address" .format(str(unlockhash)), endpoint=endpoint) info = resp['erc20info'] return ERC20AddressInfo( address_tft=UnlockHash.from_json(info['tftaddress']), address_erc20=ERC20Address.from_json(info['erc20address']), confirmations=int(info['confirmations']), ) except KeyError as exc: # return a KeyError as an invalid Explorer Response raise tfchain.errors.ExplorerInvalidResponse( str(exc), endpoint, resp) from exc
def catch_no_content(reason): if isinstance(reason, tferrors.ExplorerNoContent): return ExplorerUnlockhashResult( unlockhash=UnlockHash.from_json(unlockhash), transactions=[], multisig_addresses=None, erc20_info=None, ) # pass on any other reason raise reason
def multisig_address_add(self, address): if isinstance(address, str): address = UnlockHash.from_json(address) elif not isinstance(address, UnlockHash): raise TypeError( "can only add a multisig address as a UnlockHash or str, not: {} ({})" .format(address, type(address))) if address.uhtype.__ne__(UnlockHashType.MULTI_SIG): raise ValueError("can only add multisig addresses") self._multisig_addresses.add(address.__str__())
def _block_get_parse_cb(self, result): endpoint, block = result try: # parse the transactions transactions = [] for etxn in block['transactions']: # parse the explorer transaction transaction = self._transaction_from_explorer_transaction( etxn, endpoint=endpoint, resp=block) # append the transaction to the list of transactions transactions.append(transaction) rawblock = block['rawblock'] # parse the parent id parentid = Hash.from_json(obj=rawblock['parentid']) # parse the miner payouts miner_payouts = [] minerpayoutids = block.get_or('minerpayoutids', None) or [] eminerpayouts = rawblock.get_or('minerpayouts', None) or [] if len(eminerpayouts) != len(minerpayoutids): raise tferrors.ExplorerInvalidResponse( "amount of miner payouts and payout ids are not matching: {} != {}" .format(len(eminerpayouts), len(minerpayoutids)), endpoint, block) for idx, mp in enumerate(eminerpayouts): id = Hash.from_json(minerpayoutids[idx]) value = Currency.from_json(mp['value']) unlockhash = UnlockHash.from_json(mp['unlockhash']) miner_payouts.append( ExplorerMinerPayout(id=id, value=value, unlockhash=unlockhash)) # get the timestamp and height height = int(block['height']) timestamp = int(rawblock['timestamp']) # get the block's identifier blockid = Hash.from_json(block['blockid']) # for all transactions assign these properties for transaction in transactions: _assign_block_properties_to_transacton(transaction, block) transaction.height = height transaction.blockid = blockid # return the block, as reported by the explorer return ExplorerBlock(id=blockid, parentid=parentid, height=height, timestamp=timestamp, transactions=transactions, miner_payouts=miner_payouts) except KeyError as exc: raise tferrors.ExplorerInvalidResponse(str(exc), endpoint, block) from exc
def cb(result): _, resp = result try: if resp['hashtype'] != 'unlockhash': raise tferrors.ExplorerInvalidResponse( "expected hash type 'unlockhash' not '{}'".format( resp['hashtype']), endpoint, resp) # parse the transactions transactions = [] resp_transactions = resp['transactions'] if resp_transactions != None and jsobj.is_js_arr( resp_transactions): for etxn in resp_transactions: # parse the explorer transaction transaction = self._transaction_from_explorer_transaction( etxn, endpoint=endpoint, resp=resp) # append the transaction to the list of transactions transactions.append(transaction) # collect all multisig addresses multisig_addresses = [ UnlockHash.from_json(obj=uh) for uh in resp.get_or('multisigaddresses', None) or [] ] for addr in multisig_addresses: if addr.uhtype.__ne__(UnlockHashType.MULTI_SIG): raise tferrors.ExplorerInvalidResponse( "invalid unlock hash type {} for MultiSignature Address (expected: 3)" .format(addr.uhtype.value), endpoint, resp) erc20_info = None if 'erc20info' in resp: info = resp['erc20info'] erc20_info = ERC20AddressInfo( address_tft=UnlockHash.from_json(info['tftaddress']), address_erc20=ERC20Address.from_json( info['erc20address']), confirmations=int(info['confirmations']), ) # sort the transactions by height def txn_arr_sort(a, b): height_a = pow(2, 64) if a.height < 0 else a.height height_b = pow(2, 64) if b.height < 0 else b.height if height_a < height_b: return -1 if height_a > height_b: return 1 tx_order_a = pow( 2, 64) if a.transaction_order < 0 else a.transaction_order tx_order_b = pow( 2, 64) if b.transaction_order < 0 else b.transaction_order if tx_order_a < tx_order_b: return -1 if tx_order_a > tx_order_b: return 1 return 0 transactions = jsarr.sort(transactions, txn_arr_sort, reverse=True) # return explorer data for the unlockhash return ExplorerUnlockhashResult( unlockhash=UnlockHash.from_json(unlockhash), transactions=transactions, multisig_addresses=multisig_addresses, erc20_info=erc20_info, ) except KeyError as exc: # return a KeyError as an invalid Explorer Response raise tferrors.ExplorerInvalidResponse(str(exc), endpoint, resp) from exc
def unlockhash_get(self, target): """ Get all transactions linked to the given unlockhash (target), as well as other information such as the multisig addresses linked to the given unlockhash (target). target can be any of: - None: unlockhash of the Free-For-All wallet will be used - str (or unlockhash/bytes/bytearray): target is assumed to be the unlockhash of a personal wallet - list: target is assumed to be the addresses of a MultiSig wallet where all owners (specified as a list of addresses) have to sign - tuple (addresses, sigcount): target is a sigcount-of-addresscount MultiSig wallet @param target: the target wallet to look up transactions for in the explorer, see above for more info """ unlockhash = str(ConditionTypes.from_recipient(target).unlockhash) endpoint = "/explorer/hashes/" + unlockhash resp = self.explorer_get(endpoint=endpoint) resp = json_loads(resp) try: if resp['hashtype'] != 'unlockhash': raise tfchain.errors.ExplorerInvalidResponse( "expected hash type 'unlockhash' not '{}'".format( resp['hashtype']), endpoint, resp) # parse the transactions transactions = [] for etxn in resp['transactions']: # parse the explorer transaction transaction = self._transaction_from_explorer_transaction( etxn, endpoint=endpoint, resp=resp) # append the transaction to the list of transactions transactions.append(transaction) # collect all multisig addresses multisig_addresses = [ UnlockHash.from_json(obj=uh) for uh in resp.get('multisigaddresses', None) or [] ] for addr in multisig_addresses: if addr.type != UnlockHashType.MULTI_SIG: raise tfchain.errors.ExplorerInvalidResponse( "invalid unlock hash type {} for MultiSignature Address (expected: 3)" .format(addr.type), endpoint, resp) erc20_info = None if 'erc20info' in resp: info = resp['erc20info'] erc20_info = ERC20AddressInfo( address_tft=UnlockHash.from_json(info['tftaddress']), address_erc20=ERC20Address.from_json(info['erc20address']), confirmations=int(info['confirmations']), ) # sort the transactions by height transactions.sort(key=(lambda txn: sys.maxsize if txn.height < 0 else txn.height), reverse=True) # return explorer data for the unlockhash return ExplorerUnlockhashResult( unlockhash=UnlockHash.from_json(unlockhash), transactions=transactions, multisig_addresses=multisig_addresses, erc20_info=erc20_info, client=self) except KeyError as exc: # return a KeyError as an invalid Explorer Response raise tfchain.errors.ExplorerInvalidResponse( str(exc), endpoint, resp) from exc
def block_get(self, value): """ Get a block from an available explorer Node. @param value: the identifier or height that points to the desired block """ endpoint = "/explorer/?" resp = {} try: # get the explorer block if isinstance(value, int): endpoint = "/explorer/blocks/{}".format(int(value)) resp = self.explorer_get(endpoint=endpoint) resp = json_loads(resp) resp = resp['block'] else: blockid = self._normalize_id(value) endpoint = "/explorer/hashes/" + blockid resp = self.explorer_get(endpoint=endpoint) resp = json_loads(resp) if resp['hashtype'] != 'blockid': raise tfchain.errors.ExplorerInvalidResponse( "expected hash type 'blockid' not '{}'".format( resp['hashtype']), endpoint, resp) resp = resp['block'] if resp['blockid'] != blockid: raise tfchain.errors.ExplorerInvalidResponse( "expected block ID '{}' not '{}'".format( blockid, resp['blockid']), endpoint, resp) # parse the transactions transactions = [] for etxn in resp['transactions']: # parse the explorer transaction transaction = self._transaction_from_explorer_transaction( etxn, endpoint=endpoint, resp=resp) # append the transaction to the list of transactions transactions.append(transaction) rawblock = resp['rawblock'] # parse the parent id parentid = Hash.from_json(obj=rawblock['parentid']) # parse the miner payouts miner_payouts = [] minerpayoutids = resp.get('minerpayoutids', None) or [] eminerpayouts = rawblock.get('minerpayouts', None) or [] if len(eminerpayouts) != len(minerpayoutids): raise tfchain.errors.ExplorerInvalidResponse( "amount of miner payouts and payout ids are not matching: {} != {}" .format(len(eminerpayouts), len(minerpayoutids)), endpoint, resp) for idx, mp in enumerate(eminerpayouts): id = Hash.from_json(minerpayoutids[idx]) value = Currency.from_json(mp['value']) unlockhash = UnlockHash.from_json(mp['unlockhash']) miner_payouts.append( ExplorerMinerPayout(id=id, value=value, unlockhash=unlockhash)) # get the timestamp and height height = int(resp['height']) timestamp = int(rawblock['timestamp']) # get the block's identifier blockid = Hash.from_json(resp['blockid']) # return the block, as reported by the explorer return ExplorerBlock(id=blockid, parentid=parentid, height=height, timestamp=timestamp, transactions=transactions, miner_payouts=miner_payouts) except KeyError as exc: # return a KeyError as an invalid Explorer Response raise tfchain.errors.ExplorerInvalidResponse( str(exc), endpoint, resp) from exc