-
Notifications
You must be signed in to change notification settings - Fork 326
UI external signer support (e.g. hardware wallet) #4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
6cdbc83
eef8d64
450cb40
62ac119
3f845ea
24815c6
1c4b456
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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()) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. As this condition is pretty logical and common now, maybe it should be given its own name e.g. noPrivateKeys()
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about |
||
| 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) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.