5 #if defined(HAVE_CONFIG_H)
10 #include <qt/forms/ui_sendcoinsdialog.h>
27 #include <validation.h>
37 #include <QFontMetrics>
40 #include <QTextDocument>
45 static constexpr std::array
confTargets{2, 4, 6, 12, 24, 48, 144, 504, 1008};
47 if (index+1 >
static_cast<int>(
confTargets.size())) {
56 for (
unsigned int i = 0; i <
confTargets.size(); i++) {
70 fNewRecipientAllowed(true),
72 platformStyle(_platformStyle)
77 ui->addButton->setIcon(QIcon());
78 ui->clearButton->setIcon(QIcon());
79 ui->sendButton->setIcon(QIcon());
99 QAction *clipboardQuantityAction =
new QAction(tr(
"Copy quantity"),
this);
100 QAction *clipboardAmountAction =
new QAction(tr(
"Copy amount"),
this);
101 QAction *clipboardFeeAction =
new QAction(tr(
"Copy fee"),
this);
102 QAction *clipboardAfterFeeAction =
new QAction(tr(
"Copy after fee"),
this);
103 QAction *clipboardBytesAction =
new QAction(tr(
"Copy bytes"),
this);
104 QAction *clipboardLowOutputAction =
new QAction(tr(
"Copy dust"),
this);
105 QAction *clipboardChangeAction =
new QAction(tr(
"Copy change"),
this);
113 ui->labelCoinControlQuantity->addAction(clipboardQuantityAction);
114 ui->labelCoinControlAmount->addAction(clipboardAmountAction);
115 ui->labelCoinControlFee->addAction(clipboardFeeAction);
116 ui->labelCoinControlAfterFee->addAction(clipboardAfterFeeAction);
117 ui->labelCoinControlBytes->addAction(clipboardBytesAction);
118 ui->labelCoinControlLowOutput->addAction(clipboardLowOutputAction);
119 ui->labelCoinControlChange->addAction(clipboardChangeAction);
123 if (!settings.contains(
"fFeeSectionMinimized"))
124 settings.setValue(
"fFeeSectionMinimized",
true);
125 if (!settings.contains(
"nFeeRadio") && settings.contains(
"nTransactionFee") && settings.value(
"nTransactionFee").toLongLong() > 0)
126 settings.setValue(
"nFeeRadio", 1);
127 if (!settings.contains(
"nFeeRadio"))
128 settings.setValue(
"nFeeRadio", 0);
129 if (!settings.contains(
"nSmartFeeSliderPosition"))
130 settings.setValue(
"nSmartFeeSliderPosition", 0);
131 if (!settings.contains(
"nTransactionFee"))
133 ui->groupFee->setId(
ui->radioSmartFee, 0);
134 ui->groupFee->setId(
ui->radioCustomFee, 1);
135 ui->groupFee->button((
int)std::max(0, std::min(1, settings.value(
"nFeeRadio").toInt())))->setChecked(
true);
136 ui->customFee->SetAllowEmpty(
false);
137 ui->customFee->setValue(settings.value(
"nTransactionFee").toLongLong());
154 this->
model = _model;
158 for(
int i = 0; i <
ui->entries->count(); ++i)
160 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(
ui->entries->itemAt(i)->widget());
186 #if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
198 ui->customFee->SetMinValue(requiredFee);
199 if (
ui->customFee->value() < requiredFee) {
200 ui->customFee->setValue(requiredFee);
202 ui->customFee->setSingleStep(requiredFee);
207 ui->optInRBF->setCheckState(Qt::Checked);
211 ui->sendButton->setText(tr(
"Sign on device"));
213 ui->sendButton->setEnabled(
true);
214 ui->sendButton->setToolTip(tr(
"Connect your hardware wallet first."));
216 ui->sendButton->setEnabled(
false);
218 ui->sendButton->setToolTip(tr(
"Set external signer script path in Options -> Wallet"));
221 ui->sendButton->setText(tr(
"Cr&eate Unsigned"));
222 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));
227 if (settings.value(
"nSmartFeeSliderPosition").toInt() != 0) {
230 int nConfirmTarget = 25 - settings.value(
"nSmartFeeSliderPosition").toInt();
231 settings.setValue(
"nConfTarget", nConfirmTarget);
232 settings.remove(
"nSmartFeeSliderPosition");
234 if (settings.value(
"nConfTarget").toInt() == 0)
245 settings.setValue(
"nFeeRadio",
ui->groupFee->checkedId());
247 settings.setValue(
"nTransactionFee", (qint64)
ui->customFee->value());
254 QList<SendCoinsRecipient> recipients;
257 for(
int i = 0; i <
ui->entries->count(); ++i)
259 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(
ui->entries->itemAt(i)->widget());
264 recipients.append(entry->
getValue());
268 ui->scrollArea->ensureWidgetVisible(entry);
274 if(!valid || recipients.isEmpty())
306 QStringList formatted;
316 QString address = rcp.address;
318 QString recipientElement;
321 if(rcp.label.length() > 0)
324 recipientElement.append(QString(
" (%1)").arg(address));
328 recipientElement.append(tr(
"%1 to %2").arg(amount, address));
331 formatted.append(recipientElement);
336 question_string.append(tr(
"Do you want to create this transaction?"));
337 question_string.append(
"<br /><span style='font-size:10pt;'>");
342 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));
347 question_string.append(tr(
"Please, review your transaction. You can create and send this transaction or create 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));
350 question_string.append(tr(
"Please, review your transaction."));
352 question_string.append(
"</span>%1");
357 question_string.append(
"<hr /><b>");
358 question_string.append(tr(
"Transaction fee"));
359 question_string.append(
"</b>");
362 question_string.append(
" (" + QString::number((
double)
m_current_transaction->getTransactionSize() / 1000) +
" kB): ");
365 question_string.append(
"<span style='color:#aa0000; font-weight:bold;'>");
367 question_string.append(
"</span><br />");
370 question_string.append(
"<span style='font-size:10pt; font-weight:normal;'>");
371 if (
ui->optInRBF->isChecked()) {
372 question_string.append(tr(
"You can increase the fee later (signals Replace-By-Fee, BIP-125)."));
374 question_string.append(tr(
"Not signalling Replace-By-Fee, BIP-125."));
376 question_string.append(
"</span>");
380 question_string.append(
"<hr />");
382 QStringList alternativeUnits;
388 question_string.append(QString(
"<b>%1</b>: <b>%2</b>").arg(tr(
"Total Amount"))
390 question_string.append(QString(
"<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")
391 .arg(alternativeUnits.join(
" " + tr(
"or") +
" ")));
393 if (formatted.size() > 1) {
394 question_string = question_string.arg(
"");
395 informative_text = tr(
"To review recipient list click \"Show Details…\"");
396 detailed_text = formatted.join(
"\n\n");
398 question_string = question_string.arg(
"<br /><br />" + formatted.at(0));
409 QString question_string, informative_text, detailed_text;
410 if (!
PrepareSendText(question_string, informative_text, detailed_text))
return;
413 const QString confirmation = tr(
"Confirm send coins");
415 confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);
417 const auto retval =
static_cast<QMessageBox::StandardButton
>(confirmationDialog->exec());
419 if(retval != QMessageBox::Yes && retval != QMessageBox::Save)
425 bool send_failure =
false;
426 if (retval == QMessageBox::Save) {
429 bool complete =
false;
438 }
catch (
const std::runtime_error& e) {
439 QMessageBox::critical(
nullptr, tr(
"Sign failed"), e.what());
445 QMessageBox::critical(
nullptr, tr(
"External signer not found"),
"External signer not found");
451 QMessageBox::critical(
nullptr, tr(
"External signer failure"),
"External signer failure");
489 msgBox.setText(
"Unsigned Transaction");
490 msgBox.setInformativeText(
"The PSBT has been copied to the clipboard. You can also save it.");
491 msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);
492 msgBox.setDefaultButton(QMessageBox::Discard);
493 switch (msgBox.exec()) {
494 case QMessageBox::Save: {
495 QString selectedFilter;
496 QString fileNameSuggestion =
"";
500 fileNameSuggestion.append(
" - ");
502 QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;
504 fileNameSuggestion.append(labelOrAddress +
"-" + amount);
507 fileNameSuggestion.append(
".psbt");
509 tr(
"Save Transaction Data"), fileNameSuggestion,
511 tr(
"Partially Signed Transaction (Binary)") + QLatin1String(
" (*.psbt)"), &selectedFilter);
512 if (filename.isEmpty()) {
515 std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};
521 case QMessageBox::Discard:
554 ui->checkBoxCoinControlChange->setChecked(
false);
555 ui->lineEditCoinControlChange->clear();
559 while(
ui->entries->count())
561 ui->entries->takeAt(0)->widget()->deleteLater();
582 ui->entries->addWidget(entry);
591 ui->scrollAreaWidgetContents->resize(
ui->scrollAreaWidgetContents->sizeHint());
592 qApp->processEvents();
593 QScrollBar* bar =
ui->scrollArea->verticalScrollBar();
595 bar->setSliderPosition(bar->maximum());
612 if (
ui->entries->count() == 1)
615 entry->deleteLater();
622 for(
int i = 0; i <
ui->entries->count(); ++i)
624 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(
ui->entries->itemAt(i)->widget());
630 QWidget::setTabOrder(prev,
ui->sendButton);
631 QWidget::setTabOrder(
ui->sendButton,
ui->clearButton);
632 QWidget::setTabOrder(
ui->clearButton,
ui->addButton);
633 return ui->addButton;
640 if(
ui->entries->count() == 1)
642 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(
ui->entries->itemAt(0)->widget());
663 if(
ui->entries->count() == 1)
665 SendCoinsEntry *first = qobject_cast<SendCoinsEntry*>(
ui->entries->itemAt(0)->widget());
694 ui->labelBalanceName->setText(tr(
"External balance:"));
697 ui->labelBalanceName->setText(tr(
"Watch-only balance:"));
712 QPair<QString, CClientUIInterface::MessageBoxFlags> msgParams;
718 switch(sendCoinsReturn.
status)
721 msgParams.first = tr(
"The recipient address is not valid. Please recheck.");
724 msgParams.first = tr(
"The amount to pay must be larger than 0.");
727 msgParams.first = tr(
"The amount exceeds your balance.");
730 msgParams.first = tr(
"The total exceeds your balance when the %1 transaction fee is included.").arg(msgArg);
733 msgParams.first = tr(
"Duplicate address found: addresses should only be used once each.");
736 msgParams.first = tr(
"Transaction creation failed!");
743 msgParams.first = tr(
"Payment request expired.");
752 Q_EMIT
message(tr(
"Send Coins"), msgParams.first, msgParams.second);
757 ui->labelFeeMinimized->setVisible(fMinimize);
758 ui->buttonChooseFee ->setVisible(fMinimize);
759 ui->buttonMinimizeFee->setVisible(!fMinimize);
760 ui->frameFeeSelection->setVisible(!fMinimize);
761 ui->horizontalLayoutSmartFee->setContentsMargins(0, (fMinimize ? 0 : 6), 0, 0);
783 for (
int i = 0; i <
ui->entries->count(); ++i) {
784 SendCoinsEntry* e = qobject_cast<SendCoinsEntry*>(
ui->entries->itemAt(i)->widget());
785 if (e && !e->isHidden() && e != entry) {
800 ui->confTargetSelector ->setEnabled(
ui->radioSmartFee->isChecked());
801 ui->labelSmartFee ->setEnabled(
ui->radioSmartFee->isChecked());
802 ui->labelSmartFee2 ->setEnabled(
ui->radioSmartFee->isChecked());
803 ui->labelSmartFee3 ->setEnabled(
ui->radioSmartFee->isChecked());
804 ui->labelFeeEstimation ->setEnabled(
ui->radioSmartFee->isChecked());
805 ui->labelCustomFeeWarning ->setEnabled(
ui->radioCustomFee->isChecked());
806 ui->labelCustomPerKilobyte ->setEnabled(
ui->radioCustomFee->isChecked());
807 ui->customFee ->setEnabled(
ui->radioCustomFee->isChecked());
815 if (
ui->radioSmartFee->isChecked())
816 ui->labelFeeMinimized->setText(
ui->labelSmartFee->text());
824 if (
ui->radioCustomFee->isChecked()) {
856 ui->labelSmartFee2->show();
857 ui->labelFeeEstimation->setText(
"");
858 ui->fallbackFeeWarningLabel->setVisible(
true);
859 int lightness =
ui->fallbackFeeWarningLabel->palette().color(QPalette::WindowText).lightness();
860 QColor warning_colour(255 - (lightness / 5), 176 - (lightness / 3), 48 - (lightness / 14));
861 ui->fallbackFeeWarningLabel->setStyleSheet(
"QLabel { color: " + warning_colour.name() +
"; }");
862 ui->fallbackFeeWarningLabel->setIndent(
GUIUtil::TextWidth(QFontMetrics(
ui->fallbackFeeWarningLabel->font()),
"x"));
866 ui->labelSmartFee2->hide();
867 ui->labelFeeEstimation->setText(tr(
"Estimated to begin confirmation within %n block(s).",
"", returned_target));
868 ui->fallbackFeeWarningLabel->setVisible(
false);
919 ui->frameCoinControl->setVisible(checked);
921 if (!checked &&
model) {
939 if (state == Qt::Unchecked)
942 ui->labelCoinControlChangeLabel->clear();
948 ui->lineEditCoinControlChange->setEnabled((state == Qt::Checked));
958 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:red;}");
964 ui->labelCoinControlChangeLabel->setText(
"");
968 ui->labelCoinControlChangeLabel->setText(tr(
"Warning: Invalid Bitcoin address"));
973 ui->labelCoinControlChangeLabel->setText(tr(
"Warning: Unknown change address"));
976 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?"),
977 QMessageBox::Yes | QMessageBox::Cancel, QMessageBox::Cancel);
979 if(btnRetVal == QMessageBox::Yes)
983 ui->lineEditCoinControlChange->setText(
"");
984 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:black;}");
985 ui->labelCoinControlChangeLabel->setText(
"");
990 ui->labelCoinControlChangeLabel->setStyleSheet(
"QLabel{color:black;}");
994 if (!associatedLabel.isEmpty())
995 ui->labelCoinControlChangeLabel->setText(associatedLabel);
997 ui->labelCoinControlChangeLabel->setText(tr(
"(no label)"));
1017 for(
int i = 0; i <
ui->entries->count(); ++i)
1019 SendCoinsEntry *entry = qobject_cast<SendCoinsEntry*>(
ui->entries->itemAt(i)->widget());
1020 if(entry && !entry->isHidden())
1035 ui->labelCoinControlAutomaticallySelected->hide();
1036 ui->widgetCoinControl->show();
1041 ui->labelCoinControlAutomaticallySelected->show();
1042 ui->widgetCoinControl->hide();
1043 ui->labelCoinControlInsuffFunds->hide();
1048 : QMessageBox(parent), secDelay(_secDelay), m_enable_send(enable_send)
1050 setIcon(QMessageBox::Question);
1051 setWindowTitle(title);
1053 setInformativeText(informative_text);
1054 setDetailedText(detailed_text);
1055 setStandardButtons(QMessageBox::Yes | QMessageBox::Cancel);
1056 if (always_show_unsigned || !enable_send) addButton(QMessageBox::Save);
1057 setDefaultButton(QMessageBox::Cancel);
1071 return QMessageBox::exec();