Inside Bitcoin
Opening the selected file…
Inside Bitcoin
Opening the selected file…
The code behind the network, with room to understand it.
Release published 2026-10-28 · Snapshot fetched 2026-11-01. Latest stable release at fetch time; development branches and Git history are excluded. Only the selected code window and visible folders load here.
251ui->scrollArea->ensureWidgetVisible(entry);252valid = false;253}254}255}257if(!valid || recipients.isEmpty())258{259return false;260}262fNewRecipientAllowed = false;263WalletModel::UnlockContext ctx(model->requestUnlock());264if(!ctx.isValid())265{266// Unlock wallet was cancelled267fNewRecipientAllowed = true;268return false;269}271// prepare transaction for getting txFee earlier272m_current_transaction = std::make_unique<WalletModelTransaction>(recipients);273WalletModel::SendCoinsReturn prepareStatus;275updateCoinControlState();277CCoinControl coin_control = *m_coin_control;278coin_control.m_allow_other_inputs = !coin_control.HasSelected(); // future, could introduce a checkbox to customize this value.279prepareStatus = model->prepareTransaction(*m_current_transaction, coin_control);281// process prepareStatus and on error generate message shown to user282processSendCoinsReturn(prepareStatus,283BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), m_current_transaction->getTransactionFee()));285if(prepareStatus.status != WalletModel::OK) {286fNewRecipientAllowed = true;287return false;288}290CAmount txFee = m_current_transaction->getTransactionFee();291QStringList formatted;292for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients())293{294// generate amount string with wallet name in case of multiwallet295QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);296if (model->isMultiwallet()) {297amount = tr("%1 from wallet '%2'").arg(amount, GUIUtil::HtmlEscape(model->getWalletName()));298}300// generate address string301QString address = rcp.address;303QString recipientElement;305{306if(rcp.label.length() > 0) // label with address307{308recipientElement.append(tr("%1 to '%2'").arg(amount, GUIUtil::HtmlEscape(rcp.label)));309recipientElement.append(QString(" (%1)").arg(address));310}311else // just address312{313recipientElement.append(tr("%1 to %2").arg(amount, address));314}315}316formatted.append(recipientElement);317}319/*: Message displayed when attempting to create a transaction. Cautionary text to prompt the user to verify320that the displayed transaction details represent the transaction the user intends to create. */321question_string.append(tr("Do you want to create this transaction?"));322question_string.append("<br /><span style='font-size:10pt;'>");323if (model->wallet().privateKeysDisabled() && !model->wallet().hasExternalSigner()) {324/*: Text to inform a user attempting to create a transaction of their current options. At this stage,325a user can only create a PSBT. This string is displayed when private keys are disabled and an external326signer is not available. */327question_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(CLIENT_NAME));328} else if (model->getOptionsModel()->getEnablePSBTControls()) {329/*: Text to inform a user attempting to create a transaction of their current options. At this stage,330a user can send their transaction or create a PSBT. This string is displayed when both private keys331and PSBT controls are enabled. */332question_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(CLIENT_NAME));333} else {334/*: Text to prompt a user to review the details of the transaction they are attempting to send. */335question_string.append(tr("Please, review your transaction."));336}337question_string.append("</span>%1");339if(txFee > 0)340{341// append fee string if a fee is required342question_string.append("<hr /><b>");343question_string.append(tr("Transaction fee"));344question_string.append("</b>");346// append transaction size347//: When reviewing a newly created PSBT (via Send flow), the transaction fee is shown, with "virtual size" of the transaction displayed for context348question_string.append(" (" + tr("%1 kvB", "PSBT transaction creation").arg((double)m_current_transaction->getTransactionSize() / 1000, 0, 'g', 3) + "): ");350// append transaction fee value351question_string.append("<span style='color:#aa0000; font-weight:bold;'>");352question_string.append(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), txFee));353question_string.append("</span><br />");354}356// append RBF message357question_string.append("<span style='font-size:10pt; font-weight:normal;'>");358question_string.append(tr("You can increase the fee later."));360// add total amount in all subdivision units361question_string.append("<hr />");362CAmount totalAmount = m_current_transaction->getTotalTransactionAmount() + txFee;363QStringList alternativeUnits;364for (const BitcoinUnit u : BitcoinUnits::availableUnits()) {365if(u != model->getOptionsModel()->getDisplayUnit())366alternativeUnits.append(BitcoinUnits::formatHtmlWithUnit(u, totalAmount));367}368question_string.append(QString("<b>%1</b>: <b>%2</b>").arg(tr("Total Amount"))369.arg(BitcoinUnits::formatHtmlWithUnit(model->getOptionsModel()->getDisplayUnit(), totalAmount)));370question_string.append(QString("<br /><span style='font-size:10pt; font-weight:normal;'>(=%1)</span>")371.arg(alternativeUnits.join(" " + tr("or") + " ")));373if (formatted.size() > 1) {374question_string = question_string.arg("");375informative_text = tr("To review recipient list click \"Show Details…\"");376detailed_text = formatted.join("\n\n");377} else {378question_string = question_string.arg("<br /><br />" + formatted.at(0));379}381return true;382}384void SendCoinsDialog::presentPSBT(PartiallySignedTransaction& psbtx)385{386// Serialize the PSBT387DataStream ssTx{};388ssTx << psbtx;389GUIUtil::setClipboard(EncodeBase64(ssTx.str()).c_str());390QMessageBox msgBox(this);391//: Caption of "PSBT has been copied" messagebox392msgBox.setText(tr("Unsigned Transaction", "PSBT copied"));393msgBox.setInformativeText(tr("The PSBT has been copied to the clipboard. You can also save it."));394msgBox.setStandardButtons(QMessageBox::Save | QMessageBox::Discard);395msgBox.setDefaultButton(QMessageBox::Discard);396msgBox.setObjectName("psbt_copied_message");397switch (msgBox.exec()) {398case QMessageBox::Save: {399QString selectedFilter;400QString fileNameSuggestion = "";401bool first = true;402for (const SendCoinsRecipient &rcp : m_current_transaction->getRecipients()) {403if (!first) {404fileNameSuggestion.append(" - ");405}406QString labelOrAddress = rcp.label.isEmpty() ? rcp.address : rcp.label;407QString amount = BitcoinUnits::formatWithUnit(model->getOptionsModel()->getDisplayUnit(), rcp.amount);408fileNameSuggestion.append(labelOrAddress + "-" + amount);409first = false;410}411fileNameSuggestion.append(".psbt");412QString filename = GUIUtil::getSaveFileName(this,413tr("Save Transaction Data"), fileNameSuggestion,414//: Expanded name of the binary PSBT file format. See: BIP 174.415tr("Partially Signed Transaction (Binary)") + QLatin1String(" (*.psbt)"), &selectedFilter);416if (filename.isEmpty()) {417return;418}419std::ofstream out{filename.toLocal8Bit().data(), std::ofstream::out | std::ofstream::binary};420out << ssTx.str();421out.close();422//: Popup message when a PSBT has been saved to a file423Q_EMIT message(tr("PSBT saved"), tr("PSBT saved to disk"), CClientUIInterface::MSG_INFORMATION);424break;425}426case QMessageBox::Discard:427break;428default:429assert(false);430} // msgBox.exec()431}433bool SendCoinsDialog::signWithExternalSigner(PartiallySignedTransaction& psbtx, CMutableTransaction& mtx, bool& complete) {434std::optional<PSBTError> err;435try {436err = model->wallet().fillPSBT({.sign = true, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete);437} catch (const std::runtime_error& e) {438QMessageBox::critical(nullptr, tr("Sign failed"), e.what());439return false;440}441if (err == PSBTError::EXTERNAL_SIGNER_NOT_FOUND) {442//: "External signer" means using devices such as hardware wallets.443const QString msg = tr("External signer not found");444QMessageBox::critical(nullptr, msg, msg);445return false;446}447if (err == PSBTError::EXTERNAL_SIGNER_FAILED) {448//: "External signer" means using devices such as hardware wallets.449const QString msg = tr("External signer failure");450QMessageBox::critical(nullptr, msg, msg);451return false;452}453if (err) {454qWarning() << "Failed to sign PSBT";455processSendCoinsReturn(WalletModel::TransactionCreationFailed);456return false;457}458// fillPSBT does not always properly finalize459complete = FinalizeAndExtractPSBT(psbtx, mtx);460return true;461}463void SendCoinsDialog::sendButtonClicked([[maybe_unused]] bool checked)464{465if(!model || !model->getOptionsModel())466return;468QString question_string, informative_text, detailed_text;469if (!PrepareSendText(question_string, informative_text, detailed_text)) return;470assert(m_current_transaction);472const QString confirmation = tr("Confirm send coins");473const bool enable_send{!model->wallet().privateKeysDisabled() || model->wallet().hasExternalSigner()};474const bool always_show_unsigned{model->getOptionsModel()->getEnablePSBTControls()};475auto confirmationDialog = new SendConfirmationDialog(confirmation, question_string, informative_text, detailed_text, SEND_CONFIRM_DELAY, enable_send, always_show_unsigned, this);476confirmationDialog->setAttribute(Qt::WA_DeleteOnClose);477// TODO: Replace QDialog::exec() with safer QDialog::show().478const auto retval = static_cast<QMessageBox::StandardButton>(confirmationDialog->exec());480if(retval != QMessageBox::Yes && retval != QMessageBox::Save)481{482fNewRecipientAllowed = true;483return;484}486bool send_failure = false;487if (retval == QMessageBox::Save) {488// "Create Unsigned" clicked489CMutableTransaction mtx = CMutableTransaction{*(m_current_transaction->getWtx())};490PartiallySignedTransaction psbtx(mtx);491bool complete = false;492// Fill without signing493const auto err{model->wallet().fillPSBT({.sign = false, .bip32_derivs = true}, /*n_signed=*/nullptr, psbtx, complete)};494assert(!complete);495assert(!err);497// Copy PSBT to clipboard and offer to save498presentPSBT(psbtx);499} else {500// "Send" clicked
Independent explanations by MyBitcoinExplorer. Not affiliated with or endorsed by Bitcoin Core. Guides are educational and do not replace the source or its tests.
Bitcoin Core source © its contributors, distributed under the MIT license. Original notices and bundled third-party licenses are retained in the source tree.