예제 #1
0
    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"
        )
예제 #2
0
 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
예제 #3
0
 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)
예제 #4
0
    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
예제 #5
0
 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
예제 #6
0
 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__())
예제 #7
0
 def unlockhash(self):
     """
     Return the unlock hash generated from this public key.
     """
     e = SiaBinaryEncoder()
     self.sia_binary_encode(e)
     # need to encode again to add the length
     data = e.data
     e = SiaBinaryEncoder()
     e.add_slice(data)
     hash = jscrypto.blake2b(e.data)
     return UnlockHash(uhtype=UnlockHashType.PUBLIC_KEY, uhhash=hash)
예제 #8
0
 def unlockhash(self):
     """
     Return the unlock hash generated from this public key.
     """
     e = encoder_sia_get()
     self.sia_binary_encode(e)
     # need to encode again to add the length
     data = e.data
     e = encoder_sia_get()
     e.add_slice(data)
     hash = bytearray.fromhex(blake2_string(e.data))
     return UnlockHash(type=UnlockHashType.PUBLIC_KEY, hash=hash)
예제 #9
0
 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
예제 #10
0
        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
예제 #11
0
 def address(self):
     if self._address is None:
         return UnlockHash()
     return self._address
예제 #12
0
    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
예제 #13
0
 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