Example #1
0
    def get_utxo(cls, address: str, amount: float):
        api_key = os.environ.get('CRYPTOID_API_KEY')
        if not api_key:
            raise ValueError('API key for cryptoid is required to get UTXOs.')
        data = clove_req_json(f'{cls.cryptoid_url()}/api.dws?q=unspent&key={api_key}&active={address}')
        unspent = data.get('unspent_outputs', [])

        for output in unspent:
            output['value'] = int(output['value'])

        unspent = sorted(unspent, key=lambda k: k['value'], reverse=True)

        utxo = []
        total = 0

        for output in unspent:
            value = from_base_units(output['value'])
            utxo.append(
                Utxo(
                    tx_id=output['tx_hash'],
                    vout=output['tx_ouput_n'],
                    value=value,
                    tx_script=output['script'],
                )
            )
            total += value
            if total > amount:
                return utxo

        logger.debug(f'Cannot find enough UTXO\'s. Found %.8f from %.8f.', total, amount)
Example #2
0
    def deserialize_raw_transaction(raw_transaction: str) -> Transaction:
        '''
        Deserializing raw transaction and returning Transaction object

        Args:
            raw_transaction (str): raw transaction hex string

        Returns:
            `ethereum.transactions.Transaction`: Ethereum transaction object

        Raises:
            ImpossibleDeserialization: if the raw transaction was not deserializable

        Example:
            >>> from clove.network import EthereumTestnet
            >>> network = EthereumTestnet()
            >>> transaction = network.deserialize_raw_transaction('0xf8f28201f4843b9aca008302251694ce07ab9477bc20790b88b398a2a9e0f626c7d26387b1a2bc2ec50000b8c47337c993000000000000000000000000000000000000000000000000000000005bd564819d3e84874c199ca4656d434060ec1a393750ab74000000000000000000000000000000000000000000000000d867f293ba129629a9f9355fa285b8d3711a9092000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080')  # noqa: E501
            <Transaction(821b)>
        '''
        try:
            transaction = rlp.hex_decode(raw_transaction, Transaction)
            logger.debug('Deserialization succeed')
        except (ValueError, RLPException):
            logger.warning(f'Deserialization with {raw_transaction} failed')
            raise ImpossibleDeserialization()

        transaction._cached_rlp = None
        transaction.make_mutable()

        return transaction
Example #3
0
    def extract_secret_from_redeem_transaction(self, tx_address: str) -> str:
        '''
        Extracting secret from redeem transaction.

        Args:
            tx_address (str): address of the redeem transaction

        Returns:
            str,: Secret string

        Raises:
            ValueError: When given transaction was not a redeem type transaction

        Example:
            >>> from clove.network import EthereumTestnet
            >>> network = EthereumTestnet()
            >>> network.extract_secret_from_redeem_transaction('0x9e41847c3cc780e4cb59902cf55657f0ee92642d9dee4145e090cbf206d4748f')  # noqa: E501
            b2eefaadbbefeb9d9467092b612464db7c6724f71b5c1d70c85853845728f0e9
        '''
        tx_dict = self.get_transaction(tx_address)
        method_id = self.extract_method_id(tx_dict['input'])
        if method_id != self.redeem:
            logger.debug('Not a redeem transaction.')
            raise ValueError('Not a redeem transaction.')
        method_name = self.get_method_name(method_id)
        input_types = get_abi_input_types(
            find_matching_fn_abi(self.abi, fn_identifier=method_name))
        input_values = decode_abi(input_types,
                                  Web3.toBytes(hexstr=tx_dict['input'][10:]))
        return input_values[0].hex()
Example #4
0
def find_redeem_transaction(
    recipient_address: str,
    contract_address: str,
    value: int,
    subdomain: str,
) -> Optional[str]:

    recipient_address = recipient_address.lower()
    contract_address = contract_address.lower()
    value = str(value)

    etherscan_api_key = os.getenv('ETHERSCAN_API_KEY')
    if not etherscan_api_key:
        raise ValueError('API key for etherscan is required.')

    data = clove_req_json(
        f'http://{subdomain}.etherscan.io/api?module=account&action=txlistinternal'
        f'&address={recipient_address}&apikey={etherscan_api_key}')

    for result in reversed(data['result']):
        if result['to'] == recipient_address and result[
                'from'] == contract_address and result['value'] == value:
            return result['hash']

    logger.debug('Redeem transaction not found.')
Example #5
0
    def get_utxo(cls, address: str, amount: float):
        data = clove_req_json(
            f'{cls.blockcypher_url()}/addrs/{address}'
            '?limit=2000&unspentOnly=true&includeScript=true&confirmations=6')
        unspent = data.get('txrefs', [])

        for output in unspent:
            output['value'] = int(output['value'])

        unspent = sorted(unspent, key=lambda k: k['value'], reverse=True)

        utxo = []
        total = 0

        for output in unspent:
            value = from_base_units(output['value'])
            utxo.append(
                Utxo(
                    tx_id=output['tx_hash'],
                    vout=output['tx_output_n'],
                    value=value,
                    tx_script=output['script'],
                ))
            total += value
            if total > amount:
                return utxo

        logger.debug(f'Cannot find enough UTXO\'s. Found %.8f from %.8f.',
                     total, amount)
Example #6
0
    def sign_raw_transaction(cls, raw_transaction: str,
                             private_key: str) -> str:
        '''
        Method to sign raw transactions.

        Args:
            raw_transaction (str): raw transaction hex string
            private_key (str): private key hex string

        Returns:
            str: signed transaction hex string

        Raises:
            ValueError: if given private key is invalid

        Example:
            >>> from clove.network import EthereumTestnet
            >>> network = EthereumTestnet()
            >>> raw_transaction = '0xf8f28201f4843b9aca008302251694ce07ab9477bc20790b88b398a2a9e0f626c7d26387b1a2bc2ec50000b8c47337c993000000000000000000000000000000000000000000000000000000005bd564819d3e84874c199ca4656d434060ec1a393750ab74000000000000000000000000000000000000000000000000d867f293ba129629a9f9355fa285b8d3711a9092000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000808080'  # noqa: E501
            >>> network.sign_raw_transaction(raw_transaction, MY_PRIVATE_KEY)
            '0xf901318201f4843b9aca008302251694ce07ab9477bc20790b88b398a2a9e0f626c7d26387b1a2bc2ec50000b8c47337c993000000000000000000000000000000000000000000000000000000005bd564819d3e84874c199ca4656d434060ec1a393750ab74000000000000000000000000000000000000000000000000d867f293ba129629a9f9355fa285b8d3711a90920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ca0d1c5b984ef2629eeb7c96f48a645566b2caf4130b0f3d7060ad5225946eee9e99f9928c5dfe868b45efbb9f8ae7d64d6162591c78961439c49e836947842e178'  # noqa: E501
        '''
        transaction = cls.deserialize_raw_transaction(raw_transaction)

        try:
            transaction.sign(private_key)
            logger.debug("Transaction signed")
        except Exception:
            logger.warning(
                "Invalid private key. Transaction could not be signed.")
            raise ValueError('Invalid private key.')

        return cls.get_raw_transaction(transaction)
Example #7
0
    def get_latest_block(cls) -> Optional[int]:
        '''
        Returns the number of the latest block.

        Returns:
            int, None: number of the latest block or None if API is not working

        Example:
            >>> from clove.network import Bitcoin
            >>> network = Bitcoin()
            >>> network.get_latest_block()
            544989
        '''
        try:
            latest_block = clove_req_json(
                f'{cls.api_url}/status?q=getInfo')['info']['blocks']
        except (TypeError, KeyError):
            logger.error(
                f'Cannot get latest block, bad response ({cls.symbols[0]})')
            return
        if not latest_block:
            logger.debug(f'Latest block not found ({cls.symbols[0]})')
            return
        logger.debug(f'Latest block found: {latest_block}')
        return latest_block
Example #8
0
    def get_fee(cls) -> Optional[float]:
        '''
        Getting actual fee per kb

        Returns:
            float, None: actual fee per kb or None if eg. API is not responding

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.get_fee()
            0.00024538
        '''
        try:
            # This endpoint is available from v0.3.1
            fee = clove_req_json(
                f'{cls.api_url}/utils/estimatefee?nbBlocks=1')['1']
        except (TypeError, KeyError):
            logger.error(
                f'Incorrect response from API when getting fee from {cls.api_url}/utils/estimatefee?nbBlocks=1'
            )
            return cls._calculate_fee()

        if fee == -1:
            logger.debug(f'Incorrect value in estimatedFee: {fee}')
            return cls._calculate_fee()
        fee = float(fee)
        if fee > 0:
            logger.warning(
                f'Got fee = 0 for ({cls.symbols[0]}), calculating manually')
            return fee
        return cls._calculate_fee()
Example #9
0
    def publish(self, transaction: Union[str, Transaction]) -> Optional[str]:
        '''
        Method to publish transaction

        Args:
            transaction (str, `ethereum.transactions.Transaction`): signed transaction

        Returns:
            str, None: transaction hash or None if something goes wrong

        Example:
            >>> from clove.network import EthereumTestnet
            >>> network = EthereumTestnet()
            >>> signed_transaction = '0xf901318201f4843b9aca008302251694ce07ab9477bc20790b88b398a2a9e0f626c7d26387b1a2bc2ec50000b8c47337c993000000000000000000000000000000000000000000000000000000005bd564819d3e84874c199ca4656d434060ec1a393750ab74000000000000000000000000000000000000000000000000d867f293ba129629a9f9355fa285b8d3711a90920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001ca0d1c5b984ef2629eeb7c96f48a645566b2caf4130b0f3d7060ad5225946eee9e99f9928c5dfe868b45efbb9f8ae7d64d6162591c78961439c49e836947842e178'  # noqa: E501
            >>> network.publish(signed_transaction)
            '0x4fd41289b816f6122e59a0759bd10441ead75d550562f4b3aad2fddc56eb3274'
        '''
        raw_transaction = transaction if isinstance(
            transaction, str) else self.get_raw_transaction(transaction)
        try:
            published_transaction = self.web3.eth.sendRawTransaction(
                raw_transaction).hex()
            logger.debug(
                f'Transaction {published_transaction} published successful')
            return published_transaction
        except ValueError:
            logger.warning(f'Unable to publish transaction {raw_transaction}')
            return
Example #10
0
    def extract_secret_from_redeem_transaction(
            cls, contract_address: str) -> Optional[str]:
        '''
        Extracting secret from redeem transaction based on contract address.

        Args:
            contract_address (str): address of the contract atomic swap contract

        Returns:
            str, None: Secret string or None if contract wasn't redeemed yet.

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.extract_secret_from_redeem_transaction('2N7Gxryn4dD1mdyGM3DMxMAwD7k3RBTJ1gP')
            90f6b9b9a34acb486654b3e9cdc02cce0b8e40a8845924ffda68453ac2477d20
        '''
        contract_transactions = clove_req_json(
            f'{cls.api_url}/addr/{contract_address}')['transactions']
        if not contract_transactions:
            logger.error(
                f'Cannot get contract transactions ({cls.symbols[0]})')
            return
        if len(contract_transactions) < 2:
            logger.debug(
                'There is no redeem transaction on this contract yet.')
            return
        redeem_transaction = cls.get_transaction(contract_transactions[0])
        if not redeem_transaction:
            logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})')
            return
        return cls.extract_secret(
            scriptsig=redeem_transaction['vin'][0]['scriptSig']['hex'])
Example #11
0
    def extract_secret_from_redeem_transaction(
            cls, contract_address: str) -> Optional[str]:

        query = """
        {
            allAddressTxes(orderBy: TIME_ASC, condition: { address: "%s" }) {
                nodes {
                    txId
                }
            }
        }
        """ % (contract_address)

        data = clove_req_json(f'{cls.api_url}/graphql',
                              post_data={'query': query})
        contract_transactions = data['data']['allAddressTxes']['nodes']

        if not contract_transactions:
            logger.error(
                f'Cannot get contract transactions ({cls.symbols[0]})')
            return
        if len(contract_transactions) < 2:
            logger.debug(
                'There is no redeem transaction on this contract yet.')
            return

        redeem_transaction = cls.get_transaction(
            contract_transactions[1]['txId'])
        if not redeem_transaction:
            logger.error(f'Cannot get redeem transaction ({cls.symbols[0]})')
            return

        return cls.extract_secret(scriptsig=redeem_transaction['vinsByTxId']
                                  ['nodes'][0]['scriptSig'])
Example #12
0
    def get_latest_block(cls) -> Optional[int]:
        '''Returns the number of the latest block.'''

        query = '''
        {
            allBlocks(orderBy: HEIGHT_DESC, first: 1) {
                nodes {
                    height
                }
            }
        }
        '''

        try:
            json_response = clove_req_json(f'{cls.api_url}/graphql',
                                           post_data={'query': query})
            latest_block = json_response['data']['allBlocks']['nodes'][0][
                'height']
        except (TypeError, KeyError):
            logger.error(
                f'Cannot get latest block, bad response ({cls.symbols[0]})')
            return
        if not latest_block:
            logger.debug(f'Latest block not found ({cls.symbols[0]})')
            return
        logger.debug(f'Latest block found: {latest_block}')
        return latest_block
Example #13
0
    def refund(self):
        contract = self.network.web3.eth.contract(address=self.contract_address, abi=self.network.abi)

        if self.locktime > datetime.utcnow():
            locktime_string = self.locktime.strftime('%Y-%m-%d %H:%M:%S')
            logger.warning(f"This contract is still valid! It can't be refunded until {locktime_string} UTC.")
            raise RuntimeError(f"This contract is still valid! It can't be refunded until {locktime_string} UTC.")

        refund_func = contract.functions.refund(self.secret_hash, self.recipient_address)
        tx_dict = {
            'nonce': self.network.web3.eth.getTransactionCount(self.refund_address),
            'value': 0,
            'gas': ETH_REFUND_GAS_LIMIT,
        }

        tx_dict = refund_func.buildTransaction(tx_dict)

        transaction = EthereumTokenTransaction(network=self.network)
        transaction.tx = Transaction(
            nonce=tx_dict['nonce'],
            gasprice=tx_dict['gasPrice'],
            startgas=tx_dict['gas'],
            to=tx_dict['to'],
            value=tx_dict['value'],
            data=Web3.toBytes(hexstr=tx_dict['data']),
        )
        transaction.value = self.value
        transaction.token = self.token
        transaction.recipient_address = self.refund_address
        logger.debug('Transaction refunded')
        return transaction
Example #14
0
    def extract_secret_from_redeem_transaction(
            cls, contract_address: str) -> Optional[str]:
        '''
        Extracting secret from redeem transaction based on contract address.

        Args:
            contract_address (str): address of the contract atomic swap contract

        Returns:
            str, None: Secret string or None if contract wasn't redeemed yet.

        Example:
            >>> from clove.network import BitcoinTestNet
            >>> network = BitcoinTestNet()
            >>> network.extract_secret_from_redeem_transaction('2N7Gxryn4dD1mdyGM3DMxMAwD7k3RBTJ1gP')
            90f6b9b9a34acb486654b3e9cdc02cce0b8e40a8845924ffda68453ac2477d20
        '''
        data = clove_req_json(
            f'{cls.blockcypher_url()}/addrs/{contract_address}/full')
        if not data:
            logger.error('Unexpected response from blockcypher')
            raise ValueError('Unexpected response from blockcypher')

        transactions = data['txs']
        if len(transactions) == 1:
            logger.debug('Contract was not redeemed yet.')
            return

        return cls.extract_secret(
            scriptsig=transactions[0]['inputs'][0]['script'])
Example #15
0
    def create_connection(self,
                          node: str,
                          timeout=2) -> Optional[socket.socket]:
        '''
        Establish connection to a given node.

        Args:
            node (str): node domain or IP address
            timeout (int): number of seconds to wait before raising timeout

        Returns:
            socket.socket: socket connection

        Example:
            >>> network.create_connection('104.248.185.143')
            <socket.socket fd=11, family=AddressFamily.AF_INET, type=2049, proto=6, laddr=('10.93.5.21', 36086), raddr=('104.248.185.143', 18333)>  # noqa: E501
        '''
        try:
            self.connection = socket.create_connection(address=(node,
                                                                self.port),
                                                       timeout=timeout)
        except (socket.timeout, ConnectionRefusedError, OSError):
            logger.debug('[%s] Could not establish connection to this node',
                         node)
            return

        logger.debug('[%s] Connection established, sending version packet',
                     node)
        if self.send_version():
            return self.connection
Example #16
0
def get_last_transactions(network: str) -> Optional[list]:

    resp = clove_req(
        f'https://chainz.cryptoid.info/{network}/api.dws?q=lasttxs')
    if not resp or resp.status != 200:
        logger.debug('Could not get last transactions for %s network', network)
        return
    return [t['hash'] for t in json.loads(resp.read().decode())]
Example #17
0
 def get_nodes(seed) -> list:
     logger.debug('Getting nodes from seed node %s', seed)
     try:
         hostname, alias, nodes = socket.gethostbyname_ex(seed)
     except (socket.herror, socket.gaierror):
         return []
     logger.debug('Got %s nodes', len(nodes))
     return nodes
Example #18
0
    def connect(self) -> Optional[str]:
        '''
        Connects to some node from the network.

        Returns:
            str, None: node IP address or domain, None if doesn't connect to any node

        Example:
            >>> network.connect()
            '198.251.83.19'
            >>> network.connection
            <socket.socket fd=12, family=AddressFamily.AF_INET, type=2049, proto=6, laddr=('10.93.5.21', 54300), raddr=('198.251.83.19', 18333)>  # noqa: E501
        '''
        if self.connection and self.send_ping():
            # already connected
            return self.get_current_node()

        if self.nodes:
            # fake seed node to enter the seed nodes loop
            self.seeds = (None, )

        random_seeds = list(self.seeds)
        shuffle(random_seeds)

        for seed in random_seeds:

            if seed is None:
                # get hardcoded nodes
                nodes = self.nodes
            else:
                # get nodes from seed node
                nodes = self.get_nodes(seed)

            nodes = self.filter_blacklisted_nodes(nodes)

            for node in nodes:

                if not self.create_connection(node):
                    self.terminate(node)
                    continue

                messages = self.capture_messages([msg_version, msg_verack])
                if not messages:
                    logger.debug(
                        '[%s] Failed to get version or version acknowledge message from node',
                        node)
                    self.terminate(node)
                    continue

                logger.debug(
                    '[%s] Got version, sending version acknowledge message',
                    node)

                if not self.send_verack():
                    self.terminate(node)
                    continue

                return node
Example #19
0
 def send_message(self, msg: object, timeout: int = 2) -> bool:
     try:
         self.connection.settimeout(timeout)
         self.connection.send(msg.to_bytes())
     except (socket.timeout, ConnectionRefusedError, OSError) as e:
         logger.debug('Failed to send %s message', msg.command.decode())
         logger.debug(e)
         return False
     return True
Example #20
0
 def _get_block_hash(cls, block_number: int) -> str:
     '''Getting block hash by its number'''
     try:
         block_hash = clove_req_json(f'{cls.api_url}/block-index/{block_number}')['blockHash']
     except (TypeError, KeyError):
         logger.error(f'Cannot get block hash for block {block_number} ({cls.symbols[0]})')
         return
     logger.debug(f'Found hash for block {block_number}: {block_hash}')
     return block_hash
Example #21
0
 def get_balance(cls, wallet_address: str) -> float:
     api_key = os.environ.get('CRYPTOID_API_KEY')
     if api_key is None:
         raise ValueError('API key for cryptoid is required to get balance.')
     data = clove_req_json(f'{cls.cryptoid_url()}/api.dws?q=getbalance&a={wallet_address}&key={api_key}')
     if data is None:
         logger.debug('Could not get details for address %s in %s network', wallet_address, cls.symbols[0])
         return
     return data
Example #22
0
def get_current_fee(network: str) -> Optional[float]:
    """Getting current network fee from Clove API"""

    resp = clove_req_json(f'{CLOVE_API_URL}/fee/{network}')

    if not resp:
        logger.debug('Could not get current fee for %s network', network)
        return

    return resp['fee']
Example #23
0
 def extract_secret_from_redeem_transaction(
         cls, contract_address: str) -> Optional[str]:
     contract_transactions = clove_req_json(
         f'https://mona.chainseeker.info/api/v1/txids/{contract_address}')
     if len(contract_transactions) < 2:
         logger.debug(
             'There is no redeem transaction on this contract yet.')
         return
     redeem_transaction = cls.get_transaction(contract_transactions[1])
     return cls.extract_secret(redeem_transaction['hex'])
Example #24
0
def get_balance_blockcypher(network: str, address: str,
                            testnet: bool) -> Optional[float]:
    subnet = 'test3' if testnet else 'main'
    url = f'https://api.blockcypher.com/v1/{network.lower()}/{subnet}/addrs/{address}/full?limit=2000'
    data = clove_req_json(url)
    if data is None:
        logger.debug('Could not get details for address %s in %s network',
                     address, network)
        return
    return from_base_units(data['balance'])
Example #25
0
 def publish(self, transaction: Union[str, Transaction]) -> Optional[str]:
     """ Method to publish transaction """
     raw_transaction = transaction if isinstance(transaction, str) else self.get_raw_transaction(transaction)
     try:
         published_transaction = self.web3.eth.sendRawTransaction(raw_transaction).hex()
         logger.debug(f'Transaction {published_transaction} published successful')
         return published_transaction
     except ValueError:
         logger.warning(f'Unable to publish transaction {raw_transaction}')
         return
Example #26
0
 def extract_secret_from_redeem_transaction(self, tx_address: str) -> str:
     tx_dict = self.get_transaction(tx_address)
     method_id = self.extract_method_id(tx_dict['input'])
     if method_id != self.redeem:
         logger.debug('Not a redeem transaction.')
         raise ValueError('Not a redeem transaction.')
     method_name = self.get_method_name(method_id)
     input_types = get_abi_input_types(find_matching_fn_abi(self.abi, fn_identifier=method_name))
     input_values = decode_abi(input_types, Web3.toBytes(hexstr=tx_dict['input'][10:]))
     return input_values[0].hex()
Example #27
0
 def _get_transactions_from_block(cls, block_number: int):
     '''Getting transactions from block by given block number'''
     block_hash = cls._get_block_hash(block_number)
     if not block_hash:
         return
     transactions_page = clove_req_json(f'{cls.api_url}/txs/?block={block_hash}')
     if not transactions_page:
         return
     transactions = transactions_page['txs']
     logger.debug(f'Found {len(transactions)} in block {block_number}')
     return transactions
Example #28
0
def get_transaction_size(network: str, tx_hash: str) -> Optional[int]:
    """WARNING: this method is using undocumented endpoint used by chainz.cryptoid.info site."""
    resp = clove_req(
        f'https://chainz.cryptoid.info/explorer/tx.raw.dws?coin={network}&id={tx_hash}'
    )
    if not resp or resp.status != 200:
        logger.debug('Could not get transaction %s size for %s network',
                     tx_hash, network)
        return
    tx_details = json.loads(resp.read().decode())
    return tx_details['size']
Example #29
0
 def get_latest_block(cls) -> Optional[int]:
     '''Returns the number of the latest block.'''
     try:
         latest_block = clove_req_json(f'{cls.api_url}/status?q=getInfo')['info']['blocks']
     except (TypeError, KeyError):
         logger.error(f'Cannot get latest block, bad response ({cls.symbols[0]})')
         return
     if not latest_block:
         logger.debug(f'Latest block not found ({cls.symbols[0]})')
         return
     logger.debug(f'Latest block found: {latest_block}')
     return latest_block
Example #30
0
    def sign_raw_transaction(cls, raw_transaction: str, private_key: str) -> str:
        """ Method to sign raw transactions """
        transaction = cls.deserialize_raw_transaction(raw_transaction)

        try:
            transaction.sign(private_key)
            logger.debug("Transaction signed")
        except Exception:
            logger.warning("Invalid private key. Transaction could not be signed.")
            raise ValueError('Invalid private key.')

        return cls.get_raw_transaction(transaction)