Пример #1
0
def estimate_fee(wallet_alias):
    """Returns a json-representation of a psbt which did not get persisted. Kind of a draft-run."""
    wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
    # update balances in the wallet
    wallet.update_balance()
    # update utxo list for coin selection
    wallet.check_utxo()
    if request.form.get("estimate_fee") != "true":
        # Very critical as this form-value will prevent persisting the PSBT
        return jsonify(
            success=False,
            error=
            "Your Form did not specify estimate_fee = false. This call is not allowed",
        )
    psbt_creator = PsbtCreator(
        app.specter,
        wallet,
        request.form.get("ui_option", "ui"),
        request_form=request.form,
        recipients_txt=request.form["recipients"],
        recipients_amount_unit=request.form.get("amount_unit_text"),
    )
    try:
        # Won't get persisted
        psbt = psbt_creator.create_psbt(wallet)
        return jsonify(success=True, psbt=psbt)
    except SpecterError as se:
        app.logger.error(se)
        return jsonify(success=False, error=str(se))
Пример #2
0
def test_PsbtCreator_json(caplog):
    caplog.set_level(logging.DEBUG)
    specter_mock = MagicMock()
    # non liquid and default asset btc (for unit-calculation)
    specter_mock.is_liquid = False
    specter_mock.default_asset = "btc"

    wallet_mock = MagicMock()
    specter_mock.chain = "regtest"
    # Let's mock the request.form which behaves like a dict but also needs getlist()
    request_json = """
        {
            "recipients" : [
                { 
                    "address": "BCRT1qgc6h85z43g3ss2dl5zdrzrp3ef6av4neqcqhh8",
                    "amount": 0.1,
                    "unit": "btc",
                    "label": "someLabel"
                },
                {
                    "address": "bcrt1q3kfetuxpxvujasww6xas94nawklvpz0e52uw8a",
                    "amount": 111211,
                    "unit": "sat",
                    "label": "someOtherLabel"
                }
            ],
            "rbf_tx_id": "",
            "subtract_from": "1",
            "fee_rate": "64",
            "rbf": true
        }
    """

    psbt_creator: PsbtCreator = PsbtCreator(specter_mock,
                                            wallet_mock,
                                            "json",
                                            request_json=request_json)

    assert psbt_creator.addresses == [
        "bcrt1qgc6h85z43g3ss2dl5zdrzrp3ef6av4neqcqhh8",
        "bcrt1q3kfetuxpxvujasww6xas94nawklvpz0e52uw8a",
    ]
    assert psbt_creator.amounts == [0.1, 0.00111211]
    assert psbt_creator.labels == ["someLabel", "someOtherLabel"]
    assert psbt_creator.amount_units == ["btc", "sat"]
    assert psbt_creator.kwargs == {
        "fee_rate": 64.0,
        "rbf": True,
        "rbf_edit_mode": False,
        "readonly": False,
        "selected_coins": [],
        "subtract": False,
        "subtract_from": 0,
    }

    psbt_creator.create_psbt(wallet_mock)
Пример #3
0
def test_PsbtCreator(caplog):
    caplog.set_level(logging.DEBUG)
    specter_mock = MagicMock()
    # non liquid and default asset btc (for unit-calculation)
    specter_mock.is_liquid = False
    specter_mock.default_asset = "btc"

    wallet_mock = MagicMock()
    specter_mock.chain = "regtest"
    # Let's mock the request.form which behaves like a dict but also needs getlist()
    request_form_data = {
        "rbf_tx_id": "",
        "address_0": "BCRT1qgc6h85z43g3ss2dl5zdrzrp3ef6av4neqcqhh8",  # will need normalisation
        "label_0": "someLabel",
        "amount_0": "0.1",
        "btc_amount_0": "0.1",
        "amount_unit_0": "btc",
        "address_1": "bcrt1q3kfetuxpxvujasww6xas94nawklvpz0e52uw8a",
        "label_1": "someOtherLabel",
        "amount_1": "111211",
        "btc_amount_1": "0.00111211",
        "amount_unit_1": "sat",
        "amount_unit_text": "btc",
        "subtract_from": "1",
        "fee_options": "dynamic",
        "fee_rate": "",
        "fee_rate_dynamic": "64",
        "rbf": "on",
        "action": "createpsbt",
    }

    psbt_creator: PsbtCreator = PsbtCreator(
        specter_mock, wallet_mock, "ui", request_form=request_form_data
    )

    assert psbt_creator.addresses == [
        "bcrt1qgc6h85z43g3ss2dl5zdrzrp3ef6av4neqcqhh8",
        "bcrt1q3kfetuxpxvujasww6xas94nawklvpz0e52uw8a",
    ]
    assert psbt_creator.amounts == [0.1, 0.00111211]
    assert psbt_creator.labels == ["someLabel", "someOtherLabel"]
    assert psbt_creator.amount_units == ["btc", "sat"]
    assert psbt_creator.kwargs == {
        "fee_rate": 64.0,
        "rbf": True,
        "rbf_edit_mode": False,
        "readonly": False,
        "selected_coins": None,
        "subtract": False,
        "subtract_from": 0,
    }

    psbt_creator.create_psbt(wallet_mock)
Пример #4
0
def send_new(wallet_alias):
    wallet = app.specter.wallet_manager.get_by_alias(wallet_alias)
    # update balances in the wallet
    wallet.update_balance()
    # update utxo list for coin selection
    wallet.check_utxo()
    psbt = None
    addresses = [""]
    labels = [""]
    amounts = [0]
    amount_units = ["btc"]
    err = None
    ui_option = "ui"
    recipients_txt = ""
    fillform = False
    subtract = False
    subtract_from = 1
    fee_options = "dynamic"
    rbf = not app.specter.is_liquid
    rbf_utxo = []
    rbf_tx_id = ""
    selected_coins = request.form.getlist("coinselect")

    if request.method == "POST":
        action = request.form.get("action")
        rbf_tx_id = request.form.get("rbf_tx_id", "")
        if action == "createpsbt":
            psbt_creator = PsbtCreator(
                app.specter,
                wallet,
                request.form.get("ui_option", "ui"),
                request_form=request.form,
                recipients_txt=request.form["recipients"],
                recipients_amount_unit=request.form.get("amount_unit_text"),
            )
            psbt = psbt_creator.create_psbt(wallet)
            return render_template(
                "wallet/send/sign/wallet_send_sign_psbt.jinja",
                psbt=psbt,
                labels=labels,
                wallet_alias=wallet_alias,
                wallet=wallet,
                specter=app.specter,
                rand=rand,
            )

        elif action in ["rbf", "rbf_cancel"]:
            try:
                rbf_tx_id = request.form["rbf_tx_id"]
                rbf_fee_rate = float(request.form["rbf_fee_rate"])

                if action == "rbf":
                    psbt = wallet.bumpfee(rbf_tx_id, rbf_fee_rate)
                elif action == "rbf_cancel":
                    psbt = wallet.canceltx(rbf_tx_id, rbf_fee_rate)
                else:
                    raise SpecterError("Invalid action")

                if psbt["fee_rate"] - rbf_fee_rate > wallet.MIN_FEE_RATE / 10:
                    flash(
                        _("We had to increase the fee rate from {} to {} sat/vbyte"
                          ).format(rbf_fee_rate, psbt["fee_rate"]))
                return render_template(
                    "wallet/send/sign/wallet_send_sign_psbt.jinja",
                    psbt=psbt,
                    labels=[],
                    wallet_alias=wallet_alias,
                    wallet=wallet,
                    specter=app.specter,
                    rand=rand,
                )
            except Exception as e:
                handle_exception(e)
                flash(_("Failed to perform RBF. Error: {}").format(e), "error")
                return redirect(
                    url_for("wallets_endpoint.history",
                            wallet_alias=wallet_alias))
        elif action == "rbf_edit":
            try:
                decoded_tx = wallet.decode_tx(rbf_tx_id)
                addresses = decoded_tx["addresses"]
                amounts = decoded_tx["amounts"]
                amount_units = decoded_tx.get("assets",
                                              ["btc"] * len(addresses))
                # get_label returns a label or address if no label is set
                labels = [wallet.getlabel(addr) for addr in addresses]
                # set empty label to addresses that don't have labels
                labels = [
                    label if label != addr else ""
                    for addr, label in zip(addresses, labels)
                ]

                selected_coins = [
                    f"{utxo['txid']}, {utxo['vout']}"
                    for utxo in decoded_tx["used_utxo"]
                ]
                fee_rate = float(request.form["rbf_fee_rate"])
                fee_options = "manual"
                rbf = True
                fillform = True
            except Exception as e:
                handle_exception(e)
                flash(_("Failed to perform RBF. Error: {}").format(e), "error")
        elif action == "signhotwallet":
            passphrase = request.form["passphrase"]
            psbt = json.loads(request.form["psbt"])
            current_psbt = wallet.pending_psbts[psbt["tx"]["txid"]]
            b64psbt = str(current_psbt)
            device = request.form["device"]
            if "devices_signed" not in psbt or device not in psbt[
                    "devices_signed"]:
                try:
                    # get device and sign with it
                    signed_psbt = app.specter.device_manager.get_by_alias(
                        device).sign_psbt(b64psbt, wallet, passphrase)
                    raw = None
                    if signed_psbt["complete"]:
                        raw = wallet.rpc.finalizepsbt(b64psbt)
                    current_psbt.update(signed_psbt["psbt"], raw)
                    signed_psbt = signed_psbt["psbt"]
                    psbt = current_psbt.to_dict()
                except Exception as e:
                    handle_exception(e)
                    signed_psbt = None
                    flash(_("Failed to sign PSBT: {}").format(e), "error")
            else:
                signed_psbt = None
                flash(_("Device already signed the PSBT"), "error")
            return render_template(
                "wallet/send/sign/wallet_send_sign_psbt.jinja",
                signed_psbt=signed_psbt,
                psbt=psbt,
                labels=labels,
                wallet_alias=wallet_alias,
                wallet=wallet,
                specter=app.specter,
                rand=rand,
            )
        elif action == "fillform":
            # TODO: Not yet used. Remove if the use case doesn't happen.
            # can be used to recommend a transaction from a service (goind to an exchange or so)
            addresses = request.form.getlist("addresses[]")
            labels = request.form.getlist("labels[]")
            amounts = request.form.getlist("amounts[]")
            fillform = True

    if rbf_tx_id:
        try:
            rbf_utxo = wallet.get_rbf_utxo(rbf_tx_id)
        except Exception as e:
            handle_exception(e)
            flash(_("Failed to get RBF coins. Error: {}").format(e), "error")

    show_advanced_settings = (ui_option != "ui" or subtract
                              or fee_options != "dynamic" or not rbf
                              or selected_coins)
    wallet_utxo = wallet.utxo
    if app.specter.is_liquid:
        for tx in wallet_utxo + rbf_utxo:
            if "asset" in tx:
                tx["assetlabel"] = app.specter.asset_label(tx.get("asset"))
    return render_template(
        "wallet/send/new/wallet_send.jinja",
        psbt=psbt,
        ui_option=ui_option,
        recipients_txt=recipients_txt,
        addresses=addresses,
        labels=labels,
        amounts=amounts,
        fillform=fillform,
        recipients=list(zip(addresses, amounts, amount_units, labels)),
        subtract=subtract,
        subtract_from=subtract_from,
        fee_options=fee_options,
        rbf=rbf,
        selected_coins=selected_coins,
        show_advanced_settings=show_advanced_settings,
        rbf_utxo=rbf_utxo,
        rbf_tx_id=rbf_tx_id,
        wallet_utxo=wallet_utxo,
        wallet_alias=wallet_alias,
        wallet=wallet,
        specter=app.specter,
        rand=rand,
        error=err,
    )