5#if defined(HAVE_CONFIG_H)
6#include <config/bitcoin-config.h>
9#include <qt/forms/ui_sendcoinsdialog.h>
12#include <chainparams.h>
29#include <validation.h>
37#include <QTextDocument>
43 fNewRecipientAllowed(true), fFeeMinimized(true),
44 platformStyle(_platformStyle) {
48 ui->addButton->setIcon(QIcon());
49 ui->clearButton->setIcon(QIcon());
50 ui->sendButton->setIcon(QIcon());
53 ui->clearButton->setIcon(
55 ui->sendButton->setIcon(
63 connect(
ui->addButton, &QPushButton::clicked,
this,
65 connect(
ui->clearButton, &QPushButton::clicked,
this,
69 connect(
ui->pushButtonCoinControl, &QPushButton::clicked,
this,
71 connect(
ui->checkBoxCoinControlChange, &QCheckBox::stateChanged,
this,
73 connect(
ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited,
77 QAction *clipboardQuantityAction =
new QAction(tr(
"Copy quantity"),
this);
78 QAction *clipboardAmountAction =
new QAction(tr(
"Copy amount"),
this);
79 QAction *clipboardFeeAction =
new QAction(tr(
"Copy fee"),
this);
80 QAction *clipboardAfterFeeAction =
new QAction(tr(
"Copy after fee"),
this);
81 QAction *clipboardBytesAction =
new QAction(tr(
"Copy bytes"),
this);
82 QAction *clipboardLowOutputAction =
new QAction(tr(
"Copy dust"),
this);
83 QAction *clipboardChangeAction =
new QAction(tr(
"Copy change"),
this);
84 connect(clipboardQuantityAction, &QAction::triggered,
this,
86 connect(clipboardAmountAction, &QAction::triggered,
this,
88 connect(clipboardFeeAction, &QAction::triggered,
this,
90 connect(clipboardAfterFeeAction, &QAction::triggered,
this,
92 connect(clipboardBytesAction, &QAction::triggered,
this,
94 connect(clipboardLowOutputAction, &QAction::triggered,
this,
96 connect(clipboardChangeAction, &QAction::triggered,
this,
98 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
99 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
100 ui->labelCoinControlFee->addAction(clipboardFeeAction);
101 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
102 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
103 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
104 ui->labelCoinControlChange->addAction(clipboardChangeAction);
108 if (!settings.contains(
"fFeeSectionMinimized")) {
109 settings.setValue(
"fFeeSectionMinimized",
true);
112 if (!settings.contains(
"nFeeRadio") &&
113 settings.contains(
"nTransactionFee") &&
114 settings.value(
"nTransactionFee").toLongLong() > 0) {
116 settings.setValue(
"nFeeRadio", 1);
118 if (!settings.contains(
"nFeeRadio")) {
120 settings.setValue(
"nFeeRadio", 0);
122 if (!settings.contains(
"nTransactionFee")) {
123 settings.setValue(
"nTransactionFee",
126 ui->groupFee->setId(
ui->radioSmartFee, 0);
127 ui->groupFee->setId(
ui->radioCustomFee, 1);
130 std::max<int>(0, std::min(1, settings.value(
"nFeeRadio").toInt())))
132 ui->customFee->SetAllowEmpty(
false);
133 ui->customFee->setValue(
134 int64_t(settings.value(
"nTransactionFee").toLongLong()) *
SATOSHI);
151 this->
model = _model;
154 for (
int i = 0; i <
ui->entries->count(); ++i) {
156 ui->entries->itemAt(i)->widget());
176 ui->frameCoinControl->setVisible(
181#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
182 const auto buttonClickedEvent =
183 QOverload<int>::of(&QButtonGroup::idClicked);
186 const auto buttonClickedEvent =
187 static_cast<void (QButtonGroup::*)(
int)
>(
188 &QButtonGroup::buttonClicked);
190 connect(
ui->groupFee, buttonClickedEvent,
this,
192 connect(
ui->groupFee, buttonClickedEvent,
this,
197 ui->customFee->SetMinValue(requiredFee);
198 if (
ui->customFee->value() < requiredFee) {
199 ui->customFee->setValue(requiredFee);
201 ui->customFee->setSingleStep(requiredFee);
206 ui->sendButton->setText(tr(
"Cr&eate Unsigned"));
207 ui->sendButton->setToolTip(
208 tr(
"Creates a Partially Signed Bitcoin Transaction (PSBT) for "
209 "use with e.g. an offline %1 wallet, or a PSBT-compatible "
219 settings.setValue(
"nFeeRadio",
ui->groupFee->checkedId());
220 settings.setValue(
"nTransactionFee",
221 qint64(
ui->customFee->value() /
SATOSHI));
227 QString &informative_text,
228 QString &detailed_text) {
229 QList<SendCoinsRecipient> recipients;
232 for (
int i = 0; i <
ui->entries->count(); ++i) {
234 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
237 recipients.append(entry->
getValue());
239 ui->scrollArea->ensureWidgetVisible(entry);
245 if (!valid || recipients.isEmpty()) {
251 if (!
ctx.isValid()) {
259 std::make_unique<WalletModelTransaction>(recipients);
279 QStringList formatted;
287 tr(
" from wallet '%1'")
291 QString address = rcp.address;
293 QString recipientElement;
297 if (!rcp.paymentRequest.IsInitialized())
300 if (rcp.label.length() > 0) {
302 recipientElement.append(
305 recipientElement.append(QString(
" (%1)").arg(address));
308 recipientElement.append(tr(
"%1 to %2").arg(amount, address));
313 else if (!rcp.authenticatedMerchant.isEmpty()) {
314 recipientElement.append(
315 tr(
"%1 to '%2'").arg(amount, rcp.authenticatedMerchant));
318 recipientElement.append(tr(
"%1 to %2").arg(amount, address));
322 formatted.append(recipientElement);
326 question_string.append(tr(
"Do you want to draft this transaction?"));
328 question_string.append(tr(
"Are you sure you want to send?"));
331 question_string.append(
"<br /><span style='font-size:10pt;'>");
333 question_string.append(
334 tr(
"Please, review your transaction proposal. This will produce a "
335 "Partially Signed Bitcoin Transaction (PSBT) which you can save "
336 "or copy and then sign with e.g. an offline %1 wallet, or a "
337 "PSBT-compatible hardware wallet.")
340 question_string.append(tr(
"Please, review your transaction."));
342 question_string.append(
"</span>%1");
346 question_string.append(
"<hr /><b>");
347 question_string.append(tr(
"Transaction fee"));
348 question_string.append(
"</b>");
351 question_string.append(
358 question_string.append(
359 "<span style='color:#aa0000; font-weight:bold;'>");
362 question_string.append(
"</span><br />");
366 question_string.append(
"<hr />");
369 QStringList alternativeUnits;
372 alternativeUnits.append(
376 question_string.append(
377 QString(
"<b>%1</b>: <b>%2</b>")
378 .arg(tr(
"Total Amount"))
381 question_string.append(
382 QString(
"<br /><span style='font-size:10pt; "
383 "font-weight:normal;'>(=%1)</span>")
384 .arg(alternativeUnits.join(
" " + tr(
"or") +
" ")));
386 if (formatted.size() > 1) {
387 question_string = question_string.arg(
"");
389 tr(
"To review recipient list click \"Show Details...\"");
390 detailed_text = formatted.join(
"\n\n");
392 question_string = question_string.arg(
"<br /><br />" + formatted.at(0));
403 QString question_string, informative_text, detailed_text;
404 if (!
PrepareSendText(question_string, informative_text, detailed_text)) {
410 ? tr(
"Confirm transaction proposal")
411 : tr(
"Confirm send coins");
413 ? tr(
"Create Unsigned")
416 confirmation, question_string, informative_text, detailed_text,
418 confirmationDialog.
exec();
419 QMessageBox::StandardButton retval =
420 static_cast<QMessageBox::StandardButton
>(confirmationDialog.result());
422 if (retval != QMessageBox::Yes) {
427 bool send_failure =
false;
432 bool complete =
false;
435 true , psbtx, complete);
443 msgBox.setText(
"Unsigned Transaction");
444 msgBox.setInformativeText(
445 "The PSBT has been copied to the clipboard. You can also save it.");
446 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
447 msgBox.setDefaultButton(QMessageBox::Discard);
448 switch (msgBox.exec()) {
449 case QMessageBox::Save: {
450 QString selectedFilter;
451 QString fileNameSuggestion =
"";
456 fileNameSuggestion.append(
" - ");
458 QString labelOrAddress =
459 rcp.label.isEmpty() ? rcp.address : rcp.label;
462 fileNameSuggestion.append(labelOrAddress +
"-" + amount);
465 fileNameSuggestion.append(
".psbt");
467 this, tr(
"Save Transaction Data"), fileNameSuggestion,
468 tr(
"Partially Signed Transaction (Binary) (*.psbt)"),
470 if (filename.isEmpty()) {
473 std::ofstream out{filename.toLocal8Bit().data(),
474 std::ofstream::out | std::ofstream::binary};
477 Q_EMIT
message(tr(
"PSBT saved"),
"PSBT saved to disk",
481 case QMessageBox::Discard:
513 ui->checkBoxCoinControlChange->setChecked(
false);
514 ui->lineEditCoinControlChange->clear();
518 while (
ui->entries->count()) {
519 ui->entries->takeAt(0)->widget()->deleteLater();
536 ui->entries->addWidget(entry);
549 ui->scrollAreaWidgetContents->resize(
550 ui->scrollAreaWidgetContents->sizeHint());
551 qApp->processEvents();
552 QScrollBar *bar =
ui->scrollArea->verticalScrollBar();
554 bar->setSliderPosition(bar->maximum());
570 if (
ui->entries->count() == 1) {
574 entry->deleteLater();
580 for (
int i = 0; i <
ui->entries->count(); ++i) {
582 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
587 QWidget::setTabOrder(prev,
ui->sendButton);
588 QWidget::setTabOrder(
ui->sendButton,
ui->clearButton);
589 QWidget::setTabOrder(
ui->clearButton,
ui->addButton);
590 return ui->addButton;
596 if (
ui->entries->count() == 1) {
598 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(0)->widget());
617 if (
ui->entries->count() == 1) {
619 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(0)->widget());
643 ui->labelBalanceName->setText(tr(
"Watch-only balance:"));
658 const QString &msgArg) {
659 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
666 switch (sendCoinsReturn.
status) {
669 tr(
"The recipient address is not valid. Please recheck.");
672 msgParams.first = tr(
"The amount to pay must be larger than 0.");
675 msgParams.first = tr(
"The amount exceeds your balance.");
678 msgParams.first = tr(
"The total exceeds your balance when the %1 "
679 "transaction fee is included.")
683 msgParams.first = tr(
"Duplicate address found: addresses should "
684 "only be used once each.");
687 msgParams.first = tr(
"Transaction creation failed!");
692 tr(
"A fee higher than %1 is considered an absurdly high fee.")
698 msgParams.first = tr(
"Payment request expired.");
707 Q_EMIT
message(tr(
"Send Coins"), msgParams.first, msgParams.second);
711 ui->labelFeeMinimized->setVisible(fMinimize);
712 ui->buttonChooseFee->setVisible(fMinimize);
713 ui->buttonMinimizeFee->setVisible(!fMinimize);
714 ui->frameFeeSelection->setVisible(!fMinimize);
715 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0,
735 for (
int i = 0; i <
ui->entries->count(); ++i) {
737 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
738 if (e && !e->isHidden() && e != entry) {
752 ui->labelSmartFee->setEnabled(
ui->radioSmartFee->isChecked());
753 ui->labelSmartFee2->setEnabled(
ui->radioSmartFee->isChecked());
754 ui->labelFeeEstimation->setEnabled(
ui->radioSmartFee->isChecked());
755 ui->labelCustomFeeWarning->setEnabled(
ui->radioCustomFee->isChecked());
756 ui->labelCustomPerKilobyte->setEnabled(
ui->radioCustomFee->isChecked());
757 ui->customFee->setEnabled(
ui->radioCustomFee->isChecked());
765 if (
ui->radioSmartFee->isChecked()) {
766 ui->labelFeeMinimized->setText(
ui->labelSmartFee->text());
768 ui->labelFeeMinimized->setText(
771 ui->customFee->value()) +
777 if (
ui->radioCustomFee->isChecked()) {
787 const QDateTime &blockDate,
788 double nVerificationProgress,
806 ui->labelSmartFee->setText(
813 ui->labelSmartFee2->show();
814 ui->labelFeeEstimation->setText(
"");
816 ui->labelSmartFee2->hide();
817 ui->labelFeeEstimation->setText(
818 tr(
"Estimated to begin confirmation by next block."));
832 ui->labelCoinControlAmount->text().indexOf(
" ")));
838 ui->labelCoinControlFee->text()
839 .left(
ui->labelCoinControlFee->text().indexOf(
" "))
846 ui->labelCoinControlAfterFee->text()
847 .left(
ui->labelCoinControlAfterFee->text().indexOf(
" "))
854 ui->labelCoinControlBytes->text().replace(
ASYMP_UTF8,
""));
865 ui->labelCoinControlChange->text()
866 .left(
ui->labelCoinControlChange->text().indexOf(
" "))
872 ui->frameCoinControl->setVisible(checked);
875 if (!checked &&
model) {
891 if (state == Qt::Unchecked) {
893 ui->labelCoinControlChangeLabel->clear();
899 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
907 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:red;}");
912 if (text.isEmpty()) {
914 ui->labelCoinControlChangeLabel->setText(
"");
917 ui->labelCoinControlChangeLabel->setText(
918 tr(
"Warning: Invalid Bitcoin address"));
922 ui->labelCoinControlChangeLabel->setText(
923 tr(
"Warning: Unknown change address"));
926 QMessageBox::StandardButton btnRetVal = QMessageBox::question(
927 this, tr(
"Confirm custom change address"),
928 tr(
"The address you selected for change is not part of "
929 "this wallet. Any or all funds in your wallet may be "
930 "sent to this address. Are you sure?"),
931 QMessageBox::Yes | QMessageBox::Cancel,
932 QMessageBox::Cancel);
934 if (btnRetVal == QMessageBox::Yes) {
937 ui->lineEditCoinControlChange->setText(
"");
938 ui->labelCoinControlChangeLabel->setStyleSheet(
939 "QLabel{color:black;}");
940 ui->labelCoinControlChangeLabel->setText(
"");
944 ui->labelCoinControlChangeLabel->setStyleSheet(
945 "QLabel{color:black;}");
948 QString associatedLabel =
950 if (!associatedLabel.isEmpty()) {
951 ui->labelCoinControlChangeLabel->setText(associatedLabel);
953 ui->labelCoinControlChangeLabel->setText(tr(
"(no label)"));
973 for (
int i = 0; i <
ui->entries->count(); ++i) {
975 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
976 if (entry && !entry->isHidden()) {
990 ui->labelCoinControlAutomaticallySelected->hide();
991 ui->widgetCoinControl->show();
994 ui->labelCoinControlAutomaticallySelected->show();
995 ui->widgetCoinControl->hide();
996 ui->labelCoinControlInsuffFunds->hide();
1001 const QString &title,
const QString &text,
const QString &informative_text,
1002 const QString &detailed_text,
int _secDelay,
1003 const QString &_confirmButtonText, QWidget *parent)
1004 : QMessageBox(parent), secDelay(_secDelay),
1005 confirmButtonText(_confirmButtonText) {
1006 setIcon(QMessageBox::Question);
1009 setWindowTitle(title);
1011 setInformativeText(informative_text);
1012 setDetailedText(detailed_text);
1013 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1014 setDefaultButton(QMessageBox::Cancel);
1024 return QMessageBox::exec();
static constexpr Amount SATOSHI
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
static QString formatWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
Unit
Currency units Please add only sensible ones.
static QString formatHtmlWithUnit(int unit, const Amount amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
@ MSG_INFORMATION
Predefined combinations for certain default usage cases.
bool fAllowWatchOnly
Includes watch only addresses which are solvable.
std::optional< CFeeRate > m_feerate
Override the wallet's m_pay_tx_fee if set.
Double ended buffer combining vector and stream-like interfaces.
Fee rate in satoshis per kilobyte: Amount / kB.
Amount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
A mutable version of CTransaction.
Model for Bitcoin network client.
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType header, SynchronizationState sync_state)
static QList< Amount > payAmounts
static void updateLabels(CCoinControl &m_coin_control, WalletModel *, QDialog *)
static bool fSubtractFeeFromAmount
int getDisplayUnit() const
bool getCoinControlFeatures() const
void coinControlFeaturesChanged(bool)
void displayUnitChanged(int unit)
Dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
ClientModel * clientModel
void coinControlChangeEdited(const QString &)
void coinControlChangeChecked(int)
void coinControlClipboardFee()
void on_sendButton_clicked()
void on_buttonChooseFee_clicked()
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
void setClientModel(ClientModel *clientModel)
void updateTabsAndLabels()
void updateFeeSectionControls()
std::unique_ptr< CCoinControl > m_coin_control
SendCoinsEntry * addEntry()
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, SyncType synctype, SynchronizationState sync_state)
void pasteEntry(const SendCoinsRecipient &rv)
void updateFeeMinimizedLabel()
const PlatformStyle * platformStyle
void coinControlClipboardQuantity()
void coinControlButtonClicked()
void coinControlClipboardAfterFee()
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
void setModel(WalletModel *model)
void coinControlClipboardLowOutput()
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
SendCoinsDialog(const PlatformStyle *platformStyle, WalletModel *model, QWidget *parent=nullptr)
void setBalance(const interfaces::WalletBalances &balances)
void coinControlClipboardAmount()
void updateCoinControlState(CCoinControl &ctrl)
void setAddress(const QString &address)
void coinControlClipboardChange()
std::unique_ptr< WalletModelTransaction > m_current_transaction
bool fNewRecipientAllowed
void removeEntry(SendCoinsEntry *entry)
void updateSmartFeeLabel()
void coinControlClipboardBytes()
void message(const QString &title, const QString &message, unsigned int style)
void coinsSent(const uint256 &txid)
void on_buttonMinimizeFee_clicked()
void coinControlUpdateLabels()
void coinControlFeatureChanged(bool)
void minimizeFeeSection(bool fMinimize)
A single entry in the dialog for sending bitcoins.
void setAmount(const Amount amount)
void setAddress(const QString &address)
bool isClear()
Return whether the entry is still empty and unedited.
void subtractFeeFromAmountChanged()
void useAvailableBalance(SendCoinsEntry *entry)
void setValue(const SendCoinsRecipient &value)
void setModel(WalletModel *model)
void removeEntry(SendCoinsEntry *entry)
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
bool validate(interfaces::Node &node)
void checkSubtractFeeFromAmount()
SendCoinsRecipient getValue()
bool fSubtractFeeFromAmount
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, const QString &confirmText="Send", QWidget *parent=nullptr)
QAbstractButton * yesButton
QString confirmButtonText
Signature hash type wrapper class.
Interface to Bitcoin wallet from Qt view code.
interfaces::Node & node() const
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
const CChainParams & getChainParams() const
AddressTableModel * getAddressTableModel()
OptionsModel * getOptionsModel()
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
interfaces::Wallet & wallet() const
UnlockContext requestUnlock()
void balanceChanged(const interfaces::WalletBalances &balances)
QString getWalletName() const
@ AmountWithFeeExceedsBalance
@ TransactionCreationFailed
virtual Amount getAvailableBalance(const CCoinControl &coin_control)=0
Get available balance.
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
virtual WalletBalances getBalances()=0
Get balances.
virtual TransactionError fillPSBT(SigHashType sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete) const =0
Fill PSBT.
virtual bool privateKeysDisabled()=0
virtual Amount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control)=0
Get minimum fee.
virtual Amount getDefaultMaxTxFee()=0
Get max tx fee.
virtual Amount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
CTxDestination DecodeDestination(const std::string &addr, const CChainParams ¶ms)
QString HtmlEscape(const QString &str, bool fMultiLine)
QString getSaveFileName(QWidget *parent, const QString &caption, const QString &dir, const QString &filter, QString *selectedSuffixOut)
Get save filename, mimics QFileDialog::getSaveFileName, except that it appends a default suffix when ...
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
void setClipboard(const QString &str)
#define SEND_CONFIRM_DELAY
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
std::variant< CNoDestination, PKHash, ScriptHash > CTxDestination
A txout script template with a specific destination.
static constexpr Amount zero() noexcept
A version of CTransaction with the PSBT format.
Collection of wallet balances.
Amount watch_only_balance
std::string EncodeBase64(Span< const uint8_t > input)
SynchronizationState
Current sync state passed to tip changed callbacks.
static const int PROTOCOL_VERSION
network protocol versioning
constexpr Amount DEFAULT_PAY_TX_FEE
-paytxfee default