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>
45 fNewRecipientAllowed(true), fFeeMinimized(true),
46 platformStyle(_platformStyle) {
50 ui->addButton->setIcon(QIcon());
51 ui->clearButton->setIcon(QIcon());
52 ui->sendButton->setIcon(QIcon());
55 ui->clearButton->setIcon(
57 ui->sendButton->setIcon(
65 connect(
ui->addButton, &QPushButton::clicked,
this,
67 connect(
ui->clearButton, &QPushButton::clicked,
this,
71 connect(
ui->pushButtonCoinControl, &QPushButton::clicked,
this,
73 connect(
ui->checkBoxCoinControlChange, &QCheckBox::stateChanged,
this,
75 connect(
ui->lineEditCoinControlChange, &QValidatedLineEdit::textEdited,
79 QAction *clipboardQuantityAction =
new QAction(tr(
"Copy quantity"),
this);
80 QAction *clipboardAmountAction =
new QAction(tr(
"Copy amount"),
this);
81 QAction *clipboardFeeAction =
new QAction(tr(
"Copy fee"),
this);
82 QAction *clipboardAfterFeeAction =
new QAction(tr(
"Copy after fee"),
this);
83 QAction *clipboardBytesAction =
new QAction(tr(
"Copy bytes"),
this);
84 QAction *clipboardLowOutputAction =
new QAction(tr(
"Copy dust"),
this);
85 QAction *clipboardChangeAction =
new QAction(tr(
"Copy change"),
this);
86 connect(clipboardQuantityAction, &QAction::triggered,
this,
88 connect(clipboardAmountAction, &QAction::triggered,
this,
90 connect(clipboardFeeAction, &QAction::triggered,
this,
92 connect(clipboardAfterFeeAction, &QAction::triggered,
this,
94 connect(clipboardBytesAction, &QAction::triggered,
this,
96 connect(clipboardLowOutputAction, &QAction::triggered,
this,
98 connect(clipboardChangeAction, &QAction::triggered,
this,
100 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
101 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
102 ui->labelCoinControlFee->addAction(clipboardFeeAction);
103 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
104 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
105 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
106 ui->labelCoinControlChange->addAction(clipboardChangeAction);
110 if (!settings.contains(
"fFeeSectionMinimized")) {
111 settings.setValue(
"fFeeSectionMinimized",
true);
114 if (!settings.contains(
"nFeeRadio") &&
115 settings.contains(
"nTransactionFee") &&
116 settings.value(
"nTransactionFee").toLongLong() > 0) {
118 settings.setValue(
"nFeeRadio", 1);
120 if (!settings.contains(
"nFeeRadio")) {
122 settings.setValue(
"nFeeRadio", 0);
124 if (!settings.contains(
"nTransactionFee")) {
125 settings.setValue(
"nTransactionFee",
128 ui->groupFee->setId(
ui->radioSmartFee, 0);
129 ui->groupFee->setId(
ui->radioCustomFee, 1);
132 std::max<int>(0, std::min(1, settings.value(
"nFeeRadio").toInt())))
134 ui->customFee->SetAllowEmpty(
false);
135 ui->customFee->setValue(
136 int64_t(settings.value(
"nTransactionFee").toLongLong()) *
SATOSHI);
153 this->
model = _model;
156 for (
int i = 0; i <
ui->entries->count(); ++i) {
158 ui->entries->itemAt(i)->widget());
178 ui->frameCoinControl->setVisible(
183#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
184 const auto buttonClickedEvent =
185 QOverload<int>::of(&QButtonGroup::idClicked);
188 const auto buttonClickedEvent =
189 static_cast<void (QButtonGroup::*)(
int)
>(
190 &QButtonGroup::buttonClicked);
192 connect(
ui->groupFee, buttonClickedEvent,
this,
194 connect(
ui->groupFee, buttonClickedEvent,
this,
199 ui->customFee->SetMinValue(requiredFee);
200 if (
ui->customFee->value() < requiredFee) {
201 ui->customFee->setValue(requiredFee);
203 ui->customFee->setSingleStep(requiredFee);
208 ui->sendButton->setText(tr(
"Cr&eate Unsigned"));
209 ui->sendButton->setToolTip(
210 tr(
"Creates a Partially Signed Bitcoin Transaction (PSBT) for "
211 "use with e.g. an offline %1 wallet, or a PSBT-compatible "
221 settings.setValue(
"nFeeRadio",
ui->groupFee->checkedId());
222 settings.setValue(
"nTransactionFee",
223 qint64(
ui->customFee->value() /
SATOSHI));
229 QString &informative_text,
230 QString &detailed_text) {
231 QList<SendCoinsRecipient> recipients;
234 for (
int i = 0; i <
ui->entries->count(); ++i) {
236 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
239 recipients.append(entry->
getValue());
241 ui->scrollArea->ensureWidgetVisible(entry);
247 if (!valid || recipients.isEmpty()) {
253 if (!
ctx.isValid()) {
261 std::make_unique<WalletModelTransaction>(recipients);
281 QStringList formatted;
289 tr(
" from wallet '%1'")
293 QString address = rcp.address;
295 QString recipientElement;
299 if (!rcp.paymentRequest.IsInitialized())
302 if (rcp.label.length() > 0) {
304 recipientElement.append(
307 recipientElement.append(QString(
" (%1)").arg(address));
310 recipientElement.append(tr(
"%1 to %2").arg(amount, address));
315 else if (!rcp.authenticatedMerchant.isEmpty()) {
316 recipientElement.append(
317 tr(
"%1 to '%2'").arg(amount, rcp.authenticatedMerchant));
320 recipientElement.append(tr(
"%1 to %2").arg(amount, address));
324 formatted.append(recipientElement);
328 question_string.append(tr(
"Do you want to draft this transaction?"));
330 question_string.append(tr(
"Are you sure you want to send?"));
333 question_string.append(
"<br /><span style='font-size:10pt;'>");
335 question_string.append(
336 tr(
"Please, review your transaction proposal. This will produce a "
337 "Partially Signed Bitcoin Transaction (PSBT) which you can save "
338 "or copy and then sign with e.g. an offline %1 wallet, or a "
339 "PSBT-compatible hardware wallet.")
342 question_string.append(tr(
"Please, review your transaction."));
344 question_string.append(
"</span>%1");
348 question_string.append(
"<hr /><b>");
349 question_string.append(tr(
"Transaction fee"));
350 question_string.append(
"</b>");
353 question_string.append(
360 question_string.append(
361 "<span style='color:#aa0000; font-weight:bold;'>");
364 question_string.append(
"</span><br />");
368 question_string.append(
"<hr />");
371 QStringList alternativeUnits;
374 alternativeUnits.append(
378 question_string.append(
379 QString(
"<b>%1</b>: <b>%2</b>")
380 .arg(tr(
"Total Amount"))
383 question_string.append(
384 QString(
"<br /><span style='font-size:10pt; "
385 "font-weight:normal;'>(=%1)</span>")
386 .arg(alternativeUnits.join(
" " + tr(
"or") +
" ")));
388 if (formatted.size() > 1) {
389 question_string = question_string.arg(
"");
391 tr(
"To review recipient list click \"Show Details...\"");
392 detailed_text = formatted.join(
"\n\n");
394 question_string = question_string.arg(
"<br /><br />" + formatted.at(0));
405 QString question_string, informative_text, detailed_text;
406 if (!
PrepareSendText(question_string, informative_text, detailed_text)) {
412 ? tr(
"Confirm transaction proposal")
413 : tr(
"Confirm send coins");
415 ? tr(
"Create Unsigned")
418 confirmation, question_string, informative_text, detailed_text,
420 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
423 static_cast<QMessageBox::StandardButton
>(confirmationDialog->exec());
425 if (retval != QMessageBox::Yes) {
430 bool send_failure =
false;
435 bool complete =
false;
438 true , psbtx, complete);
446 msgBox.setText(
"Unsigned Transaction");
447 msgBox.setInformativeText(
448 "The PSBT has been copied to the clipboard. You can also save it.");
449 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
450 msgBox.setDefaultButton(QMessageBox::Discard);
451 switch (msgBox.exec()) {
452 case QMessageBox::Save: {
453 QString selectedFilter;
454 QString fileNameSuggestion =
"";
459 fileNameSuggestion.append(
" - ");
461 QString labelOrAddress =
462 rcp.label.isEmpty() ? rcp.address : rcp.label;
465 fileNameSuggestion.append(labelOrAddress +
"-" + amount);
468 fileNameSuggestion.append(
".psbt");
470 this, tr(
"Save Transaction Data"), fileNameSuggestion,
471 tr(
"Partially Signed Transaction (Binary) (*.psbt)"),
473 if (filename.isEmpty()) {
476 std::ofstream out{filename.toLocal8Bit().data(),
477 std::ofstream::out | std::ofstream::binary};
480 Q_EMIT
message(tr(
"PSBT saved"),
"PSBT saved to disk",
484 case QMessageBox::Discard:
516 ui->checkBoxCoinControlChange->setChecked(
false);
517 ui->lineEditCoinControlChange->clear();
521 while (
ui->entries->count()) {
522 ui->entries->takeAt(0)->widget()->deleteLater();
539 ui->entries->addWidget(entry);
552 ui->scrollAreaWidgetContents->resize(
553 ui->scrollAreaWidgetContents->sizeHint());
554 qApp->processEvents();
555 QScrollBar *bar =
ui->scrollArea->verticalScrollBar();
557 bar->setSliderPosition(bar->maximum());
573 if (
ui->entries->count() == 1) {
577 entry->deleteLater();
583 for (
int i = 0; i <
ui->entries->count(); ++i) {
585 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
590 QWidget::setTabOrder(prev,
ui->sendButton);
591 QWidget::setTabOrder(
ui->sendButton,
ui->clearButton);
592 QWidget::setTabOrder(
ui->clearButton,
ui->addButton);
593 return ui->addButton;
599 if (
ui->entries->count() == 1) {
601 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(0)->widget());
620 if (
ui->entries->count() == 1) {
622 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(0)->widget());
646 ui->labelBalanceName->setText(tr(
"Watch-only balance:"));
661 const QString &msgArg) {
662 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
669 switch (sendCoinsReturn.
status) {
672 tr(
"The recipient address is not valid. Please recheck.");
675 msgParams.first = tr(
"The amount to pay must be larger than 0.");
678 msgParams.first = tr(
"The amount exceeds your balance.");
681 msgParams.first = tr(
"The total exceeds your balance when the %1 "
682 "transaction fee is included.")
686 msgParams.first = tr(
"Duplicate address found: addresses should "
687 "only be used once each.");
690 msgParams.first = tr(
"Transaction creation failed!");
695 tr(
"A fee higher than %1 is considered an absurdly high fee.")
701 msgParams.first = tr(
"Payment request expired.");
710 Q_EMIT
message(tr(
"Send Coins"), msgParams.first, msgParams.second);
714 ui->labelFeeMinimized->setVisible(fMinimize);
715 ui->buttonChooseFee->setVisible(fMinimize);
716 ui->buttonMinimizeFee->setVisible(!fMinimize);
717 ui->frameFeeSelection->setVisible(!fMinimize);
718 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0,
738 for (
int i = 0; i <
ui->entries->count(); ++i) {
740 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
741 if (e && !e->isHidden() && e != entry) {
755 ui->labelSmartFee->setEnabled(
ui->radioSmartFee->isChecked());
756 ui->labelSmartFee2->setEnabled(
ui->radioSmartFee->isChecked());
757 ui->labelFeeEstimation->setEnabled(
ui->radioSmartFee->isChecked());
758 ui->labelCustomFeeWarning->setEnabled(
ui->radioCustomFee->isChecked());
759 ui->labelCustomPerKilobyte->setEnabled(
ui->radioCustomFee->isChecked());
760 ui->customFee->setEnabled(
ui->radioCustomFee->isChecked());
768 if (
ui->radioSmartFee->isChecked()) {
769 ui->labelFeeMinimized->setText(
ui->labelSmartFee->text());
771 ui->labelFeeMinimized->setText(
774 ui->customFee->value()) +
780 if (
ui->radioCustomFee->isChecked()) {
790 const QDateTime &blockDate,
791 double nVerificationProgress,
809 ui->labelSmartFee->setText(
816 ui->labelSmartFee2->show();
817 ui->labelFeeEstimation->setText(
"");
819 ui->labelSmartFee2->hide();
820 ui->labelFeeEstimation->setText(
821 tr(
"Estimated to begin confirmation by next block."));
835 ui->labelCoinControlAmount->text().indexOf(
" ")));
841 ui->labelCoinControlFee->text()
842 .left(
ui->labelCoinControlFee->text().indexOf(
" "))
849 ui->labelCoinControlAfterFee->text()
850 .left(
ui->labelCoinControlAfterFee->text().indexOf(
" "))
857 ui->labelCoinControlBytes->text().replace(
ASYMP_UTF8,
""));
868 ui->labelCoinControlChange->text()
869 .left(
ui->labelCoinControlChange->text().indexOf(
" "))
875 ui->frameCoinControl->setVisible(checked);
878 if (!checked &&
model) {
888 connect(dlg, &QDialog::finished,
this,
895 if (state == Qt::Unchecked) {
897 ui->labelCoinControlChangeLabel->clear();
903 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
911 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:red;}");
916 if (text.isEmpty()) {
918 ui->labelCoinControlChangeLabel->setText(
"");
921 ui->labelCoinControlChangeLabel->setText(
922 tr(
"Warning: Invalid Bitcoin address"));
926 ui->labelCoinControlChangeLabel->setText(
927 tr(
"Warning: Unknown change address"));
930 QMessageBox::StandardButton btnRetVal = QMessageBox::question(
931 this, tr(
"Confirm custom change address"),
932 tr(
"The address you selected for change is not part of "
933 "this wallet. Any or all funds in your wallet may be "
934 "sent to this address. Are you sure?"),
935 QMessageBox::Yes | QMessageBox::Cancel,
936 QMessageBox::Cancel);
938 if (btnRetVal == QMessageBox::Yes) {
941 ui->lineEditCoinControlChange->setText(
"");
942 ui->labelCoinControlChangeLabel->setStyleSheet(
943 "QLabel{color:black;}");
944 ui->labelCoinControlChangeLabel->setText(
"");
948 ui->labelCoinControlChangeLabel->setStyleSheet(
949 "QLabel{color:black;}");
952 QString associatedLabel =
954 if (!associatedLabel.isEmpty()) {
955 ui->labelCoinControlChangeLabel->setText(associatedLabel);
957 ui->labelCoinControlChangeLabel->setText(tr(
"(no label)"));
977 for (
int i = 0; i <
ui->entries->count(); ++i) {
979 qobject_cast<SendCoinsEntry *>(
ui->entries->itemAt(i)->widget());
980 if (entry && !entry->isHidden()) {
994 ui->labelCoinControlAutomaticallySelected->hide();
995 ui->widgetCoinControl->show();
998 ui->labelCoinControlAutomaticallySelected->show();
999 ui->widgetCoinControl->hide();
1000 ui->labelCoinControlInsuffFunds->hide();
1005 const QString &title,
const QString &text,
const QString &informative_text,
1006 const QString &detailed_text,
int _secDelay,
1007 const QString &_confirmButtonText, QWidget *parent)
1008 : QMessageBox(parent), secDelay(_secDelay),
1009 confirmButtonText(_confirmButtonText) {
1010 setIcon(QMessageBox::Question);
1013 setWindowTitle(title);
1015 setInformativeText(informative_text);
1016 setDetailedText(detailed_text);
1017 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1018 setDefaultButton(QMessageBox::Cancel);
1028 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
Double ended buffer combining vector and stream-like interfaces.
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 std::optional< common::PSBTError > fillPSBT(SigHashType sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete) const =0
Fill PSBT.
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
virtual WalletBalances getBalances()=0
Get balances.
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.
constexpr Amount DEFAULT_PAY_TX_FEE
-paytxfee default