Exemple #1
0
def _mine(config, client, wallet, dashboard=False):
    """ Start a mining chip if not already running. Otherwise mine at CLI.

    On a 21 Bitcoin Computer, we attempt to start the mining chip if
    is not already running. If it is already running, repeated
    invocation of 21 mine will result in buffered mining (advances
    against the next day's mining proceeds). Finally, if we are
    running the 21 software on an arbitrary device (i.e. not on a
    Bitcoin Computer), we prompt the user to use 21 earn instead.

    Args:
        config (Config): config object used for getting .two1 information
        client (two1.server.rest_client.TwentyOneRestClient) an object for
            sending authenticated requests to the TwentyOne backend.
        wallet (two1.wallet.Wallet): a user's wallet instance
        dashboard (bool): shows minertop dashboard if True
    """
    if bitcoin_computer.has_mining_chip():
        if not is_minerd_running():
            start_minerd(config, dashboard)
        elif dashboard:
            show_minertop(dashboard)
        # if minerd is running and we have not specified a dashboard
        # flag do a cpu mine
        else:
            start_cpu_mining(config.username, client, wallet)
    else:
        logger.info(uxstring.UxString.use_21_earn_instead)
        sys.exit(1)
Exemple #2
0
def status_mining(client):
    """ Prints the mining status if the device has a mining chip

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api

    Returns:
        dict: a dictionary containing 'is_mining', 'hashrate', and 'mined' values
    """
    has_chip = bitcoin_computer.has_mining_chip()
    is_mining, mined, hashrate = None, None, None
    if has_chip:
        try:
            hashrate = bitcoin_computer.get_hashrate("15min")
            if hashrate > 0:
                hashrate = uxstring.UxString.status_mining_hashrate.format(
                    hashrate / 1e9)
            else:
                hashrate = uxstring.UxString.status_mining_hashrate_unknown
        except FileNotFoundError:
            is_mining = uxstring.UxString.status_mining_file_not_found
        except TimeoutError:
            is_mining = uxstring.UxString.status_mining_timeout
        else:
            is_mining = uxstring.UxString.status_mining_success

        mined = client.get_mined_satoshis()
        logger.info(
            uxstring.UxString.status_mining.format(is_mining, hashrate, mined))

    return dict(is_mining=is_mining, hashrate=hashrate, mined=mined)
Exemple #3
0
def status_mining(client):
    """ Prints the mining status if the device has a mining chip

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api

    Returns:
        dict: a dictionary containing 'is_mining', 'hashrate', and 'mined' values
    """
    has_chip = bitcoin_computer.has_mining_chip()
    is_mining, mined, hashrate = None, None, None
    if has_chip:
        try:
            hashrate = bitcoin_computer.get_hashrate("15min")
            if hashrate > 0:
                hashrate = uxstring.UxString.status_mining_hashrate.format(hashrate/1e9)
            else:
                hashrate = uxstring.UxString.status_mining_hashrate_unknown
        except FileNotFoundError:
            is_mining = uxstring.UxString.status_mining_file_not_found
        except TimeoutError:
            is_mining = uxstring.UxString.status_mining_timeout
        else:
            is_mining = uxstring.UxString.status_mining_success

        mined = client.get_mined_satoshis()
        logger.info(uxstring.UxString.status_mining.format(is_mining, hashrate, mined))

    return dict(is_mining=is_mining, hashrate=hashrate, mined=mined)
Exemple #4
0
def _mine(config, client, wallet, dashboard=False):
    """ Start a mining chip if not already running. Otherwise mine at CLI.

    On a 21 Bitcoin Computer, we attempt to start the mining chip if
    is not already running. If it is already running, repeated
    invocation of 21 mine will result in buffered mining (advances
    against the next day's mining proceeds). Finally, if we are
    running the 21 software on an arbitrary device (i.e. not on a
    Bitcoin Computer), we prompt the user to use 21 earn instead.

    Args:
        config (Config): config object used for getting .two1 information
        client (two1.server.rest_client.TwentyOneRestClient) an object for
            sending authenticated requests to the TwentyOne backend.
        wallet (two1.wallet.Wallet): a user's wallet instance
        dashboard (bool): shows minertop dashboard if True
    """
    if bitcoin_computer.has_mining_chip():
        if not is_minerd_running():
            start_minerd(config, dashboard)
        elif dashboard:
            show_minertop(dashboard)
        # if minerd is running and we have not specified a dashboard
        # flag do a cpu mine
        else:
            start_cpu_mining(config.username, client, wallet)
    else:
        logger.info(uxstring.UxString.use_21_earn_instead)
        sys.exit(1)
Exemple #5
0
def _send(wallet, address, satoshis, verbose, use_unconfirmed=False):
    """Send bitcoin to the specified address"""
    txids = []
    try:
        txids = wallet.send_to(address=address, amount=satoshis, use_unconfirmed=use_unconfirmed)
        # For now there is only a single txn created, so assume it's 0
        txid, txn = txids[0]["txid"], txids[0]["txn"]
        if verbose:
            logger.info(uxstring.UxString.send_success_verbose.format(satoshis, address, txid, txn))
        else:
            logger.info(uxstring.UxString.send_success.format(satoshis, address, txid))
    except ValueError as e:
        # This will trigger if there's a below dust-limit output.
        raise exceptions.Two1Error(str(e))
    except WalletBalanceError as e:
        if wallet.unconfirmed_balance() > satoshis:
            raise exceptions.Two1Error(uxstring.UxString.send_insufficient_confirmed + str(e))
        else:
            balance = min(wallet.confirmed_balance(), wallet.unconfirmed_balance())
            if has_mining_chip():
                raise exceptions.Two1Error(uxstring.UxString.send_insufficient_blockchain_21bc.format(
                    balance, satoshis, address, str(e)))
            else:
                raise exceptions.Two1Error(uxstring.UxString.send_insufficient_blockchain_free.format(
                    balance, satoshis, address, str(e)))
    except DataProviderError as e:
        if "rejected" in str(e):
            raise exceptions.Two1Error(uxstring.UxString.send_rejected)
        else:
            raise exceptions.Two1Error(str(e))
    return txids
Exemple #6
0
def get_work(client):
    """ Get work from the pool using the rest client.

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api

    Returns:
        WorkNotification: a Swirl work notification message
    """
    try:
        response = client.get_work()
    except exceptions.ServerRequestError as e:
        if e.status_code == 403 and "detail" in e.data and "TO200" in e.data["detail"]:
            raise exceptions.BitcoinComputerNeededError(
                msg=uxstring.UxString.mining_bitcoin_computer_needed, response=response)
        elif e.status_code == 403 and e.data.get("detail") == "TO201":
            raise exceptions.MiningDisabledError(uxstring.UxString.Error.suspended_account)
        elif e.status_code == 403 and e.data.get("detail") == "TO501":
            raise exceptions.MiningDisabledError(uxstring.UxString.daily_mining_limit_reached)
        elif e.status_code == 403 and e.data.get("detail") == "TO502":
            raise exceptions.MiningDisabledError(uxstring.UxString.lifetime_earn_limit_reached)
        elif e.status_code == 404:
            if has_mining_chip():
                raise exceptions.MiningDisabledError(uxstring.UxString.daily_mining_limit_reached)
            else:
                raise exceptions.MiningDisabledError(uxstring.UxString.earn_limit_reached)
        else:
            raise e

    msg_factory = message_factory.SwirlMessageFactory()
    msg = base64.decodebytes(response.content)
    work = msg_factory.read_object(msg)
    return work
Exemple #7
0
    def check_BC_has_chip(self):
        """ Checks if the system has a 21 bitcoin shield

        Returns:
            Check.Result, str, str: Result of the check
                                    Human readable message describing the check
                                    "Yes" if the device has a bitcoin shield, "No" otherwise
        """
        check_str = "Has Mining Chip"
        if bitcoin_computer.has_mining_chip():
            return Check.Result.PASS, check_str, "Yes"
        else:
            return Check.Result.FAIL, check_str, "No"
Exemple #8
0
    def check_BC_has_chip(self):
        """ Checks if the system has a 21 bitcoin shield

        Returns:
            Check.Result, str, str: Result of the check
                                    Human readable message describing the check
                                    "Yes" if the device has a bitcoin shield, "No" otherwise
        """
        check_str = "Has Mining Chip"
        if bitcoin_computer.has_mining_chip():
            return Check.Result.PASS, check_str, "Yes"
        else:
            return Check.Result.FAIL, check_str, "No"
Exemple #9
0
def login_account(config, machine_auth, username=None, password=None):
    """ Log in a user into the two1 account

    Args:
        config (Config): config object used for getting .two1 information
        username (str): optional command line arg to skip username prompt
        password (str): optional command line are to skip password prompt
    """
    # prints the sign up page link when a username is not set and not on a BC
    if not config.username and not bitcoin_computer.has_mining_chip():
        logger.info(uxstring.UxString.signin_title)

    # uses specifies username or asks for a different one
    username = username or get_username_interactive()
    password = password or get_password_interactive()

    # use existing username in config
    rest_client = _rest_client.TwentyOneRestClient(two1.TWO1_HOST,
                                                   machine_auth, username)

    # get the payout address and the pubkey from the machine auth wallet
    machine_auth_pubkey_b64 = base64.b64encode(
        machine_auth.public_key.compressed_bytes).decode()
    payout_address = machine_auth.wallet.current_address

    logger.info(uxstring.UxString.login_in_progress.format(username))
    try:
        rest_client.login(payout_address=payout_address, password=password)
    # handles 401 gracefully
    except exceptions.ServerRequestError as ex:
        if ex.status_code == 403 and "error" in ex.data and ex.data[
                "error"] == "TO408":
            email = ex.data["email"]
            raise exceptions.UnloggedException(
                click.style(uxstring.UxString.unconfirmed_email.format(email),
                            fg="blue"))
        elif ex.status_code == 403 or ex.status_code == 404:
            raise exceptions.UnloggedException(
                uxstring.UxString.incorrect_password)
        else:
            raise ex

    logger.info(uxstring.UxString.payout_address.format(payout_address))
    logger.info(uxstring.UxString.get_started)

    # Save the new username and auth key
    config.set("username", username)
    config.set("mining_auth_pubkey", machine_auth_pubkey_b64)
    config.save()
Exemple #10
0
def login_account(config, machine_auth, username=None, password=None):
    """ Log in a user into the two1 account

    Args:
        config (Config): config object used for getting .two1 information
        username (str): optional command line arg to skip username prompt
        password (str): optional command line are to skip password prompt
    """
    # prints the sign up page link when a username is not set and not on a BC
    if not config.username and not bitcoin_computer.has_mining_chip():
        logger.info(uxstring.UxString.signin_title)

    # uses specifies username or asks for a different one
    username = username or get_username_interactive()
    password = password or get_password_interactive()

    # use existing username in config
    rest_client = _rest_client.TwentyOneRestClient(two1.TWO1_HOST, machine_auth, username)

    # get the payout address and the pubkey from the machine auth wallet
    machine_auth_pubkey_b64 = base64.b64encode(machine_auth.public_key.compressed_bytes).decode()
    payout_address = machine_auth.wallet.current_address

    logger.info(uxstring.UxString.login_in_progress.format(username))
    try:
        rest_client.login(payout_address=payout_address, password=password)
    # handles 401 gracefully
    except exceptions.ServerRequestError as ex:
        if ex.status_code == 403 and "error" in ex.data and ex.data["error"] == "TO408":
            email = ex.data["email"]
            raise exceptions.UnloggedException(
                click.style(uxstring.UxString.unconfirmed_email.format(email),
                            fg="blue"))
        elif ex.status_code == 403 or ex.status_code == 404:
            raise exceptions.UnloggedException(uxstring.UxString.incorrect_password)
        else:
            raise ex

    logger.info(uxstring.UxString.payout_address.format(payout_address))
    logger.info(uxstring.UxString.get_started)

    # If config file hasn't been created yet ask for opt-in to analytics
    if not config.username:
        analytics_optin(config)

    # Save the new username and auth key
    config.set("username", username)
    config.set("mining_auth_pubkey", machine_auth_pubkey_b64)
    config.save()
Exemple #11
0
def get_work(client):
    """ Get work from the pool using the rest client.

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api

    Returns:
        WorkNotification: a Swirl work notification message
    """
    try:
        response = client.get_work()
    except exceptions.ServerRequestError as e:
        if e.status_code == 403 and "detail" in e.data and "TO200" in e.data[
                "detail"]:
            raise exceptions.BitcoinComputerNeededError(
                msg=uxstring.UxString.mining_bitcoin_computer_needed,
                response=response)
        elif e.status_code == 403 and e.data.get("detail") == "TO201":
            raise exceptions.MiningDisabledError(
                uxstring.UxString.Error.suspended_account)
        elif e.status_code == 403 and e.data.get("detail") == "TO501":
            raise exceptions.MiningDisabledError(
                uxstring.UxString.monthly_mining_limit_reached)
        elif e.status_code == 403 and e.data.get("detail") == "TO502":
            raise exceptions.MiningDisabledError(
                uxstring.UxString.lifetime_earn_limit_reached)
        elif e.status_code == 403 and e.data.get("detail") == "TO503":
            raise exceptions.MiningDisabledError(
                uxstring.UxString.no_earn_allocations.format(
                    two1.TWO1_WWW_HOST, client.username))
        elif e.status_code == 404:
            if bitcoin_computer.has_mining_chip():
                raise exceptions.MiningDisabledError(
                    uxstring.UxString.monthly_mining_limit_reached)
            else:
                raise exceptions.MiningDisabledError(
                    uxstring.UxString.earn_limit_reached)
        else:
            raise e

    msg_factory = message_factory.SwirlMessageFactory()
    msg = base64.decodebytes(response.content)
    work = msg_factory.read_object(msg)
    return work
Exemple #12
0
def get_work(client):
    """ Get work from the pool using the rest client.

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api

    Returns:
        WorkNotification: a Swirl work notification message
    """
    try:
        response = client.get_work()
    except exceptions.ServerRequestError as e:
        profile_url = "{}/{}".format(two1.TWO1_WWW_HOST, client.username)
        profile_cta = uxstring.UxString.mining_profile_call_to_action.format(
            profile_url)
        err_string = None
        if e.status_code == 403 and e.data.get("error") == "TO201":
            err_string = uxstring.UxString.Error.suspended_account
        elif e.status_code == 403 and e.data.get("error") == "TO501":
            err_string = uxstring.UxString.monthly_mining_limit_reached
        elif e.status_code == 403 and e.data.get("error") == "TO502":
            err_string = uxstring.UxString.lifetime_earn_limit_reached
        elif e.status_code == 403 and e.data.get("error") == "TO503":
            err_string = uxstring.UxString.no_earn_allocations.format(
                two1.TWO1_WWW_HOST, client.username)
        elif e.status_code == 404:
            if bitcoin_computer.has_mining_chip():
                err_string = uxstring.UxString.monthly_mining_limit_reached
            else:
                err_string = uxstring.UxString.earn_limit_reached

        if err_string:
            raise exceptions.MiningDisabledError("{}\n\n{}".format(
                err_string, profile_cta))
        else:
            raise e

    msg_factory = message_factory.SwirlMessageFactory()
    msg = base64.decodebytes(response.content)
    work = msg_factory.read_object(msg)
    return work
Exemple #13
0
def _send(wallet, address, satoshis, verbose, use_unconfirmed=False):
    """Send bitcoin to the specified address"""
    txids = []
    try:
        txids = wallet.send_to(address=address,
                               amount=satoshis,
                               use_unconfirmed=use_unconfirmed)
        # For now there is only a single txn created, so assume it's 0
        txid, txn = txids[0]["txid"], txids[0]["txn"]
        if verbose:
            logger.info(
                uxstring.UxString.send_success_verbose.format(
                    satoshis, address, txid, txn))
        else:
            logger.info(
                uxstring.UxString.send_success.format(satoshis, address, txid))
    except ValueError as e:
        # This will trigger if there's a below dust-limit output.
        raise exceptions.Two1Error(str(e))
    except WalletBalanceError as e:
        if wallet.unconfirmed_balance() > satoshis:
            raise exceptions.Two1Error(
                uxstring.UxString.send_insufficient_confirmed + str(e))
        else:
            balance = min(wallet.confirmed_balance(),
                          wallet.unconfirmed_balance())
            if has_mining_chip():
                raise exceptions.Two1Error(
                    uxstring.UxString.send_insufficient_blockchain_21bc.format(
                        balance, satoshis, address))
            else:
                raise exceptions.Two1Error(
                    uxstring.UxString.send_insufficient_blockchain_free.format(
                        balance, satoshis, address))
    except DataProviderError as e:
        if "rejected" in str(e):
            raise exceptions.Two1Error(uxstring.UxString.send_rejected)
        else:
            raise exceptions.Two1Error(str(e))
    return txids
Exemple #14
0
def get_work(client):
    """ Get work from the pool using the rest client.

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api

    Returns:
        WorkNotification: a Swirl work notification message
    """
    try:
        response = client.get_work()
    except exceptions.ServerRequestError as e:
        profile_url = "{}/{}".format(two1.TWO1_WWW_HOST, client.username)
        profile_cta = uxstring.UxString.mining_profile_call_to_action.format(profile_url)
        err_string = None
        if e.status_code == 403 and e.data.get("error") == "TO201":
            err_string = uxstring.UxString.Error.suspended_account
        elif e.status_code == 403 and e.data.get("error") == "TO501":
            err_string = uxstring.UxString.monthly_mining_limit_reached
        elif e.status_code == 403 and e.data.get("error") == "TO502":
            err_string = uxstring.UxString.lifetime_earn_limit_reached
        elif e.status_code == 403 and e.data.get("error") == "TO503":
            err_string = uxstring.UxString.no_earn_allocations.format(two1.TWO1_WWW_HOST,
                                                                      client.username)
        elif e.status_code == 404:
            if bitcoin_computer.has_mining_chip():
                err_string = uxstring.UxString.monthly_mining_limit_reached
            else:
                err_string = uxstring.UxString.earn_limit_reached

        if err_string:
            raise exceptions.MiningDisabledError("{}\n\n{}".format(err_string, profile_cta))
        else:
            raise e

    msg_factory = message_factory.SwirlMessageFactory()
    msg = base64.decodebytes(response.content)
    work = msg_factory.read_object(msg)
    return work
Exemple #15
0
def _buy(config, client, machine_auth, resource, info_only=False, payment_method='offchain', header=(),
         method='GET', output_file=None, data=None, data_file=None, maxprice=10000):
    """Purchase a 402-enabled resource via CLI.

    This function attempts to purchase the requested resource using the
    `payment_method` and then write out its results to STDOUT. This allows a
    user to view results or pipe them into another command-line function.

    Args:
        config (two1.commands.config.Config): an object necessary for various
            user-specific actions, as well as for using the `capture_usage`
            function decorator.
        client (two1.server.rest_client.TwentyOneRestClient) an object for
            sending authenticated requests to the TwentyOne backend.
        machine_auth (two1.server.machine_auth_wallet.MachineAuthWallet): a wallet
            used for machine authentication.
        resource (str): a URI of the form scheme://host:port/path with `http`
            and `https` strictly enforced as required schemes.
        info_only (bool): if True, do not purchase the resource, and cause the
            function to write only the 402-related headers.
        payment_method (str): the payment method used for the purchase.
        header (tuple): list of HTTP headers to send with the request.
        method (str): the HTTP method/verb to make with the request.
        output_file (str): name of the file to redirect function output.
        data (str): serialized data to send with the request. The function will
            attempt to deserialize the data and determine its encoding type.
        data_file (str): name of the data file to send in HTTP body.
        maxprice (int): allowed maximum price (in satoshis) of the resource.

    Raises:
        click.ClickException: if some set of parameters or behavior cause the
            purchase to not complete successfully for any reason.
    """
    # Find the correct payment method
    if payment_method == 'offchain':
        requests = bitrequests.BitTransferRequests(machine_auth, config.username)
    elif payment_method == 'onchain':
        requests = bitrequests.OnChainRequests(machine_auth.wallet)
    elif payment_method == 'channel':
        requests = bitrequests.ChannelRequests(machine_auth.wallet)
    else:
        raise click.ClickException(uxstring.UxString.buy_bad_payment_method.format(payment_method))

    # Request user consent if they're creating a channel for the first time
    if payment_method == 'channel' and not requests._channelclient.list():
        confirmed = click.confirm(uxstring.UxString.buy_channel_warning.format(
            requests.DEFAULT_DEPOSIT_AMOUNT, statemachine.PaymentChannelStateMachine.PAYMENT_TX_MIN_OUTPUT_AMOUNT
        ), default=True)
        if not confirmed:
            raise click.ClickException(uxstring.UxString.buy_channel_aborted)

    resource = parse_resource(resource)

    # Retrieve 402-related header information, print it, then exit
    if info_only:
        response = requests.get_402_info(resource)
        return logger.info('\n'.join(['{}: {}'.format(key, val) for key, val in response.items()]))

    # Collect HTTP header parameters into a single dictionary
    headers = {key.strip(): value.strip() for key, value in (h.split(':') for h in header)}

    # Handle data if applicable
    if data or data_file:
        method = 'POST' if method == 'GET' else method
        data, headers['Content-Type'] = _parse_post_data(data)

    # Make the paid request for the resource
    try:
        response = requests.request(
            method.lower(), resource, max_price=maxprice, data=data or data_file, headers=headers
        )
    except bitrequests.ResourcePriceGreaterThanMaxPriceError as e:
        raise click.ClickException(uxstring.UxString.Error.resource_price_greater_than_max_price.format(e))
    except wallet_exceptions.DustLimitError as e:
        raise click.ClickException(e)
    except ValueError as e:
        if bitcoin_computer.has_mining_chip():
            raise click.ClickException(uxstring.UxString.Error.insufficient_funds_mine_more)
        else:
            raise click.ClickException(uxstring.UxString.Error.insufficient_funds_earn_more)
    except Exception as e:
        raise click.ClickException(e)

    # Write response text to stdout or a filename if provided
    if not output_file:
        try:
            json_resp = response.json()
        except ValueError:
            logger.info(response.content, nl=False)
        else:
            if isinstance(json_resp, dict):
                ordered = OrderedDict(sorted(json_resp.items()))
                logger.info(json.dumps(ordered, indent=4), nl=False)
            else:
                logger.info(json.dumps(json_resp, indent=4), nl=False)
    else:
        with open(output_file, 'wb') as f:
            logger.info(response.content, file=f, nl=False)

    logger.info('', err=True)  # newline for pretty-printing errors to stdout

    # Exit successfully if no amount was paid for the resource (standard HTTP request)
    if not hasattr(response, 'amount_paid'):
        return

    # Fetch and write out diagnostic payment information for balances
    if payment_method == 'offchain':
        twentyone_balance = client.get_earnings()["total_earnings"]
        logger.info(uxstring.UxString.buy_balances.format(response.amount_paid, '21.co', twentyone_balance), err=True)
    elif payment_method == 'onchain':
        onchain_balance = min(requests.wallet.confirmed_balance(), requests.wallet.unconfirmed_balance())
        logger.info(uxstring.UxString.buy_balances.format(response.amount_paid, 'blockchain', onchain_balance),
                    err=True)
    elif payment_method == 'channel':
        channel_client = requests._channelclient
        channel_client.sync()
        channels_balance = sum(s.balance for s in (channel_client.status(url) for url in channel_client.list())
                               if s.state == channels.PaymentChannelState.READY)
        logger.info(uxstring.UxString.buy_balances.format(response.amount_paid, 'payment channels', channels_balance),
                    err=True)
def test_has_mining_asic(mock_file, side_effect, outcome):
    """ Mocks the builtin open function to test various outcomes of opening and reading the product file """
    with mock.patch.object(builtins, "open", mock_file) as open_mock:
        open_mock.side_effect = side_effect
        assert bitcoin_computer.has_mining_chip() == outcome
Exemple #17
0
def status_wallet(client, wallet, detail=False):
    """ Logs a formatted string displaying wallet status to the command line

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api
        detail (bool): Lists all balance details in status report

    Returns:
        dict: a dictionary of 'wallet' and 'buyable' items with formatted
            strings for each value
    """
    channel_client = channels.PaymentChannelClient(wallet)
    user_balances = _get_balances(client, wallet, channel_client)

    status_wallet_dict = {
        "twentyone_balance": user_balances.twentyone,
        "onchain": user_balances.onchain,
        "flushing": user_balances.flushed,
        "channels_balance": user_balances.channels
    }
    logger.info(uxstring.UxString.status_wallet.format(**status_wallet_dict))

    if detail:
        # show balances by address for default wallet
        address_balances = wallet.balances_by_address(0)
        status_addresses = []
        for addr, balances in address_balances.items():
            if balances['confirmed'] > 0 or balances['total'] > 0:
                status_addresses.append(
                    uxstring.UxString.status_wallet_address.format(
                        addr, balances['confirmed'], balances['total']))

        # Display status for all payment channels
        status_channels = []
        for url in channel_client.list():
            status_resp = channel_client.status(url)
            url = urllib.parse.urlparse(url)
            status_channels.append(
                uxstring.UxString.status_wallet_channel.format(
                    url.scheme, url.netloc, status_resp.state,
                    status_resp.balance,
                    format_expiration_time(status_resp.expiration_time)))
        if not len(status_channels):
            status_channels = [uxstring.UxString.status_wallet_channels_none]

        logger.info(
            uxstring.UxString.status_wallet_detail_on.format(
                addresses=''.join(status_addresses),
                channels=''.join(status_channels)))
    else:
        logger.info(uxstring.UxString.status_wallet_detail_off)

    total_balance = user_balances.twentyone + user_balances.onchain

    if total_balance == 0:
        if bitcoin_computer.has_mining_chip():
            command = "21 mine"
        else:
            command = "21 earn"
        logger.info(
            uxstring.UxString.status_empty_wallet.format(
                click.style(command, bold=True)))
    else:
        buy21 = click.style("21 buy", bold=True)
        buy21help = click.style("21 buy --help", bold=True)
        logger.info(
            uxstring.UxString.status_exit_message.format(buy21, buy21help))

    return {
        "wallet": status_wallet_dict,
    }
Exemple #18
0
def status_wallet(client, wallet, detail=False):
    """ Logs a formatted string displaying wallet status to the command line

    Args:
        client (TwentyOneRestClient): rest client used for communication with the backend api
        detail (bool): Lists all balance details in status report

    Returns:
        dict: a dictionary of 'wallet' and 'buyable' items with formatted
            strings for each value
    """
    channel_client = channels.PaymentChannelClient(wallet)
    user_balances = _get_balances(client, wallet, channel_client)

    status_wallet_dict = {
        "twentyone_balance": user_balances.twentyone,
        "onchain": user_balances.onchain,
        "flushing": user_balances.flushed,
        "channels_balance": user_balances.channels
    }
    logger.info(uxstring.UxString.status_wallet.format(**status_wallet_dict))

    if detail:
        # show balances by address for default wallet
        address_balances = wallet.balances_by_address(0)
        status_addresses = []
        for addr, balances in address_balances.items():
            if balances['confirmed'] > 0 or balances['total'] > 0:
                status_addresses.append(uxstring.UxString.status_wallet_address.format(
                    addr, balances['confirmed'], balances['total']))

        # Display status for all payment channels
        status_channels = []
        for url in channel_client.list():
            status_resp = channel_client.status(url)
            url = urllib.parse.urlparse(url)
            status_channels.append(uxstring.UxString.status_wallet_channel.format(
                url.scheme, url.netloc, status_resp .state, status_resp .balance,
                format_expiration_time(status_resp .expiration_time)))
        if not len(status_channels):
            status_channels = [uxstring.UxString.status_wallet_channels_none]

        logger.info(uxstring.UxString.status_wallet_detail_on.format(
            addresses=''.join(status_addresses), channels=''.join(status_channels)))
    else:
        logger.info(uxstring.UxString.status_wallet_detail_off)

    total_balance = user_balances.twentyone + user_balances.onchain

    if total_balance == 0:
        if bitcoin_computer.has_mining_chip():
            command = "21 mine"
        else:
            command = "21 earn"
        logger.info(uxstring.UxString.status_empty_wallet.format(
            click.style(command, bold=True)))
    else:
        buy21 = click.style("21 buy", bold=True)
        buy21help = click.style("21 buy --help", bold=True)
        logger.info(uxstring.UxString.status_exit_message.format(buy21, buy21help))

    return {
        "wallet": status_wallet_dict,
    }
Exemple #19
0
def _buy(config,
         client,
         machine_auth,
         resource,
         info_only=False,
         payment_method='offchain',
         header=(),
         method='GET',
         output_file=None,
         data=None,
         data_file=None,
         maxprice=10000):
    """Purchase a 402-enabled resource via CLI.

    This function attempts to purchase the requested resource using the
    `payment_method` and then write out its results to STDOUT. This allows a
    user to view results or pipe them into another command-line function.

    Args:
        config (two1.commands.config.Config): an object necessary for various
            user-specific actions, as well as for using the `capture_usage`
            function decorator.
        client (two1.server.rest_client.TwentyOneRestClient) an object for
            sending authenticated requests to the TwentyOne backend.
        machine_auth (two1.server.machine_auth_wallet.MachineAuthWallet): a wallet
            used for machine authentication.
        resource (str): a URI of the form scheme://host:port/path with `http`
            and `https` strictly enforced as required schemes.
        info_only (bool): if True, do not purchase the resource, and cause the
            function to write only the 402-related headers.
        payment_method (str): the payment method used for the purchase.
        header (tuple): list of HTTP headers to send with the request.
        method (str): the HTTP method/verb to make with the request.
        output_file (str): name of the file to redirect function output.
        data (str): serialized data to send with the request. The function will
            attempt to deserialize the data and determine its encoding type.
        data_file (str): name of the data file to send in HTTP body.
        maxprice (int): allowed maximum price (in satoshis) of the resource.

    Raises:
        click.ClickException: if some set of parameters or behavior cause the
            purchase to not complete successfully for any reason.
    """
    # Find the correct payment method
    if payment_method == 'offchain':
        requests = bitrequests.BitTransferRequests(machine_auth,
                                                   config.username)
    elif payment_method == 'onchain':
        requests = bitrequests.OnChainRequests(machine_auth.wallet)
    elif payment_method == 'channel':
        requests = bitrequests.ChannelRequests(machine_auth.wallet)
    else:
        raise click.ClickException(
            uxstring.UxString.buy_bad_payment_method.format(payment_method))

    # Request user consent if they're creating a channel for the first time
    if payment_method == 'channel' and not requests._channelclient.list():
        confirmed = click.confirm(uxstring.UxString.buy_channel_warning.format(
            requests.DEFAULT_DEPOSIT_AMOUNT, statemachine.
            PaymentChannelStateMachine.PAYMENT_TX_MIN_OUTPUT_AMOUNT),
                                  default=True)
        if not confirmed:
            raise click.ClickException(uxstring.UxString.buy_channel_aborted)

    resource = parse_resource(resource)

    # Retrieve 402-related header information, print it, then exit
    if info_only:
        response = requests.get_402_info(resource)
        return logger.info('\n'.join(
            ['{}: {}'.format(key, val) for key, val in response.items()]))

    # Collect HTTP header parameters into a single dictionary
    headers = {
        key.strip(): value.strip()
        for key, value in (h.split(':') for h in header)
    }

    # Handle data if applicable
    if data or data_file:
        method = 'POST' if method == 'GET' else method
        data, headers['Content-Type'] = _parse_post_data(data)

    # Make the paid request for the resource
    try:
        response = requests.request(method.lower(),
                                    resource,
                                    max_price=maxprice,
                                    data=data or data_file,
                                    headers=headers)
    except bitrequests.ResourcePriceGreaterThanMaxPriceError as e:
        raise click.ClickException(
            uxstring.UxString.Error.resource_price_greater_than_max_price.
            format(e))
    except wallet_exceptions.DustLimitError as e:
        raise click.ClickException(e)
    except ValueError as e:
        if bitcoin_computer.has_mining_chip():
            raise click.ClickException(
                uxstring.UxString.Error.insufficient_funds_mine_more)
        else:
            raise click.ClickException(
                uxstring.UxString.Error.insufficient_funds_earn_more)
    except Exception as e:
        raise click.ClickException(e)

    # Write response text to stdout or a filename if provided
    if not output_file:
        try:
            json_resp = response.json()
        except ValueError:
            logger.info(response.content, nl=False)
        else:
            if isinstance(json_resp, dict):
                ordered = OrderedDict(sorted(json_resp.items()))
                logger.info(json.dumps(ordered, indent=4), nl=False)
            else:
                logger.info(json.dumps(json_resp, indent=4), nl=False)
    else:
        with open(output_file, 'wb') as f:
            logger.info(response.content, file=f, nl=False)

    logger.info('', err=True)  # newline for pretty-printing errors to stdout

    # Exit successfully if no amount was paid for the resource (standard HTTP request)
    if not hasattr(response, 'amount_paid'):
        return

    # Fetch and write out diagnostic payment information for balances
    if payment_method == 'offchain':
        twentyone_balance = client.get_earnings()["total_earnings"]
        logger.info(uxstring.UxString.buy_balances.format(
            response.amount_paid, '21.co', twentyone_balance),
                    err=True)
    elif payment_method == 'onchain':
        onchain_balance = min(requests.wallet.confirmed_balance(),
                              requests.wallet.unconfirmed_balance())
        logger.info(uxstring.UxString.buy_balances.format(
            response.amount_paid, 'blockchain', onchain_balance),
                    err=True)
    elif payment_method == 'channel':
        channel_client = requests._channelclient
        channel_client.sync()
        channels_balance = sum(
            s.balance for s in (channel_client.status(url)
                                for url in channel_client.list())
            if s.state == channels.PaymentChannelState.READY)
        logger.info(uxstring.UxString.buy_balances.format(
            response.amount_paid, 'payment channels', channels_balance),
                    err=True)