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->setAttribute(Qt::WA_DeleteOnClose);
421 static_cast<QMessageBox::StandardButton
>(confirmationDialog->exec());
423 if (retval != QMessageBox::Yes) {
428 bool send_failure =
false;
433 bool complete =
false;
436 true , psbtx, complete);
444 msgBox.setText(
"Unsigned Transaction");
445 msgBox.setInformativeText(
446 "The PSBT has been copied to the clipboard. You can also save it.");
447 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
448 msgBox.setDefaultButton(QMessageBox::Discard);
449 switch (msgBox.exec()) {
450 case QMessageBox::Save: {
451 QString selectedFilter;
452 QString fileNameSuggestion =
"";
457 fileNameSuggestion.append(
" - ");
459 QString labelOrAddress =
460 rcp.label.isEmpty() ? rcp.address : rcp.label;
463 fileNameSuggestion.append(labelOrAddress +
"-" + amount);
466 fileNameSuggestion.append(
".psbt");
468 this, tr(
"Save Transaction Data"), fileNameSuggestion,
469 tr(
"Partially Signed Transaction (Binary) (*.psbt)"),
471 if (filename.isEmpty()) {
474 std::ofstream out{filename.toLocal8Bit().data(),
475 std::ofstream::out | std::ofstream::binary};
478 Q_EMIT
message(tr(
"PSBT saved"),
"PSBT saved to disk",
482 case QMessageBox::Discard:
514 ui->checkBoxCoinControlChange->setChecked(
false);
515 ui->lineEditCoinControlChange->clear();
519 while (
ui->entries->count()) {
520 ui->entries->takeAt(0)->widget()->deleteLater();
537 ui->entries->addWidget(entry);
550 ui->scrollAreaWidgetContents->resize(
551 ui->scrollAreaWidgetContents->sizeHint());
552 qApp->processEvents();
553 QScrollBar *bar =
ui->scrollArea->verticalScrollBar();
555 bar->setSliderPosition(bar->maximum());
571 if (
ui->entries->count() == 1) {
575 entry->deleteLater();
581 for (
int i = 0; i <
ui->entries->count(); ++i) {
583 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
588 QWidget::setTabOrder(prev,
ui->sendButton);
589 QWidget::setTabOrder(
ui->sendButton,
ui->clearButton);
590 QWidget::setTabOrder(
ui->clearButton,
ui->addButton);
591 return ui->addButton;
597 if (
ui->entries->count() == 1) {
599 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(0)->widget());
618 if (
ui->entries->count() == 1) {
620 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(0)->widget());
644 ui->labelBalanceName->setText(tr(
"Watch-only balance:"));
659 const QString &msgArg) {
660 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
667 switch (sendCoinsReturn.
status) {
670 tr(
"The recipient address is not valid. Please recheck.");
673 msgParams.first = tr(
"The amount to pay must be larger than 0.");
676 msgParams.first = tr(
"The amount exceeds your balance.");
679 msgParams.first = tr(
"The total exceeds your balance when the %1 "
680 "transaction fee is included.")
684 msgParams.first = tr(
"Duplicate address found: addresses should "
685 "only be used once each.");
688 msgParams.first = tr(
"Transaction creation failed!");
693 tr(
"A fee higher than %1 is considered an absurdly high fee.")
699 msgParams.first = tr(
"Payment request expired.");
708 Q_EMIT
message(tr(
"Send Coins"), msgParams.first, msgParams.second);
712 ui->labelFeeMinimized->setVisible(fMinimize);
713 ui->buttonChooseFee->setVisible(fMinimize);
714 ui->buttonMinimizeFee->setVisible(!fMinimize);
715 ui->frameFeeSelection->setVisible(!fMinimize);
716 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0,
736 for (
int i = 0; i <
ui->entries->count(); ++i) {
738 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
739 if (e && !e->isHidden() && e != entry) {
753 ui->labelSmartFee->setEnabled(
ui->radioSmartFee->isChecked());
754 ui->labelSmartFee2->setEnabled(
ui->radioSmartFee->isChecked());
755 ui->labelFeeEstimation->setEnabled(
ui->radioSmartFee->isChecked());
756 ui->labelCustomFeeWarning->setEnabled(
ui->radioCustomFee->isChecked());
757 ui->labelCustomPerKilobyte->setEnabled(
ui->radioCustomFee->isChecked());
758 ui->customFee->setEnabled(
ui->radioCustomFee->isChecked());
766 if (
ui->radioSmartFee->isChecked()) {
767 ui->labelFeeMinimized->setText(
ui->labelSmartFee->text());
769 ui->labelFeeMinimized->setText(
772 ui->customFee->value()) +
778 if (
ui->radioCustomFee->isChecked()) {
788 const QDateTime &blockDate,
789 double nVerificationProgress,
807 ui->labelSmartFee->setText(
814 ui->labelSmartFee2->show();
815 ui->labelFeeEstimation->setText(
"");
817 ui->labelSmartFee2->hide();
818 ui->labelFeeEstimation->setText(
819 tr(
"Estimated to begin confirmation by next block."));
833 ui->labelCoinControlAmount->text().indexOf(
" ")));
839 ui->labelCoinControlFee->text()
840 .left(
ui->labelCoinControlFee->text().indexOf(
" "))
847 ui->labelCoinControlAfterFee->text()
848 .left(
ui->labelCoinControlAfterFee->text().indexOf(
" "))
855 ui->labelCoinControlBytes->text().replace(
ASYMP_UTF8,
""));
866 ui->labelCoinControlChange->text()
867 .left(
ui->labelCoinControlChange->text().indexOf(
" "))
873 ui->frameCoinControl->setVisible(checked);
876 if (!checked &&
model) {
886 connect(dlg, &QDialog::finished,
this,
893 if (state == Qt::Unchecked) {
895 ui->labelCoinControlChangeLabel->clear();
901 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
909 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:red;}");
914 if (text.isEmpty()) {
916 ui->labelCoinControlChangeLabel->setText(
"");
919 ui->labelCoinControlChangeLabel->setText(
920 tr(
"Warning: Invalid Bitcoin address"));
924 ui->labelCoinControlChangeLabel->setText(
925 tr(
"Warning: Unknown change address"));
928 QMessageBox::StandardButton btnRetVal = QMessageBox::question(
929 this, tr(
"Confirm custom change address"),
930 tr(
"The address you selected for change is not part of "
931 "this wallet. Any or all funds in your wallet may be "
932 "sent to this address. Are you sure?"),
933 QMessageBox::Yes | QMessageBox::Cancel,
934 QMessageBox::Cancel);
936 if (btnRetVal == QMessageBox::Yes) {
939 ui->lineEditCoinControlChange->setText(
"");
940 ui->labelCoinControlChangeLabel->setStyleSheet(
941 "QLabel{color:black;}");
942 ui->labelCoinControlChangeLabel->setText(
"");
946 ui->labelCoinControlChangeLabel->setStyleSheet(
947 "QLabel{color:black;}");
950 QString associatedLabel =
952 if (!associatedLabel.isEmpty()) {
953 ui->labelCoinControlChangeLabel->setText(associatedLabel);
955 ui->labelCoinControlChangeLabel->setText(tr(
"(no label)"));
975 for (
int i = 0; i <
ui->entries->count(); ++i) {
977 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
978 if (entry && !entry->isHidden()) {
992 ui->labelCoinControlAutomaticallySelected->hide();
993 ui->widgetCoinControl->show();
996 ui->labelCoinControlAutomaticallySelected->show();
997 ui->widgetCoinControl->hide();
998 ui->labelCoinControlInsuffFunds->hide();
1003 const QString &title,
const QString &text,
const QString &informative_text,
1004 const QString &detailed_text,
int _secDelay,
1005 const QString &_confirmButtonText, QWidget *parent)
1006 : QMessageBox(parent), secDelay(_secDelay),
1007 confirmButtonText(_confirmButtonText) {
1008 setIcon(QMessageBox::Question);
1011 setWindowTitle(title);
1013 setInformativeText(informative_text);
1014 setDetailedText(detailed_text);
1015 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1016 setDefaultButton(QMessageBox::Cancel);
1026 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.
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)
void ShowModalDialogAsynchronously(QDialog *dialog)
Shows a QDialog instance asynchronously, and deletes it on close.
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