Skip to content
Bitcoin Roots

v30.3-roots.1 · Commit 26: [PATCH 26/68] feat(qt): enhance privacy-aware coin control

src/qt/sendcoinsdialog.cpp

Before: src/qt/sendcoinsdialog.cpp · After: src/qt/sendcoinsdialog.cpp

File metadata
index 8fea0af8e27d7852f6e5df6d02532a928bf9339f..e8308101af7d49e1a4a75c516376af080260545c 100644

Download original patch · Release notes

Diff rows 1–93 of 93. Line numbers refer to the original before/after files.

+ Added− RemovedUnmarked lines are context.

Scroll the code area horizontally to read long lines. Before is on the left; after is on the right. Unified view fits narrow screens more easily.

src/qt/sendcoinsdialog.cpp CHANGED
@@ -164,6 +164,8 @@ void SendCoinsDialog::setModel(WalletModel *_model)
164
  }
165
 
166
  connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::setBalance);
 
 
167
  connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance);
168
  refreshBalance();
169
 
@@ -370,6 +372,8 @@ bool SendCoinsDialog::PrepareSendText(QString& question_string, QString& informa
370
 
371
  }
372
 
 
 
373
  // Show the signaling in the prepared transaction, rather than the
374
  // requested value, so the confirmation states the effective result even
375
  // when no fee is required.
@@ -484,6 +488,45 @@ bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx,
484
  return true;
485
  }
486
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
487
  void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
488
  {
489
  if(!model || !model->getOptionsModel())
@@ -506,6 +549,11 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
506
  fNewRecipientAllowed = true;
507
  return;
508
  }
 
 
 
 
 
509
 
510
  bool send_failure = false;
511
  if (retval == QMessageBox::Save) {
@@ -552,9 +600,18 @@ void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
552
  // Broadcast the transaction, unless an external signer was used and it
553
  // failed, or more signatures are needed.
554
  if (broadcast) {
555
- // now send the prepared transaction
556
- model->sendCoins(*m_current_transaction);
557
- Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
 
 
 
 
 
 
 
 
 
558
  }
559
  }
560
  if (!send_failure) {
 
164
  }
165
 
166
  connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::setBalance);
167
+ connect(_model, &WalletModel::balanceChanged, this, &SendCoinsDialog::walletChanged);
168
+ connect(_model, &WalletModel::unload, this, &SendCoinsDialog::reject);
169
  connect(_model->getOptionsModel(), &OptionsModel::displayUnitChanged, this, &SendCoinsDialog::refreshBalance);
170
  refreshBalance();
171
 
 
372
 
373
  }
374
 
375
+ appendCoinControlSummary(question_string);
376
+
377
  // Show the signaling in the prepared transaction, rather than the
378
  // requested value, so the confirmation states the effective result even
379
  // when no fee is required.
 
488
  return true;
489
  }
490
 
491
+ void SendCoinsDialog::appendCoinControlSummary(QString& text) const
492
+ {
493
+ if (!m_current_transaction || !m_current_transaction->getWtx()) return;
494
+
495
+ const CTransaction& tx{*m_current_transaction->getWtx()};
496
+ std::vector<COutPoint> outpoints;
497
+ outpoints.reserve(tx.vin.size());
498
+ for (const CTxIn& input : tx.vin) outpoints.push_back(input.prevout);
499
+
500
+ CAmount input_amount{0};
501
+ for (const auto& coin : model->wallet().getCoins(outpoints)) input_amount += coin.txout.nValue;
502
+ const CAmount fee{m_current_transaction->getTransactionFee()};
503
+ const CAmount change{input_amount - m_current_transaction->getTotalTransactionAmount() - fee};
504
+ const BitcoinUnit unit{model->getOptionsModel()->getDisplayUnit()};
505
+
506
+ text.append("<hr /><b>" + tr("Effective coin control") + "</b><br />");
507
+ text.append(tr("Inputs: %1 (%2)")
508
+ .arg(tx.vin.size())
509
+ .arg(BitcoinUnits::formatHtmlWithUnit(unit, input_amount)) + "<br />");
510
+ text.append(tr("Estimated virtual size: %1 vB").arg(m_current_transaction->getTransactionSize()) + "<br />");
511
+ const unsigned int vsize{m_current_transaction->getTransactionSize()};
512
+ text.append(tr("Fee rate: %1 sat/vB")
513
+ .arg(QString::number(vsize > 0 ? static_cast<double>(fee) / vsize : 0, 'f', 1)) + "<br />");
514
+ text.append(tr("Change: %1").arg(BitcoinUnits::formatHtmlWithUnit(unit, std::max<CAmount>(change, 0))) + "<br />");
515
+ }
516
+
517
+ void SendCoinsDialog::invalidateSendPreview()
518
+ {
519
+ if (m_sending) return;
520
+ m_current_transaction.reset();
521
+ m_coin_control->UnSelectAll();
522
+ coinControlUpdateLabels();
523
+ }
524
+
525
+ void SendCoinsDialog::walletChanged([[maybe_unused]] const interfaces::WalletBalances& balances)
526
+ {
527
+ invalidateSendPreview();
528
+ }
529
+
530
  void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)
531
  {
532
  if(!model || !model->getOptionsModel())
 
549
  fNewRecipientAllowed = true;
550
  return;
551
  }
552
+ if (!m_current_transaction) {
553
+ Q_EMIT message(tr("Send Coins"), tr("Wallet state changed. Review the current inputs and create a new preview."), CClientUIInterface::MSG_WARNING);
554
+ fNewRecipientAllowed = true;
555
+ return;
556
+ }
557
 
558
  bool send_failure = false;
559
  if (retval == QMessageBox::Save) {
 
600
  // Broadcast the transaction, unless an external signer was used and it
601
  // failed, or more signatures are needed.
602
  if (broadcast) {
603
+ // Keep the prepared choice valid through the synchronous commit.
604
+ // Wallet balance notifications from the successful commit must not
605
+ // invalidate its own transaction before it is announced.
606
+ m_sending = true;
607
+ try {
608
+ model->sendCoins(*m_current_transaction);
609
+ Q_EMIT coinsSent(m_current_transaction->getWtx()->GetHash());
610
+ } catch (const std::runtime_error& e) {
611
+ Q_EMIT message(tr("Send Coins"), QString::fromStdString(e.what()), CClientUIInterface::MSG_ERROR);
612
+ send_failure = true;
613
+ }
614
+ m_sending = false;
615
  }
616
  }
617
  if (!send_failure) {