5 #if defined(HAVE_CONFIG_H)
10 #include <qt/forms/ui_sendcoinsdialog.h>
31 #include <validation.h>
33 #include <QFontMetrics>
36 #include <QTextDocument>
38 static const std::array<int, 9>
confTargets = { {2, 4, 6, 12, 24, 48, 144, 504, 1008} };
40 if (index+1 > static_cast<int>(
confTargets.size())) {
49 for (
unsigned int i = 0; i <
confTargets.size(); i++) {
63 fNewRecipientAllowed(true),
65 platformStyle(_platformStyle)
70 ui->addButton->setIcon(QIcon());
71 ui->clearButton->setIcon(QIcon());
72 ui->sendButton->setIcon(QIcon());
92 QAction *clipboardQuantityAction =
new QAction(tr(
"Copy quantity"),
this);
93 QAction *clipboardAmountAction =
new QAction(tr(
"Copy amount"),
this);
94 QAction *clipboardFeeAction =
new QAction(tr(
"Copy fee"),
this);
95 QAction *clipboardAfterFeeAction =
new QAction(tr(
"Copy after fee"),
this);
96 QAction *clipboardBytesAction =
new QAction(tr(
"Copy bytes"),
this);
97 QAction *clipboardLowOutputAction =
new QAction(tr(
"Copy dust"),
this);
98 QAction *clipboardChangeAction =
new QAction(tr(
"Copy change"),
this);
106 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
107 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
108 ui->labelCoinControlFee->addAction(clipboardFeeAction);
109 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
110 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
111 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
112 ui->labelCoinControlChange->addAction(clipboardChangeAction);
116 if (!settings.contains(
"fFeeSectionMinimized"))
117 settings.setValue(
"fFeeSectionMinimized",
true);
118 if (!settings.contains(
"nFeeRadio") && settings.contains(
"nTransactionFee") && settings.value(
"nTransactionFee").toLongLong() > 0)
119 settings.setValue(
"nFeeRadio", 1);
120 if (!settings.contains(
"nFeeRadio"))
121 settings.setValue(
"nFeeRadio", 0);
122 if (!settings.contains(
"nSmartFeeSliderPosition"))
123 settings.setValue(
"nSmartFeeSliderPosition", 0);
124 if (!settings.contains(
"nTransactionFee"))
126 ui->groupFee->setId(
ui->radioSmartFee, 0);
127 ui->groupFee->setId(
ui->radioCustomFee, 1);
128 ui->groupFee->button((
int)std::max(0, std::min(1, settings.value(
"nFeeRadio").toInt())))->setChecked(
true);
129 ui->customFee->SetAllowEmpty(
false);
130 ui->customFee->setValue(settings.value(
"nTransactionFee").toLongLong());
145 this->
model = _model;
149 for(
int i = 0; i <
ui->entries->count(); ++i)
182 ui->customFee->SetMinValue(requiredFee);
183 if (
ui->customFee->value() < requiredFee) {
184 ui->customFee->setValue(requiredFee);
186 ui->customFee->setSingleStep(requiredFee);
191 ui->optInRBF->setCheckState(Qt::Checked);
194 ui->sendButton->setText(tr(
"Cr&eate Unsigned"));
195 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));
200 if (settings.value(
"nSmartFeeSliderPosition").toInt() != 0) {
203 int nConfirmTarget = 25 - settings.value(
"nSmartFeeSliderPosition").toInt();
204 settings.setValue(
"nConfTarget", nConfirmTarget);
205 settings.remove(
"nSmartFeeSliderPosition");
207 if (settings.value(
"nConfTarget").toInt() == 0)
218 settings.setValue(
"nFeeRadio",
ui->groupFee->checkedId());
220 settings.setValue(
"nTransactionFee", (qint64)
ui->customFee->value());
227 QList<SendCoinsRecipient> recipients;
230 for(
int i = 0; i <
ui->entries->count(); ++i)
237 recipients.append(entry->
getValue());
241 ui->scrollArea->ensureWidgetVisible(entry);
247 if(!valid || recipients.isEmpty())
279 QStringList formatted;
289 QString address = rcp.address;
291 QString recipientElement;
294 if(rcp.label.length() > 0)
297 recipientElement.append(QString(
" (%1)").arg(address));
301 recipientElement.append(tr(
"%1 to %2").arg(amount, address));
304 formatted.append(recipientElement);
308 question_string.append(tr(
"Do you want to draft this transaction?"));
310 question_string.append(tr(
"Are you sure you want to send?"));
313 question_string.append(
"<br /><span style='font-size:10pt;'>");
315 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));
317 question_string.append(tr(
"Please, review your transaction."));
319 question_string.append(
"</span>%1");
324 question_string.append(
"<hr /><b>");
325 question_string.append(tr(
"Transaction fee"));
326 question_string.append(
"</b>");
329 question_string.append(
" (" + QString::number((
double)
m_current_transaction->getTransactionSize() / 1000) +
" kB): ");
332 question_string.append(
"<span style='color:#aa0000; font-weight:bold;'>");
334 question_string.append(
"</span><br />");
337 question_string.append(
"<span style='font-size:10pt; font-weight:normal;'>");
338 if (
ui->optInRBF->isChecked()) {
339 question_string.append(tr(
"You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
341 question_string.append(tr(
"Not signalling Replace-By-Fee, BIP-125."));
343 question_string.append(
"</span>");
347 question_string.append(
"<hr />");
349 QStringList alternativeUnits;
355 question_string.append(QString(
"<b>%1</b>: <b>%2</b>").arg(tr(
"Total Amount"))
357 question_string.append(QString(
"<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
358 .arg(alternativeUnits.join(
" " + tr(
"or") +
" ")));
360 if (formatted.size() > 1) {
361 question_string = question_string.arg(
"");
362 informative_text = tr(
"To review recipient list click \"Show Details...\"");
363 detailed_text = formatted.join(
"\n\n");
365 question_string = question_string.arg(
"<br /><br />" + formatted.at(0));
376 QString question_string, informative_text, detailed_text;
377 if (!
PrepareSendText(question_string, informative_text, detailed_text))
return;
383 confirmationDialog.
exec();
384 QMessageBox::StandardButton retval =
static_cast<QMessageBox::StandardButton
>(confirmationDialog.result());
386 if(retval != QMessageBox::Yes)
392 bool send_failure =
false;
396 bool complete =
false;
405 msgBox.setText(
"Unsigned Transaction");
406 msgBox.setInformativeText(
"The PSBT has been copied to the clipboard. You can also save it.");
407 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
408 msgBox.setDefaultButton(QMessageBox::Discard);
409 switch (msgBox.exec()) {
410 case QMessageBox::Save: {
411 QString selectedFilter;
412 QString fileNameSuggestion =
"";
416 fileNameSuggestion.append(
" - ");
418 QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
420 fileNameSuggestion.append(labelOrAddress +
"-" + amount);
423 fileNameSuggestion.append(
".psbt");
425 tr(
"Save Transaction Data"), fileNameSuggestion,
426 tr(
"Partially Signed Transaction (Binary) (*.psbt)"), &selectedFilter);
427 if (filename.isEmpty()) {
430 std::ofstream out(filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary);
436 case QMessageBox::Discard:
468 ui->checkBoxCoinControlChange->setChecked(
false);
469 ui->lineEditCoinControlChange->clear();
473 while(
ui->entries->count())
475 ui->entries->takeAt(0)->widget()->deleteLater();
496 ui->entries->addWidget(entry);
505 ui->scrollAreaWidgetContents->resize(
ui->scrollAreaWidgetContents->sizeHint());
506 qApp->processEvents();
507 QScrollBar* bar =
ui->scrollArea->verticalScrollBar();
509 bar->setSliderPosition(bar->maximum());
526 if (
ui->entries->count() == 1)
529 entry->deleteLater();
536 for(
int i = 0; i <
ui->entries->count(); ++i)
544 QWidget::setTabOrder(prev,
ui->sendButton);
545 QWidget::setTabOrder(
ui->sendButton,
ui->clearButton);
546 QWidget::setTabOrder(
ui->clearButton,
ui->addButton);
547 return ui->addButton;
554 if(
ui->entries->count() == 1)
577 if(
ui->entries->count() == 1)
609 ui->labelBalanceName->setText(tr(
"Watch-only balance:"));
624 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
630 switch(sendCoinsReturn.
status)
633 msgParams.first = tr(
"The recipient address is not valid. Please recheck.");
636 msgParams.first = tr(
"The amount to pay must be larger than 0.");
639 msgParams.first = tr(
"The amount exceeds your balance.");
642 msgParams.first = tr(
"The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
645 msgParams.first = tr(
"Duplicate address found: addresses should only be used once each.");
648 msgParams.first = tr(
"Transaction creation failed!");
655 msgParams.first = tr(
"Payment request expired.");
664 Q_EMIT
message(tr(
"Send Coins"), msgParams.first, msgParams.second);
669 ui->labelFeeMinimized->setVisible(fMinimize);
670 ui->buttonChooseFee ->setVisible(fMinimize);
671 ui->buttonMinimizeFee->setVisible(!fMinimize);
672 ui->frameFeeSelection->setVisible(!fMinimize);
673 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
695 for (
int i = 0; i <
ui->entries->count(); ++i) {
697 if (e && !e->isHidden() && e != entry) {
712 ui->confTargetSelector ->setEnabled(
ui->radioSmartFee->isChecked());
713 ui->labelSmartFee ->setEnabled(
ui->radioSmartFee->isChecked());
714 ui->labelSmartFee2 ->setEnabled(
ui->radioSmartFee->isChecked());
715 ui->labelSmartFee3 ->setEnabled(
ui->radioSmartFee->isChecked());
716 ui->labelFeeEstimation ->setEnabled(
ui->radioSmartFee->isChecked());
717 ui->labelCustomFeeWarning ->setEnabled(
ui->radioCustomFee->isChecked());
718 ui->labelCustomPerKilobyte ->setEnabled(
ui->radioCustomFee->isChecked());
719 ui->customFee ->setEnabled(
ui->radioCustomFee->isChecked());
727 if (
ui->radioSmartFee->isChecked())
728 ui->labelFeeMinimized->setText(
ui->labelSmartFee->text());
736 if (
ui->radioCustomFee->isChecked()) {
768 ui->labelSmartFee2->show();
769 ui->labelFeeEstimation->setText(
"");
770 ui->fallbackFeeWarningLabel->setVisible(
true);
771 int lightness =
ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
772 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
773 ui->fallbackFeeWarningLabel->setStyleSheet(
"QLabel { color: " + warning_colour.name() +
"; }");
774 ui->fallbackFeeWarningLabel->setIndent(
GUIUtil::TextWidth(QFontMetrics(
ui->fallbackFeeWarningLabel->font()),
"x"));
778 ui->labelSmartFee2->hide();
779 ui->labelFeeEstimation->setText(tr(
"Estimated to begin confirmation within %n block(s).",
"", returned_target));
780 ui->fallbackFeeWarningLabel->setVisible(
false);
831 ui->frameCoinControl->setVisible(checked);
833 if (!checked &&
model)
850 if (state == Qt::Unchecked)
853 ui->labelCoinControlChangeLabel->clear();
859 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
869 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:red;}");
875 ui->labelCoinControlChangeLabel->setText(
"");
879 ui->labelCoinControlChangeLabel->setText(tr(
"Warning: Invalid Bitcoin address"));
884 ui->labelCoinControlChangeLabel->setText(tr(
"Warning: Unknown change address"));
887 QMessageBox::StandardButton btnRetVal = QMessageBox::question(
this, tr(
"Confirm custom change address"), tr(
"The address you selected for change is not part of this wallet. Any or all funds in your wallet may be sent to this address. Are you sure?"),
888 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
890 if(btnRetVal == QMessageBox::Yes)
894 ui->lineEditCoinControlChange->setText(
"");
895 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:black;}");
896 ui->labelCoinControlChangeLabel->setText(
"");
901 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:black;}");
905 if (!associatedLabel.isEmpty())
906 ui->labelCoinControlChangeLabel->setText(associatedLabel);
908 ui->labelCoinControlChangeLabel->setText(tr(
"(no label)"));
928 for(
int i = 0; i <
ui->entries->count(); ++i)
931 if(entry && !entry->isHidden())
946 ui->labelCoinControlAutomaticallySelected->hide();
947 ui->widgetCoinControl->show();
952 ui->labelCoinControlAutomaticallySelected->show();
953 ui->widgetCoinControl->hide();
954 ui->labelCoinControlInsuffFunds->hide();
959 : QMessageBox(parent), secDelay(_secDelay), confirmButtonText(_confirmButtonText)
961 setIcon(QMessageBox::Question);
962 setWindowTitle(title);
964 setInformativeText(informative_text);
965 setDetailedText(detailed_text);
966 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
967 setDefaultButton(QMessageBox::Cancel);
969 if (confirmButtonText.isEmpty()) {
980 return QMessageBox::exec();
virtual bool privateKeysDisabled()=0
CAmount GetFeePerK() const
Return the fee in satoshis for a size of 1000 bytes.
void removeEntry(SendCoinsEntry *entry)
void updateNumberOfBlocks(int count, const QDateTime &blockDate, double nVerificationProgress, bool headers, SynchronizationState sync_state)
void setValue(const SendCoinsRecipient &value)
SynchronizationState
Current sync state passed to tip changed callbacks.
bool fAllowWatchOnly
Includes watch only addresses which are solvable.
void updateFeeMinimizedLabel()
constexpr CAmount DEFAULT_PAY_TX_FEE
-paytxfee default
void on_buttonChooseFee_clicked()
SendCoinsRecipient getValue()
static const std::array< int, 9 > confTargets
int TextWidth(const QFontMetrics &fm, const QString &text)
Returns the distance in pixels appropriate for drawing a subsequent character after text...
UnlockContext requestUnlock()
void coinControlClipboardQuantity()
void coinControlClipboardAfterFee()
void setAddress(const QString &address)
virtual CAmount getDefaultMaxTxFee()=0
Get max tx fee.
bool IsValidDestination(const CTxDestination &dest)
Check whether a CTxDestination is a CNoDestination.
#define SEND_CONFIRM_DELAY
interfaces::Wallet & wallet() const
SendCoinsReturn sendCoins(WalletModelTransaction &transaction)
void coinControlFeaturesChanged(bool)
void updateCoinControlState(CCoinControl &ctrl)
QString HtmlEscape(const QString &str, bool fMultiLine)
bool handlePaymentRequest(const SendCoinsRecipient &recipient)
QString getWalletName() const
A version of CTransaction with the PSBT format.
Double ended buffer combining vector and stream-like interfaces.
void numBlocksChanged(int count, const QDateTime &blockDate, double nVerificationProgress, bool header, SynchronizationState sync_state)
AddressTableModel * getAddressTableModel()
static void updateLabels(CCoinControl &m_coin_control, WalletModel *, QDialog *)
virtual bool isSpendable(const CTxDestination &dest)=0
Return whether wallet has private key.
A single entry in the dialog for sending bitcoins.
void coinControlFeatureChanged(bool)
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
std::unique_ptr< WalletModelTransaction > m_current_transaction
int64_t CAmount
Amount in satoshis (Can be negative)
static QList< CAmount > payAmounts
QWidget * setupTabChain(QWidget *prev)
Set up the tab chain manually, as Qt messes up the tab chain by default in some cases (issue https://...
std::string EncodeBase64(Span< const unsigned char > input)
Optional< bool > m_signal_bip125_rbf
Override the wallet's m_signal_rbf if set.
void on_sendButton_clicked()
SendCoinsEntry * addEntry()
bool validate(interfaces::Node &node)
void setupAddressWidget(QValidatedLineEdit *widget, QWidget *parent)
CAmount watch_only_balance
void setBalance(const interfaces::WalletBalances &balances)
void setAddress(const QString &address)
void coinControlClipboardChange()
Collection of wallet balances.
void useAvailableBalance(SendCoinsEntry *entry)
void setClientModel(ClientModel *clientModel)
void setClipboard(const QString &str)
static QString formatHtmlWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as HTML string (with unit)
ClientModel * clientModel
static secp256k1_context * ctx
SendCoinsReturn prepareTransaction(WalletModelTransaction &transaction, const CCoinControl &coinControl)
virtual CAmount getMinimumFee(unsigned int tx_bytes, const CCoinControl &coin_control, int *returned_target, FeeReason *reason)=0
Get minimum fee.
Dialog for sending bitcoins.
static QString formatWithUnit(int unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
Optional< unsigned int > m_confirm_target
Override the default confirmation target if set.
void coinControlChangeEdited(const QString &)
void removeEntry(SendCoinsEntry *entry)
void displayUnitChanged(int unit)
Model for Bitcoin network client.
void coinControlUpdateLabels()
void setModel(WalletModel *model)
int getConfTargetForIndex(int index)
SendCoinsDialog(const PlatformStyle *platformStyle, QWidget *parent=nullptr)
SendConfirmationDialog(const QString &title, const QString &text, const QString &informative_text="", const QString &detailed_text="", int secDelay=SEND_CONFIRM_DELAY, const QString &confirmText="", QWidget *parent=nullptr)
virtual TransactionError fillPSBT(int sighash_type, bool sign, bool bip32derivs, PartiallySignedTransaction &psbtx, bool &complete, size_t *n_signed)=0
Fill PSBT.
int getDisplayUnit() const
void checkSubtractFeeFromAmount()
bool isClear()
Return whether the entry is still empty and unedited.
void minimizeFeeSection(bool fMinimize)
void coinControlClipboardLowOutput()
void updateFeeSectionControls()
static QList< Unit > availableUnits()
Get list of units, for drop-down box.
CTxDestination DecodeDestination(const std::string &str)
void subtractFeeFromAmountChanged()
static bool fSubtractFeeFromAmount
void updateSmartFeeLabel()
bool PrepareSendText(QString &question_string, QString &informative_text, QString &detailed_text)
QString labelForAddress(const QString &address) const
Look up label for address in address book, if not found return empty string.
interfaces::Node & node() const
int getIndexForConfTarget(int target)
void updateTabsAndLabels()
const CChainParams & Params()
Return the currently selected parameters.
Interface to Bitcoin wallet from Qt view code.
static const int PROTOCOL_VERSION
network protocol versioning
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 setAmount(const CAmount &amount)
void setModel(WalletModel *model)
void processSendCoinsReturn(const WalletModel::SendCoinsReturn &sendCoinsReturn, const QString &msgArg=QString())
Fee rate in satoshis per kilobyte: CAmount / kB.
void coinControlClipboardBytes()
void useAvailableBalance(SendCoinsEntry *entry)
A mutable version of CTransaction.
virtual unsigned int getConfirmTarget()=0
Get tx confirm target.
bool fSubtractFeeFromAmount
const PlatformStyle * platformStyle
QString formatNiceTimeOffset(qint64 secs)
std::unique_ptr< CCoinControl > m_coin_control
Optional< CFeeRate > m_feerate
Override the wallet's m_pay_tx_fee if set.
void coinControlClipboardAmount()
void on_buttonMinimizeFee_clicked()
virtual CAmount getAvailableBalance(const CCoinControl &coin_control)=0
Get available balance.
virtual WalletBalances getBalances()=0
Get balances.
boost::variant< CNoDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessUnknown > CTxDestination
A txout script template with a specific destination.
void pasteEntry(const SendCoinsRecipient &rv)
bool getCoinControlFeatures() const
QAbstractButton * yesButton
virtual CAmount getRequiredFee(unsigned int tx_bytes)=0
Get required fee.
void message(const QString &title, const QString &message, unsigned int style)
void coinsSent(const uint256 &txid)
bool fNewRecipientAllowed
void balanceChanged(const interfaces::WalletBalances &balances)
void coinControlButtonClicked()
void coinControlClipboardFee()
OptionsModel * getOptionsModel()
Predefined combinations for certain default usage cases.
void coinControlChangeChecked(int)