Changeset View
Changeset View
Standalone View
Standalone View
src/qt/sendcoinsdialog.cpp
Show First 20 Lines • Show All 235 Lines • ▼ Show 20 Lines | SendCoinsDialog::~SendCoinsDialog() { | ||||
settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId()); | settings.setValue("nCustomFeeRadio", ui->groupCustomFee->checkedId()); | ||||
settings.setValue("nTransactionFee", | settings.setValue("nTransactionFee", | ||||
qint64(ui->customFee->value() / SATOSHI)); | qint64(ui->customFee->value() / SATOSHI)); | ||||
settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); | settings.setValue("fPayOnlyMinFee", ui->checkBoxMinimumFee->isChecked()); | ||||
delete ui; | delete ui; | ||||
} | } | ||||
void SendCoinsDialog::on_sendButton_clicked() { | bool SendCoinsDialog::PrepareSendText(QString &question_string, | ||||
if (!model || !model->getOptionsModel()) { | QString &informative_text, | ||||
return; | QString &detailed_text) { | ||||
} | |||||
QList<SendCoinsRecipient> recipients; | QList<SendCoinsRecipient> recipients; | ||||
bool valid = true; | bool valid = true; | ||||
for (int i = 0; i < ui->entries->count(); ++i) { | for (int i = 0; i < ui->entries->count(); ++i) { | ||||
SendCoinsEntry *entry = | SendCoinsEntry *entry = | ||||
qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget()); | qobject_cast<SendCoinsEntry *>(ui->entries->itemAt(i)->widget()); | ||||
if (entry) { | if (entry) { | ||||
if (entry->validate(model->node())) { | if (entry->validate(model->node())) { | ||||
recipients.append(entry->getValue()); | recipients.append(entry->getValue()); | ||||
} else if (valid) { | } else if (valid) { | ||||
ui->scrollArea->ensureWidgetVisible(entry); | ui->scrollArea->ensureWidgetVisible(entry); | ||||
valid = false; | valid = false; | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (!valid || recipients.isEmpty()) { | if (!valid || recipients.isEmpty()) { | ||||
return; | return false; | ||||
} | } | ||||
fNewRecipientAllowed = false; | fNewRecipientAllowed = false; | ||||
WalletModel::UnlockContext ctx(model->requestUnlock()); | WalletModel::UnlockContext ctx(model->requestUnlock()); | ||||
if (!ctx.isValid()) { | if (!ctx.isValid()) { | ||||
// Unlock wallet was cancelled | // Unlock wallet was cancelled | ||||
fNewRecipientAllowed = true; | fNewRecipientAllowed = true; | ||||
return; | return false; | ||||
} | } | ||||
// prepare transaction for getting txFee earlier | // prepare transaction for getting txFee earlier | ||||
WalletModelTransaction currentTransaction(recipients); | m_current_transaction = | ||||
std::make_unique<WalletModelTransaction>(recipients); | |||||
WalletModel::SendCoinsReturn prepareStatus; | WalletModel::SendCoinsReturn prepareStatus; | ||||
// Always use a CCoinControl instance, use the CoinControlDialog instance if | // Always use a CCoinControl instance, use the CoinControlDialog instance if | ||||
// CoinControl has been enabled | // CoinControl has been enabled | ||||
CCoinControl ctrl; | CCoinControl ctrl; | ||||
if (model->getOptionsModel()->getCoinControlFeatures()) { | if (model->getOptionsModel()->getCoinControlFeatures()) { | ||||
ctrl = *CoinControlDialog::coinControl(); | ctrl = *CoinControlDialog::coinControl(); | ||||
} | } | ||||
updateCoinControlState(ctrl); | updateCoinControlState(ctrl); | ||||
prepareStatus = model->prepareTransaction(currentTransaction, ctrl); | prepareStatus = model->prepareTransaction(*m_current_transaction, ctrl); | ||||
// process prepareStatus and on error generate message shown to user | // process prepareStatus and on error generate message shown to user | ||||
processSendCoinsReturn( | processSendCoinsReturn(prepareStatus, | ||||
prepareStatus, | BitcoinUnits::formatWithUnit( | ||||
BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), | model->getOptionsModel()->getDisplayUnit(), | ||||
currentTransaction.getTransactionFee())); | m_current_transaction->getTransactionFee())); | ||||
if (prepareStatus.status != WalletModel::OK) { | if (prepareStatus.status != WalletModel::OK) { | ||||
fNewRecipientAllowed = true; | fNewRecipientAllowed = true; | ||||
return; | return false; | ||||
} | } | ||||
Amount txFee = currentTransaction.getTransactionFee(); | Amount txFee = m_current_transaction->getTransactionFee(); | ||||
// Format confirmation message | |||||
QStringList formatted; | QStringList formatted; | ||||
for (const SendCoinsRecipient &rcp : currentTransaction.getRecipients()) { | for (const SendCoinsRecipient &rcp : | ||||
m_current_transaction->getRecipients()) { | |||||
// generate amount string with wallet name in case of multiwallet | // generate amount string with wallet name in case of multiwallet | ||||
QString amount = BitcoinUnits::formatWithUnit( | QString amount = BitcoinUnits::formatWithUnit( | ||||
model->getOptionsModel()->getDisplayUnit(), rcp.amount); | model->getOptionsModel()->getDisplayUnit(), rcp.amount); | ||||
if (model->isMultiwallet()) { | if (model->isMultiwallet()) { | ||||
amount.append( | amount.append( | ||||
tr(" from wallet '%1'") | tr(" from wallet '%1'") | ||||
.arg(GUIUtil::HtmlEscape(model->getWalletName()))); | .arg(GUIUtil::HtmlEscape(model->getWalletName()))); | ||||
} | } | ||||
Show All 27 Lines | #ifdef ENABLE_BIP70 | ||||
// unauthenticated payment request | // unauthenticated payment request | ||||
recipientElement.append(tr("%1 to %2").arg(amount, address)); | recipientElement.append(tr("%1 to %2").arg(amount, address)); | ||||
} | } | ||||
#endif | #endif | ||||
formatted.append(recipientElement); | formatted.append(recipientElement); | ||||
} | } | ||||
QString questionString; | |||||
if (model->wallet().privateKeysDisabled()) { | if (model->wallet().privateKeysDisabled()) { | ||||
questionString.append(tr("Do you want to draft this transaction?")); | question_string.append(tr("Do you want to draft this transaction?")); | ||||
} else { | } else { | ||||
questionString.append(tr("Are you sure you want to send?")); | question_string.append(tr("Are you sure you want to send?")); | ||||
} | } | ||||
questionString.append("<br /><span style='font-size:10pt;'>"); | question_string.append("<br /><span style='font-size:10pt;'>"); | ||||
if (model->wallet().privateKeysDisabled()) { | if (model->wallet().privateKeysDisabled()) { | ||||
questionString.append( | question_string.append( | ||||
tr("Please, review your transaction proposal. This will produce a " | tr("Please, review your transaction proposal. This will produce a " | ||||
"Partially Signed Bitcoin Transaction (PSBT) which you can copy " | "Partially Signed Bitcoin Transaction (PSBT) which you can copy " | ||||
"and then sign with e.g. an offline %1 wallet, or a " | "and then sign with e.g. an offline %1 wallet, or a " | ||||
"PSBT-compatible hardware wallet.") | "PSBT-compatible hardware wallet.") | ||||
.arg(PACKAGE_NAME)); | .arg(PACKAGE_NAME)); | ||||
} else { | } else { | ||||
questionString.append(tr("Please, review your transaction.")); | question_string.append(tr("Please, review your transaction.")); | ||||
} | } | ||||
questionString.append("</span>%1"); | question_string.append("</span>%1"); | ||||
if (txFee > Amount::zero()) { | if (txFee > Amount::zero()) { | ||||
// append fee string if a fee is required | // append fee string if a fee is required | ||||
questionString.append("<hr /><b>"); | question_string.append("<hr /><b>"); | ||||
questionString.append(tr("Transaction fee")); | question_string.append(tr("Transaction fee")); | ||||
questionString.append("</b>"); | question_string.append("</b>"); | ||||
// append transaction size | // append transaction size | ||||
questionString.append( | question_string.append( | ||||
" (" + | " (" + | ||||
QString::number((double)currentTransaction.getTransactionSize() / | QString::number( | ||||
1000) + | (double)m_current_transaction->getTransactionSize() / 1000) + | ||||
" kB): "); | " kB): "); | ||||
// append transaction fee value | // append transaction fee value | ||||
questionString.append( | question_string.append( | ||||
"<span style='color:#aa0000; font-weight:bold;'>"); | "<span style='color:#aa0000; font-weight:bold;'>"); | ||||
questionString.append(BitcoinUnits::formatHtmlWithUnit( | question_string.append(BitcoinUnits::formatHtmlWithUnit( | ||||
model->getOptionsModel()->getDisplayUnit(), txFee)); | model->getOptionsModel()->getDisplayUnit(), txFee)); | ||||
questionString.append("</span><br />"); | question_string.append("</span><br />"); | ||||
} | } | ||||
// add total amount in all subdivision units | // add total amount in all subdivision units | ||||
questionString.append("<hr />"); | question_string.append("<hr />"); | ||||
Amount totalAmount = currentTransaction.getTotalTransactionAmount() + txFee; | Amount totalAmount = | ||||
m_current_transaction->getTotalTransactionAmount() + txFee; | |||||
QStringList alternativeUnits; | QStringList alternativeUnits; | ||||
for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { | for (const BitcoinUnits::Unit u : BitcoinUnits::availableUnits()) { | ||||
if (u != model->getOptionsModel()->getDisplayUnit()) { | if (u != model->getOptionsModel()->getDisplayUnit()) { | ||||
alternativeUnits.append( | alternativeUnits.append( | ||||
BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); | BitcoinUnits::formatHtmlWithUnit(u, totalAmount)); | ||||
} | } | ||||
} | } | ||||
questionString.append( | question_string.append( | ||||
QString("<b>%1</b>: <b>%2</b>") | QString("<b>%1</b>: <b>%2</b>") | ||||
.arg(tr("Total Amount")) | .arg(tr("Total Amount")) | ||||
.arg(BitcoinUnits::formatHtmlWithUnit( | .arg(BitcoinUnits::formatHtmlWithUnit( | ||||
model->getOptionsModel()->getDisplayUnit(), totalAmount))); | model->getOptionsModel()->getDisplayUnit(), totalAmount))); | ||||
questionString.append( | question_string.append( | ||||
QString("<br /><span style='font-size:10pt; " | QString("<br /><span style='font-size:10pt; " | ||||
"font-weight:normal;'>(=%1)</span>") | "font-weight:normal;'>(=%1)</span>") | ||||
.arg(alternativeUnits.join(" " + tr("or") + " "))); | .arg(alternativeUnits.join(" " + tr("or") + " "))); | ||||
QString informative_text; | |||||
QString detailed_text; | |||||
if (formatted.size() > 1) { | if (formatted.size() > 1) { | ||||
questionString = questionString.arg(""); | question_string = question_string.arg(""); | ||||
informative_text = | informative_text = | ||||
tr("To review recipient list click \"Show Details...\""); | tr("To review recipient list click \"Show Details...\""); | ||||
detailed_text = formatted.join("\n\n"); | detailed_text = formatted.join("\n\n"); | ||||
} else { | } else { | ||||
questionString = questionString.arg("<br /><br />" + formatted.at(0)); | question_string = question_string.arg("<br /><br />" + formatted.at(0)); | ||||
} | |||||
return true; | |||||
} | |||||
void SendCoinsDialog::on_sendButton_clicked() { | |||||
if (!model || !model->getOptionsModel()) { | |||||
return; | |||||
} | |||||
QString question_string, informative_text, detailed_text; | |||||
if (!PrepareSendText(question_string, informative_text, detailed_text)) { | |||||
return; | |||||
} | } | ||||
assert(m_current_transaction); | |||||
const QString confirmation = model->wallet().privateKeysDisabled() | const QString confirmation = model->wallet().privateKeysDisabled() | ||||
? tr("Confirm transaction proposal") | ? tr("Confirm transaction proposal") | ||||
: tr("Confirm send coins"); | : tr("Confirm send coins"); | ||||
const QString confirmButtonText = model->wallet().privateKeysDisabled() | const QString confirmButtonText = model->wallet().privateKeysDisabled() | ||||
? tr("Copy PSBT to clipboard") | ? tr("Copy PSBT to clipboard") | ||||
: tr("Send"); | : tr("Send"); | ||||
SendConfirmationDialog confirmationDialog( | SendConfirmationDialog confirmationDialog( | ||||
confirmation, questionString, informative_text, detailed_text, | confirmation, question_string, informative_text, detailed_text, | ||||
SEND_CONFIRM_DELAY, confirmButtonText, this); | SEND_CONFIRM_DELAY, confirmButtonText, this); | ||||
confirmationDialog.exec(); | confirmationDialog.exec(); | ||||
QMessageBox::StandardButton retval = | QMessageBox::StandardButton retval = | ||||
static_cast<QMessageBox::StandardButton>(confirmationDialog.result()); | static_cast<QMessageBox::StandardButton>(confirmationDialog.result()); | ||||
if (retval != QMessageBox::Yes) { | if (retval != QMessageBox::Yes) { | ||||
fNewRecipientAllowed = true; | fNewRecipientAllowed = true; | ||||
return; | return; | ||||
} | } | ||||
bool send_failure = false; | bool send_failure = false; | ||||
if (model->wallet().privateKeysDisabled()) { | if (model->wallet().privateKeysDisabled()) { | ||||
CMutableTransaction mtx = | CMutableTransaction mtx = | ||||
CMutableTransaction{*(currentTransaction.getWtx())}; | CMutableTransaction{*(m_current_transaction->getWtx())}; | ||||
PartiallySignedTransaction psbtx(mtx); | PartiallySignedTransaction psbtx(mtx); | ||||
bool complete = false; | bool complete = false; | ||||
const TransactionError err = model->wallet().fillPSBT( | const TransactionError err = model->wallet().fillPSBT( | ||||
SigHashType().withForkId(), false /* sign */, | SigHashType().withForkId(), false /* sign */, | ||||
true /* bip32derivs */, psbtx, complete); | true /* bip32derivs */, psbtx, complete); | ||||
assert(!complete); | assert(!complete); | ||||
assert(err == TransactionError::OK); | assert(err == TransactionError::OK); | ||||
// Serialize the PSBT | // Serialize the PSBT | ||||
CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); | ||||
ssTx << psbtx; | ssTx << psbtx; | ||||
GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); | GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str()); | ||||
Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", | Q_EMIT message(tr("PSBT copied"), "Copied to clipboard", | ||||
CClientUIInterface::MSG_INFORMATION); | CClientUIInterface::MSG_INFORMATION); | ||||
} else { | } else { | ||||
// now send the prepared transaction | // now send the prepared transaction | ||||
WalletModel::SendCoinsReturn sendStatus = | WalletModel::SendCoinsReturn sendStatus = | ||||
model->sendCoins(currentTransaction); | model->sendCoins(*m_current_transaction); | ||||
// process sendStatus and on error generate message shown to user | // process sendStatus and on error generate message shown to user | ||||
processSendCoinsReturn(sendStatus); | processSendCoinsReturn(sendStatus); | ||||
if (sendStatus.status == WalletModel::OK) { | if (sendStatus.status == WalletModel::OK) { | ||||
Q_EMIT coinsSent(currentTransaction.getWtx()->GetId()); | Q_EMIT coinsSent(m_current_transaction->getWtx()->GetId()); | ||||
} else { | } else { | ||||
send_failure = true; | send_failure = true; | ||||
} | } | ||||
} | } | ||||
if (!send_failure) { | if (!send_failure) { | ||||
accept(); | accept(); | ||||
CoinControlDialog::coinControl()->UnSelectAll(); | CoinControlDialog::coinControl()->UnSelectAll(); | ||||
coinControlUpdateLabels(); | coinControlUpdateLabels(); | ||||
▲ Show 20 Lines • Show All 554 Lines • Show Last 20 Lines |