mirror of
https://github.com/bitcoin/bitcoin.git
synced 2025-01-10 20:03:34 -03:00
Merge bitcoin-core/gui#4: UI external signer support (e.g. hardware wallet)
1c4b456e1a
gui: send using external signer (Sjors Provoost)24815c6309
gui: wallet creation detects external signer (Sjors Provoost)3f845ea299
node: add externalSigners to interface (Sjors Provoost)62ac119f91
gui: display address on external signer (Sjors Provoost)450cb40a34
wallet: add displayAddress to interface (Sjors Provoost)eef8d64529
gui: create wallet with external signer (Sjors Provoost)6cdbc83e93
gui: add external signer path to options dialog (Sjors Provoost) Pull request description: Big picture overview in [this gist](https://gist.github.com/Sjors/29d06728c685e6182828c1ce9b74483d). This PR adds GUI support for external signers, based on the since merged bitcoin/bitcoin#16546 (RPC). The UX isn't amazing - especially the blocking calls - but it works. First we adds a GUI setting for the signer script (e.g. path to HWI): <img width="625" alt="Schermafbeelding 2019-08-05 om 19 32 59" src="https://user-images.githubusercontent.com/10217/62483415-e1ff1680-b7b7-11e9-97ca-8d2ce54ca1cb.png"> Then we add an external signer checkbox to the wallet creation dialog: <img width="374" alt="Schermafbeelding 2019-11-07 om 19 17 23" src="https://user-images.githubusercontent.com/10217/68416387-b57ee000-0194-11ea-9730-127d60273008.png"> It's checked by default if HWI detects a device. It also grabs the name. It then creates a fresh wallet and imports the keys. You can verify an address on the device (blocking...): <img width="673" alt="Schermafbeelding 2019-08-05 om 19 29 22" src="https://user-images.githubusercontent.com/10217/62483560-43bf8080-b7b8-11e9-9902-8a036116dc4b.png"> Sending, including coin selection, Just Works(tm) as long the device is present. ~External signer support is enabled by default when the GUI is configured and Boost::Process is present.~ External signer support remains disabled by default, see https://github.com/bitcoin/bitcoin/pull/21935. ACKs for top commit: achow101: Code Review ACK1c4b456e1a
hebasto: ACK1c4b456e1a
, tested on Linux Mint 20.1 (Qt 5.12.8) with HWW `2.0.2-rc.1`. promag: Tested ACK1c4b456e1a
but rebased withe033ca1379
, with HWI 2.0.2, with Nano S and Nano X. meshcollider: re-code-review ACK1c4b456e1a
Tree-SHA512: 3503113c5c69d40adb6ce364d8e7cae23ce82d032a00474ba9aeb6202eb70f496ef4a6bf2e623e5171e524ad31ade7941a4e0e89539c64518aaec74f4562d86b
This commit is contained in:
commit
68a89d7a46
19 changed files with 284 additions and 11 deletions
|
@ -6,6 +6,7 @@
|
|||
#define BITCOIN_INTERFACES_NODE_H
|
||||
|
||||
#include <amount.h> // For CAmount
|
||||
#include <external_signer.h>
|
||||
#include <net.h> // For NodeId
|
||||
#include <net_types.h> // For banmap_t
|
||||
#include <netaddress.h> // For Network
|
||||
|
@ -110,6 +111,11 @@ public:
|
|||
//! Disconnect node by id.
|
||||
virtual bool disconnectById(NodeId id) = 0;
|
||||
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
//! List external signers
|
||||
virtual std::vector<ExternalSigner> externalSigners() = 0;
|
||||
#endif
|
||||
|
||||
//! Get total bytes recv.
|
||||
virtual int64_t getTotalBytesRecv() = 0;
|
||||
|
||||
|
|
|
@ -118,6 +118,9 @@ public:
|
|||
//! Save or remove receive request.
|
||||
virtual bool setAddressReceiveRequest(const CTxDestination& dest, const std::string& id, const std::string& value) = 0;
|
||||
|
||||
//! Display address on external signer
|
||||
virtual bool displayAddress(const CTxDestination& dest) = 0;
|
||||
|
||||
//! Lock coin.
|
||||
virtual void lockCoin(const COutPoint& output) = 0;
|
||||
|
||||
|
@ -252,6 +255,9 @@ public:
|
|||
// Return whether private keys enabled.
|
||||
virtual bool privateKeysDisabled() = 0;
|
||||
|
||||
// Return whether wallet uses an external signer.
|
||||
virtual bool hasExternalSigner() = 0;
|
||||
|
||||
// Get default address type.
|
||||
virtual OutputType getDefaultAddressType() = 0;
|
||||
|
||||
|
|
|
@ -170,6 +170,16 @@ public:
|
|||
}
|
||||
return false;
|
||||
}
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
std::vector<ExternalSigner> externalSigners() override
|
||||
{
|
||||
std::vector<ExternalSigner> signers = {};
|
||||
const std::string command = gArgs.GetArg("-signer", "");
|
||||
if (command == "") return signers;
|
||||
ExternalSigner::Enumerate(command, signers, Params().NetworkIDString());
|
||||
return signers;
|
||||
}
|
||||
#endif
|
||||
int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; }
|
||||
int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; }
|
||||
size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; }
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#include <config/bitcoin-config.h>
|
||||
#endif
|
||||
|
||||
#include <external_signer.h>
|
||||
#include <qt/createwalletdialog.h>
|
||||
#include <qt/forms/ui_createwalletdialog.h>
|
||||
|
||||
|
@ -27,14 +28,39 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
|
|||
});
|
||||
|
||||
connect(ui->encrypt_wallet_checkbox, &QCheckBox::toggled, [this](bool checked) {
|
||||
// Disable the disable_privkeys_checkbox when isEncryptWalletChecked is
|
||||
// Disable the disable_privkeys_checkbox and external_signer_checkbox when isEncryptWalletChecked is
|
||||
// set to true, enable it when isEncryptWalletChecked is false.
|
||||
ui->disable_privkeys_checkbox->setEnabled(!checked);
|
||||
ui->external_signer_checkbox->setEnabled(!checked);
|
||||
|
||||
// When the disable_privkeys_checkbox is disabled, uncheck it.
|
||||
if (!ui->disable_privkeys_checkbox->isEnabled()) {
|
||||
ui->disable_privkeys_checkbox->setChecked(false);
|
||||
}
|
||||
|
||||
// When the external_signer_checkbox box is disabled, uncheck it.
|
||||
if (!ui->external_signer_checkbox->isEnabled()) {
|
||||
ui->external_signer_checkbox->setChecked(false);
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
connect(ui->external_signer_checkbox, &QCheckBox::toggled, [this](bool checked) {
|
||||
ui->encrypt_wallet_checkbox->setEnabled(!checked);
|
||||
ui->blank_wallet_checkbox->setEnabled(!checked);
|
||||
ui->disable_privkeys_checkbox->setEnabled(!checked);
|
||||
ui->descriptor_checkbox->setEnabled(!checked);
|
||||
|
||||
// The external signer checkbox is only enabled when a device is detected.
|
||||
// In that case it is checked by default. Toggling it restores the other
|
||||
// options to their default.
|
||||
ui->descriptor_checkbox->setChecked(checked);
|
||||
ui->encrypt_wallet_checkbox->setChecked(false);
|
||||
ui->disable_privkeys_checkbox->setChecked(checked);
|
||||
// The blank check box is ambiguous. This flag is always true for a
|
||||
// watch-only wallet, even though we immedidately fetch keys from the
|
||||
// external signer.
|
||||
ui->blank_wallet_checkbox->setChecked(checked);
|
||||
});
|
||||
|
||||
connect(ui->disable_privkeys_checkbox, &QCheckBox::toggled, [this](bool checked) {
|
||||
|
@ -63,11 +89,22 @@ CreateWalletDialog::CreateWalletDialog(QWidget* parent) :
|
|||
ui->descriptor_checkbox->setToolTip(tr("Compiled without sqlite support (required for descriptor wallets)"));
|
||||
ui->descriptor_checkbox->setEnabled(false);
|
||||
ui->descriptor_checkbox->setChecked(false);
|
||||
ui->external_signer_checkbox->setEnabled(false);
|
||||
ui->external_signer_checkbox->setChecked(false);
|
||||
#endif
|
||||
|
||||
#ifndef USE_BDB
|
||||
ui->descriptor_checkbox->setEnabled(false);
|
||||
ui->descriptor_checkbox->setChecked(true);
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_EXTERNAL_SIGNER
|
||||
//: "External signing" means using devices such as hardware wallets.
|
||||
ui->external_signer_checkbox->setToolTip(tr("Compiled without external signing support (required for external signing)"));
|
||||
ui->external_signer_checkbox->setEnabled(false);
|
||||
ui->external_signer_checkbox->setChecked(false);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
CreateWalletDialog::~CreateWalletDialog()
|
||||
|
@ -75,6 +112,28 @@ CreateWalletDialog::~CreateWalletDialog()
|
|||
delete ui;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
void CreateWalletDialog::setSigners(std::vector<ExternalSigner>& signers)
|
||||
{
|
||||
if (!signers.empty()) {
|
||||
ui->external_signer_checkbox->setEnabled(true);
|
||||
ui->external_signer_checkbox->setChecked(true);
|
||||
ui->encrypt_wallet_checkbox->setEnabled(false);
|
||||
ui->encrypt_wallet_checkbox->setChecked(false);
|
||||
// The order matters, because connect() is called when toggling a checkbox:
|
||||
ui->blank_wallet_checkbox->setEnabled(false);
|
||||
ui->blank_wallet_checkbox->setChecked(false);
|
||||
ui->disable_privkeys_checkbox->setEnabled(false);
|
||||
ui->disable_privkeys_checkbox->setChecked(true);
|
||||
const std::string label = signers[0].m_name;
|
||||
ui->wallet_name_line_edit->setText(QString::fromStdString(label));
|
||||
ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
|
||||
} else {
|
||||
ui->external_signer_checkbox->setEnabled(false);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
QString CreateWalletDialog::walletName() const
|
||||
{
|
||||
return ui->wallet_name_line_edit->text();
|
||||
|
@ -99,3 +158,8 @@ bool CreateWalletDialog::isDescriptorWalletChecked() const
|
|||
{
|
||||
return ui->descriptor_checkbox->isChecked();
|
||||
}
|
||||
|
||||
bool CreateWalletDialog::isExternalSignerChecked() const
|
||||
{
|
||||
return ui->external_signer_checkbox->isChecked();
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@
|
|||
|
||||
class WalletModel;
|
||||
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
class ExternalSigner;
|
||||
#endif
|
||||
|
||||
namespace Ui {
|
||||
class CreateWalletDialog;
|
||||
}
|
||||
|
@ -23,11 +27,16 @@ public:
|
|||
explicit CreateWalletDialog(QWidget* parent);
|
||||
virtual ~CreateWalletDialog();
|
||||
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
void setSigners(std::vector<ExternalSigner>& signers);
|
||||
#endif
|
||||
|
||||
QString walletName() const;
|
||||
bool isEncryptWalletChecked() const;
|
||||
bool isDisablePrivateKeysChecked() const;
|
||||
bool isMakeBlankWalletChecked() const;
|
||||
bool isDescriptorWalletChecked() const;
|
||||
bool isExternalSignerChecked() const;
|
||||
|
||||
private:
|
||||
Ui::CreateWalletDialog *ui;
|
||||
|
|
|
@ -109,6 +109,16 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="external_signer_checkbox">
|
||||
<property name="toolTip">
|
||||
<string>Use an external signing device such as a hardware wallet. Configure the external signer script in wallet preferences first.</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>External signer</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -143,6 +153,7 @@
|
|||
<tabstop>disable_privkeys_checkbox</tabstop>
|
||||
<tabstop>blank_wallet_checkbox</tabstop>
|
||||
<tabstop>descriptor_checkbox</tabstop>
|
||||
<tabstop>external_signer_checkbox</tabstop>
|
||||
</tabstops>
|
||||
<resources/>
|
||||
<connections>
|
||||
|
|
|
@ -229,6 +229,36 @@
|
|||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="groupBoxHww">
|
||||
<property name="title">
|
||||
<string>External Signer (e.g. hardware wallet)</string>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayoutHww">
|
||||
<item>
|
||||
<layout class="QHBoxLayout" name="horizontalLayoutHww">
|
||||
<item>
|
||||
<widget class="QLabel" name="externalSignerPathLabel">
|
||||
<property name="text">
|
||||
<string>&External signer script path</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>externalSignerPath</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLineEdit" name="externalSignerPath">
|
||||
<property name="toolTip">
|
||||
<string>Full path to a Bitcoin Core compatible script (e.g. C:\Downloads\hwi.exe or /Users/you/Downloads/hwi.py). Beware: malware can steal your coins!</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer_Wallet">
|
||||
<property name="orientation">
|
||||
|
|
|
@ -254,6 +254,19 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnVerify">
|
||||
<property name="text">
|
||||
<string>&Verify</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>Verify this address on e.g. a hardware wallet screen</string>
|
||||
</property>
|
||||
<property name="autoDefault">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="btnSaveAs">
|
||||
<property name="text">
|
||||
|
|
|
@ -199,6 +199,7 @@ void OptionsDialog::setModel(OptionsModel *_model)
|
|||
connect(ui->prune, &QCheckBox::clicked, this, &OptionsDialog::togglePruneWarning);
|
||||
connect(ui->pruneSize, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
|
||||
connect(ui->databaseCache, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
|
||||
connect(ui->externalSignerPath, &QLineEdit::textChanged, [this]{ showRestartWarning(); });
|
||||
connect(ui->threadsScriptVerif, qOverload<int>(&QSpinBox::valueChanged), this, &OptionsDialog::showRestartWarning);
|
||||
/* Wallet */
|
||||
connect(ui->spendZeroConfChange, &QCheckBox::clicked, this, &OptionsDialog::showRestartWarning);
|
||||
|
@ -233,6 +234,7 @@ void OptionsDialog::setMapper()
|
|||
/* Wallet */
|
||||
mapper->addMapping(ui->spendZeroConfChange, OptionsModel::SpendZeroConfChange);
|
||||
mapper->addMapping(ui->coinControlFeatures, OptionsModel::CoinControlFeatures);
|
||||
mapper->addMapping(ui->externalSignerPath, OptionsModel::ExternalSignerPath);
|
||||
|
||||
/* Network */
|
||||
mapper->addMapping(ui->mapPortUpnp, OptionsModel::MapPortUPnP);
|
||||
|
|
|
@ -117,6 +117,13 @@ void OptionsModel::Init(bool resetSettings)
|
|||
settings.setValue("bSpendZeroConfChange", true);
|
||||
if (!gArgs.SoftSetBoolArg("-spendzeroconfchange", settings.value("bSpendZeroConfChange").toBool()))
|
||||
addOverriddenOption("-spendzeroconfchange");
|
||||
|
||||
if (!settings.contains("external_signer_path"))
|
||||
settings.setValue("external_signer_path", "");
|
||||
|
||||
if (!gArgs.SoftSetArg("-signer", settings.value("external_signer_path").toString().toStdString())) {
|
||||
addOverriddenOption("-signer");
|
||||
}
|
||||
#endif
|
||||
|
||||
// Network
|
||||
|
@ -326,6 +333,8 @@ QVariant OptionsModel::data(const QModelIndex & index, int role) const
|
|||
#ifdef ENABLE_WALLET
|
||||
case SpendZeroConfChange:
|
||||
return settings.value("bSpendZeroConfChange");
|
||||
case ExternalSignerPath:
|
||||
return settings.value("external_signer_path");
|
||||
#endif
|
||||
case DisplayUnit:
|
||||
return nDisplayUnit;
|
||||
|
@ -445,6 +454,12 @@ bool OptionsModel::setData(const QModelIndex & index, const QVariant & value, in
|
|||
setRestartRequired(true);
|
||||
}
|
||||
break;
|
||||
case ExternalSignerPath:
|
||||
if (settings.value("external_signer_path") != value.toString()) {
|
||||
settings.setValue("external_signer_path", value.toString());
|
||||
setRestartRequired(true);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case DisplayUnit:
|
||||
setDisplayUnit(value);
|
||||
|
|
|
@ -65,6 +65,7 @@ public:
|
|||
Prune, // bool
|
||||
PruneSize, // int
|
||||
DatabaseCache, // int
|
||||
ExternalSignerPath, // QString
|
||||
SpendZeroConfChange, // bool
|
||||
Listen, // bool
|
||||
OptionIDRowCount,
|
||||
|
|
|
@ -89,6 +89,12 @@ void ReceiveRequestDialog::setInfo(const SendCoinsRecipient &_info)
|
|||
ui->wallet_tag->hide();
|
||||
ui->wallet_content->hide();
|
||||
}
|
||||
|
||||
ui->btnVerify->setVisible(this->model->wallet().hasExternalSigner());
|
||||
|
||||
connect(ui->btnVerify, &QPushButton::clicked, [this] {
|
||||
model->displayAddress(info.address.toStdString());
|
||||
});
|
||||
}
|
||||
|
||||
void ReceiveRequestDialog::updateDisplayUnit()
|
||||
|
|
|
@ -199,7 +199,16 @@ void SendCoinsDialog::setModel(WalletModel *_model)
|
|||
// set default rbf checkbox state
|
||||
ui->optInRBF->setCheckState(Qt::Checked);
|
||||
|
||||
if (model->wallet().privateKeysDisabled()) {
|
||||
if (model->wallet().hasExternalSigner()) {
|
||||
ui->sendButton->setText(tr("Sign on device"));
|
||||
if (gArgs.GetArg("-signer", "") != "") {
|
||||
ui->sendButton->setEnabled(true);
|
||||
ui->sendButton->setToolTip(tr("Connect your hardware wallet first."));
|
||||
} else {
|
||||
ui->sendButton->setEnabled(false);
|
||||
ui->sendButton->setToolTip(tr("Set external signer script path in Options -> Wallet"));
|
||||
}
|
||||
} else if (model->wallet().privateKeysDisabled()) {
|
||||
ui->sendButton->setText(tr("Cr&eate Unsigned"));
|
||||
ui->sendButton->setToolTip(tr("Creates a Partially Signed Bitcoin Transaction (PSBT) for use with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
|
||||
}
|
||||
|
@ -313,14 +322,14 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
|
|||
formatted.append(recipientElement);
|
||||
}
|
||||
|
||||
if (model->wallet().privateKeysDisabled()) {
|
||||
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
|
||||
question_string.append(tr("Do you want to draft this transaction?"));
|
||||
} else {
|
||||
question_string.append(tr("Are you sure you want to send?"));
|
||||
}
|
||||
|
||||
question_string.append("<br /><span style='font-size:10pt;'>");
|
||||
if (model->wallet().privateKeysDisabled()) {
|
||||
if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {
|
||||
question_string.append(tr("Please, review your transaction proposal. This will produce a Partially Signed Bitcoin Transaction (PSBT) which you can save or copy and then sign with e.g. an offline %1 wallet, or a PSBT-compatible hardware wallet.").arg(PACKAGE_NAME));
|
||||
} else {
|
||||
question_string.append(tr("Please, review your transaction."));
|
||||
|
@ -386,8 +395,8 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
|
|||
if (!PrepareSendText(question_string, informative_text, detailed_text)) return;
|
||||
assert(m_current_transaction);
|
||||
|
||||
const QString confirmation = model->wallet().privateKeysDisabled() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
|
||||
const QString confirmButtonText = model->wallet().privateKeysDisabled() ? tr("Create Unsigned") : tr("Send");
|
||||
const QString confirmation = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Confirm transaction proposal") : tr("Confirm send coins");
|
||||
const QString confirmButtonText = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner() ? tr("Create Unsigned") : tr("Sign and send");
|
||||
SendConfirmationDialog confirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, confirmButtonText, this);
|
||||
confirmationDialog.exec();
|
||||
QMessageBox::StandardButton retval = static_cast<QMessageBox::StandardButton>(confirmationDialog.result());
|
||||
|
@ -403,9 +412,58 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
|
|||
CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};
|
||||
PartiallySignedTransaction psbtx(mtx);
|
||||
bool complete = false;
|
||||
const TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
|
||||
// Always fill without signing first. This prevents an external signer
|
||||
// from being called prematurely and is not expensive.
|
||||
TransactionError err = model->wallet().fillPSBT(SIGHASH_ALL, false /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
|
||||
assert(!complete);
|
||||
assert(err == TransactionError::OK);
|
||||
if (model->wallet().hasExternalSigner()) {
|
||||
try {
|
||||
err = model->wallet().fillPSBT(SIGHASH_ALL, true /* sign */, true /* bip32derivs */, psbtx, complete, nullptr);
|
||||
} catch (const std::runtime_error& e) {
|
||||
QMessageBox::critical(nullptr, tr("Sign failed"), e.what());
|
||||
send_failure = true;
|
||||
return;
|
||||
}
|
||||
if (err == TransactionError::EXTERNAL_SIGNER_NOT_FOUND) {
|
||||
QMessageBox::critical(nullptr, tr("External signer not found"), "External signer not found");
|
||||
send_failure = true;
|
||||
return;
|
||||
}
|
||||
if (err == TransactionError::EXTERNAL_SIGNER_FAILED) {
|
||||
QMessageBox::critical(nullptr, tr("External signer failure"), "External signer failure");
|
||||
send_failure = true;
|
||||
return;
|
||||
}
|
||||
if (err != TransactionError::OK) {
|
||||
tfm::format(std::cerr, "Failed to sign PSBT");
|
||||
processSendCoinsReturn(WalletModel::TransactionCreationFailed);
|
||||
send_failure = true;
|
||||
return;
|
||||
}
|
||||
// fillPSBT does not always properly finalize
|
||||
complete = FinalizeAndExtractPSBT(psbtx, mtx);
|
||||
}
|
||||
|
||||
// Broadcast transaction if complete (even with an external signer this
|
||||
// is not always the case, e.g. in a multisig wallet).
|
||||
if (complete) {
|
||||
const CTransactionRef tx = MakeTransactionRef(mtx);
|
||||
m_current_transaction->setWtx(tx);
|
||||
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
|
||||
// process sendStatus and on error generate message shown to user
|
||||
processSendCoinsReturn(sendStatus);
|
||||
|
||||
if (sendStatus.status == WalletModel::OK) {
|
||||
Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
|
||||
} else {
|
||||
send_failure = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Copy PSBT to clipboard and offer to save
|
||||
assert(!complete);
|
||||
// Serialize the PSBT
|
||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION);
|
||||
ssTx << psbtx;
|
||||
|
@ -447,7 +505,7 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
|
|||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
} // msgBox.exec()
|
||||
} else {
|
||||
// now send the prepared transaction
|
||||
WalletModel::SendCoinsReturn sendStatus = model->sendCoins(*m_current_transaction);
|
||||
|
@ -614,7 +672,9 @@ void SendCoinsDialog::setBalance(const interfaces::WalletBalances& balances)
|
|||
if(model && model->getOptionsModel())
|
||||
{
|
||||
CAmount balance = balances.balance;
|
||||
if (model->wallet().privateKeysDisabled()) {
|
||||
if (model->wallet().hasExternalSigner()) {
|
||||
ui->labelBalanceName->setText(tr("External balance:"));
|
||||
} else if (model->wallet().privateKeysDisabled()) {
|
||||
balance = balances.watch_only_balance;
|
||||
ui->labelBalanceName->setText(tr("Watch-only balance:"));
|
||||
}
|
||||
|
@ -698,7 +758,7 @@ void SendCoinsDialog::on_buttonMinimizeFee_clicked()
|
|||
void SendCoinsDialog::useAvailableBalance(SendCoinsEntry* entry)
|
||||
{
|
||||
// Include watch-only for wallets without private key
|
||||
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
|
||||
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
|
||||
|
||||
// Calculate available amount to send.
|
||||
CAmount amount = model->wallet().getAvailableBalance(*m_coin_control);
|
||||
|
@ -753,7 +813,7 @@ void SendCoinsDialog::updateCoinControlState()
|
|||
m_coin_control->m_confirm_target = getConfTargetForIndex(ui->confTargetSelector->currentIndex());
|
||||
m_coin_control->m_signal_bip125_rbf = ui->optInRBF->isChecked();
|
||||
// Include watch-only for wallets without private key
|
||||
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled();
|
||||
m_coin_control->fAllowWatchOnly = model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner();
|
||||
}
|
||||
|
||||
void SendCoinsDialog::updateNumberOfBlocks(int count, const QDateTime& blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state) {
|
||||
|
|
|
@ -263,6 +263,9 @@ void CreateWalletActivity::createWallet()
|
|||
if (m_create_wallet_dialog->isDescriptorWalletChecked()) {
|
||||
flags |= WALLET_FLAG_DESCRIPTORS;
|
||||
}
|
||||
if (m_create_wallet_dialog->isExternalSignerChecked()) {
|
||||
flags |= WALLET_FLAG_EXTERNAL_SIGNER;
|
||||
}
|
||||
|
||||
QTimer::singleShot(500, worker(), [this, name, flags] {
|
||||
std::unique_ptr<interfaces::Wallet> wallet = node().walletClient().createWallet(name, m_passphrase, flags, m_error_message, m_warning_message);
|
||||
|
@ -291,6 +294,17 @@ void CreateWalletActivity::finish()
|
|||
void CreateWalletActivity::create()
|
||||
{
|
||||
m_create_wallet_dialog = new CreateWalletDialog(m_parent_widget);
|
||||
|
||||
#ifdef ENABLE_EXTERNAL_SIGNER
|
||||
std::vector<ExternalSigner> signers;
|
||||
try {
|
||||
signers = node().externalSigners();
|
||||
} catch (const std::runtime_error& e) {
|
||||
QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
|
||||
}
|
||||
m_create_wallet_dialog->setSigners(signers);
|
||||
#endif
|
||||
|
||||
m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
|
||||
m_create_wallet_dialog->show();
|
||||
|
||||
|
|
|
@ -552,6 +552,18 @@ bool WalletModel::bumpFee(uint256 hash, uint256& new_hash)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool WalletModel::displayAddress(std::string sAddress)
|
||||
{
|
||||
CTxDestination dest = DecodeDestination(sAddress);
|
||||
bool res = false;
|
||||
try {
|
||||
res = m_wallet->displayAddress(dest);
|
||||
} catch (const std::runtime_error& e) {
|
||||
QMessageBox::critical(nullptr, tr("Can't display address"), e.what());
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool WalletModel::isWalletEnabled()
|
||||
{
|
||||
return !gArgs.GetBoolArg("-disablewallet", DEFAULT_DISABLE_WALLET);
|
||||
|
|
|
@ -136,6 +136,7 @@ public:
|
|||
UnlockContext requestUnlock();
|
||||
|
||||
bool bumpFee(uint256 hash, uint256& new_hash);
|
||||
bool displayAddress(std::string sAddress);
|
||||
|
||||
static bool isWalletEnabled();
|
||||
|
||||
|
|
|
@ -26,6 +26,11 @@ CTransactionRef& WalletModelTransaction::getWtx()
|
|||
return wtx;
|
||||
}
|
||||
|
||||
void WalletModelTransaction::setWtx(const CTransactionRef& newTx)
|
||||
{
|
||||
wtx = newTx;
|
||||
}
|
||||
|
||||
unsigned int WalletModelTransaction::getTransactionSize()
|
||||
{
|
||||
return wtx ? GetVirtualTransactionSize(*wtx) : 0;
|
||||
|
|
|
@ -27,6 +27,8 @@ public:
|
|||
QList<SendCoinsRecipient> getRecipients() const;
|
||||
|
||||
CTransactionRef& getWtx();
|
||||
void setWtx(const CTransactionRef&);
|
||||
|
||||
unsigned int getTransactionSize();
|
||||
|
||||
void setTransactionFee(const CAmount& newFee);
|
||||
|
|
|
@ -206,6 +206,11 @@ public:
|
|||
WalletBatch batch{m_wallet->GetDatabase()};
|
||||
return m_wallet->SetAddressReceiveRequest(batch, dest, id, value);
|
||||
}
|
||||
bool displayAddress(const CTxDestination& dest) override
|
||||
{
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
return m_wallet->DisplayAddress(dest);
|
||||
}
|
||||
void lockCoin(const COutPoint& output) override
|
||||
{
|
||||
LOCK(m_wallet->cs_wallet);
|
||||
|
@ -446,6 +451,7 @@ public:
|
|||
unsigned int getConfirmTarget() override { return m_wallet->m_confirm_target; }
|
||||
bool hdEnabled() override { return m_wallet->IsHDEnabled(); }
|
||||
bool canGetAddresses() override { return m_wallet->CanGetAddresses(); }
|
||||
bool hasExternalSigner() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_EXTERNAL_SIGNER); }
|
||||
bool privateKeysDisabled() override { return m_wallet->IsWalletFlagSet(WALLET_FLAG_DISABLE_PRIVATE_KEYS); }
|
||||
OutputType getDefaultAddressType() override { return m_wallet->m_default_address_type; }
|
||||
CAmount getDefaultMaxTxFee() override { return m_wallet->m_default_max_tx_fee; }
|
||||
|
|
Loading…
Reference in a new issue