29#include <validation.h>
37#include <QAbstractButton>
39#include <QApplication>
48#include <QDialogButtonBox>
63void ConfirmSend(QString* text =
nullptr, QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
65 QTimer::singleShot(0, [text, confirm_type]() {
66 for (QWidget* widget : QApplication::topLevelWidgets()) {
67 if (widget->inherits(
"SendConfirmationDialog")) {
68 SendConfirmationDialog* dialog = qobject_cast<SendConfirmationDialog*>(widget);
69 if (text) *text = dialog->text();
70 QAbstractButton* button = dialog->button(confirm_type);
71 button->setEnabled(true);
80 QMessageBox::StandardButton confirm_type = QMessageBox::Yes)
82 QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>(
"entries");
83 SendCoinsEntry* entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
88 if (status == CT_NEW) txid = hash;
90 ConfirmSend(
nullptr, confirm_type);
91 bool invoked = QMetaObject::invokeMethod(&sendCoinsDialog,
"sendButtonClicked", Q_ARG(
bool,
false));
97QModelIndex FindTx(
const QAbstractItemModel& model,
const Txid& txid)
99 QString hash = QString::fromStdString(txid.
ToString());
100 int rows = model.rowCount({});
101 for (
int row = 0; row < rows; ++row) {
102 QModelIndex index = model.index(row, 0, {});
111void BumpFee(
TransactionView& view,
const Txid& txid,
bool expectDisabled, std::string expectError,
bool cancel)
113 QTableView* table = view.findChild<QTableView*>(
"transactionView");
114 QModelIndex index = FindTx(*table->selectionModel()->model(), txid);
115 QVERIFY2(index.isValid(),
"Could not find BumpFee txid");
119 QAction* action = view.findChild<QAction*>(
"bumpFeeAction");
120 table->selectionModel()->select(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows);
121 action->setEnabled(expectDisabled);
122 table->customContextMenuRequested({});
123 QCOMPARE(action->isEnabled(), !expectDisabled);
125 action->setEnabled(
true);
127 if (expectError.empty()) {
128 ConfirmSend(&text, cancel ? QMessageBox::Cancel : QMessageBox::Yes);
133 QVERIFY(text.indexOf(QString::fromStdString(expectError)) != -1);
136void CompareBalance(
WalletModel& walletModel,
CAmount expected_balance, QLabel* balance_label_to_check)
140 QCOMPARE(balance_label_to_check->text().trimmed(), balanceComparison);
151 QVBoxLayout* entries = sendCoinsDialog.findChild<QVBoxLayout*>(
"entries");
152 QVERIFY(entries->count() == 1);
153 SendCoinsEntry* send_entry = qobject_cast<SendCoinsEntry*>(entries->itemAt(0)->widget());
161 int COINS_TO_SELECT = 2;
163 CAmount sum_selected_coins = 0;
165 QVERIFY(coins.size() == 1);
166 for (
const auto& [outpoint, tx_out] : coins.begin()->second) {
168 sum_selected_coins += tx_out.txout.nValue;
169 if (++selected == COINS_TO_SELECT)
break;
171 QVERIFY(selected == COINS_TO_SELECT);
197 wallet->SetupDescriptorScriptPubKeyMans();
209 auto descs =
Parse(
"combo(" + key_str +
")",
provider, error,
false);
211 assert(descs.size() == 1);
212 auto& desc = descs.at(0);
217 wallet->SetLastBlockProcessed(105,
WITH_LOCK(
node.context()->chainman->GetMutex(),
return node.context()->chainman->ActiveChain().Tip()->GetBlockHash()));
219 wallet->SetBroadcastTransactions(
true);
228 std::unique_ptr<ClientModel> clientModel;
229 std::unique_ptr<WalletModel> walletModel;
233 QVERIFY(optionsModel.
Init(error));
234 clientModel = std::make_unique<ClientModel>(
node, &optionsModel);
243 sendCoinsDialog.
setModel(walletModel.get());
244 transactionView.
setModel(walletModel.get());
266 MiniGUI mini_gui(
node, platformStyle.get());
267 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
275 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
278 VerifyUseAvailableBalance(sendCoinsDialog, walletModel);
282 QCOMPARE(transactionTableModel->
rowCount({}), 105);
286 qApp->processEvents();
287 QCOMPARE(transactionTableModel->
rowCount({}), 107);
288 QVERIFY(FindTx(*transactionTableModel, txid1).isValid());
289 QVERIFY(FindTx(*transactionTableModel, txid2).isValid());
292 BumpFee(transactionView, txid1,
false, {},
true);
293 BumpFee(transactionView, txid2,
false, {},
true);
294 BumpFee(transactionView, txid2,
false, {},
false);
295 BumpFee(transactionView, txid2,
true,
"already bumped",
false);
299 overviewPage.setWalletModel(&walletModel);
301 CompareBalance(walletModel, walletModel.
wallet().
getBalance(), overviewPage.findChild<QLabel*>(
"labelBalance"));
305 receiveCoinsDialog.setModel(&walletModel);
309 QLineEdit* labelInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqLabel");
310 labelInput->setText(
"TEST_LABEL_1");
317 QLineEdit* messageInput = receiveCoinsDialog.findChild<QLineEdit*>(
"reqMessage");
318 messageInput->setText(
"TEST_MESSAGE_1");
319 int initialRowCount = requestTableModel->
rowCount({});
320 QPushButton* requestPaymentButton = receiveCoinsDialog.findChild<QPushButton*>(
"receiveButton");
321 requestPaymentButton->click();
323 for (QWidget* widget : QApplication::topLevelWidgets()) {
324 if (widget->inherits(
"ReceiveRequestDialog")) {
326 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"payment_header")->text(), QString(
"Payment information"));
327 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_tag")->text(), QString(
"URI:"));
328 QString uri = receiveRequestDialog->QObject::findChild<QLabel*>(
"uri_content")->text();
329 QCOMPARE(uri.count(
"bitcoin:"), 2);
330 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"address_tag")->text(), QString(
"Address:"));
331 QVERIFY(address.isEmpty());
332 address = receiveRequestDialog->QObject::findChild<QLabel*>(
"address_content")->text();
333 QVERIFY(!address.isEmpty());
335 QCOMPARE(uri.count(
"amount=0.00000001"), 2);
336 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_tag")->text(), QString(
"Amount:"));
337 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"amount_content")->text(), QString::fromStdString(
"0.00000001 " +
CURRENCY_UNIT));
339 QCOMPARE(uri.count(
"label=TEST_LABEL_1"), 2);
340 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_tag")->text(), QString(
"Label:"));
341 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"label_content")->text(), QString(
"TEST_LABEL_1"));
343 QCOMPARE(uri.count(
"message=TEST_MESSAGE_1"), 2);
344 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_tag")->text(), QString(
"Message:"));
345 QCOMPARE(receiveRequestDialog->QObject::findChild<QLabel*>(
"message_content")->text(), QString(
"TEST_MESSAGE_1"));
350 QPushButton* clearButton = receiveCoinsDialog.findChild<QPushButton*>(
"clearButton");
351 clearButton->click();
352 QCOMPARE(labelInput->text(), QString(
""));
354 QCOMPARE(messageInput->text(), QString(
""));
357 int currentRowCount = requestTableModel->
rowCount({});
358 QCOMPARE(currentRowCount, initialRowCount+1);
362 QCOMPARE(requests.size(),
size_t{1});
366 QCOMPARE(entry.
id, int64_t{1});
367 QVERIFY(entry.
date.isValid());
376 QTableView* table = receiveCoinsDialog.findChild<QTableView*>(
"recentRequestsView");
377 table->selectRow(currentRowCount-1);
378 QPushButton* removeRequestButton = receiveCoinsDialog.findChild<QPushButton*>(
"removeRequestButton");
379 removeRequestButton->click();
380 QCOMPARE(requestTableModel->
rowCount({}), currentRowCount-1);
388 const std::shared_ptr<CWallet>&
wallet = SetupDescriptorsWallet(
node, test,
true);
392 MiniGUI mini_gui(
node, platformStyle.get());
393 mini_gui.initModelForWallet(
node,
wallet, platformStyle.get());
401 sendCoinsDialog.findChild<QLabel*>(
"labelBalance"));
408 timer.setInterval(500);
409 QObject::connect(&timer, &QTimer::timeout, [&](){
410 for (QWidget* widget : QApplication::topLevelWidgets()) {
411 if (widget->inherits(
"QMessageBox") && widget->objectName().compare(
"psbt_copied_message") == 0) {
412 QMessageBox* dialog = qobject_cast<QMessageBox*>(widget);
413 QAbstractButton* button = dialog->button(QMessageBox::Discard);
414 button->setEnabled(true);
424 SendCoins(*
wallet.get(), sendCoinsDialog,
PKHash(), 5 *
COIN, QMessageBox::Save);
425 const std::string& psbt_string = QApplication::clipboard()->text().toStdString();
426 QVERIFY(!psbt_string.empty());
429 std::optional<std::vector<unsigned char>> decoded_psbt =
DecodeBase64(psbt_string);
430 QVERIFY(decoded_psbt);
439 for (
int i = 0; i < 5; ++i) {
447 const std::shared_ptr<CWallet>& desc_wallet = SetupDescriptorsWallet(
node, test);
448 TestGUI(
node, desc_wallet);
452 TestGUIWatchOnly(
node, test);
std::variant< CNoDestination, PubKeyDestination, PKHash, ScriptHash, WitnessV0ScriptHash, WitnessV0KeyHash, WitnessV1Taproot, PayToAnchor, WitnessUnknown > CTxDestination
A txout script categorized into standard templates.
int64_t CAmount
Amount in satoshis (Can be negative)
constexpr CAmount COIN
The amount of satoshis in one BTC.
const CChainParams & Params()
Return the currently selected parameters.
#define Assert(val)
Identity function.
Widget for entering bitcoin amounts.
void setValue(const CAmount &value)
static QString formatWithUnit(Unit unit, const CAmount &amount, bool plussign=false, SeparatorStyle separators=SeparatorStyle::STANDARD)
Format as string (with unit)
CPubKey GetPubKey() const
Compute the public key from a private key.
Interface from Qt to configuration data structure for Bitcoin client.
bool Init(bilingual_str &error)
BitcoinUnit getDisplayUnit() const
Overview ("home") page widget.
Line edit that can be marked as "invalid" to show input validation feedback.
Dialog for requesting payment of bitcoins.
SendCoinsRecipient recipient
Model for list of recently generated payment requests / bitcoin: URIs.
int rowCount(const QModelIndex &parent) const override
Dialog for sending bitcoins.
void setModel(WalletModel *model)
wallet::CCoinControl * getCoinControl()
A single entry in the dialog for sending bitcoins.
void useAvailableBalance(SendCoinsEntry *entry)
SendCoinsRecipient getValue()
std::string sPaymentRequest
QString authenticatedMerchant
Minimal stream for reading from an existing byte array by std::span.
UI model for the transaction table of a wallet.
@ TxHashRole
Transaction hash.
int rowCount(const QModelIndex &parent) const override
Widget showing the transaction list for a wallet, including a filter row.
void setModel(WalletModel *model)
QTableView * transactionView
Interface to Bitcoin wallet from Qt view code.
RecentRequestsTableModel * getRecentRequestsTableModel() const
void pollBalanceChanged()
TransactionTableModel * getTransactionTableModel() const
interfaces::Wallet & wallet() const
OptionsModel * getOptionsModel() const
interfaces::WalletBalances getCachedBalance() const
interfaces::Node & m_node
constexpr bool IsNull() const
Top-level interface for a bitcoin node (bitcoind process).
virtual CoinsList listCoins()=0
virtual CAmount getBalance()=0
Get balance.
virtual WalletBalances getBalances()=0
Get balances.
virtual std::vector< std::string > getAddressReceiveRequests()=0
Get receive requests.
std::string ToString() const
PreselectedInput & Select(const COutPoint &outpoint)
Lock-in the given output for spending.
CTxDestination destChange
Custom change destination, if not set an address is generated.
A CWallet maintains a set of transactions and balances, and provides the ability to create new transa...
Descriptor with some wallet metadata.
RAII object to check and reserve a wallet rescan.
static UniValue Parse(std::string_view raw, ParamFormat format=ParamFormat::JSON)
Parse string to UniValue or throw runtime_error if string contains invalid JSON.
const std::string CURRENCY_UNIT
std::string HexStr(const std::span< const uint8_t > s)
Convert a span of bytes to a lower-case hexadecimal string.
std::string EncodeSecret(const CKey &key)
std::string EncodeDestination(const CTxDestination &dest)
std::unique_ptr< WalletLoader > MakeWalletLoader(Chain &chain, ArgsManager &args)
Return implementation of ChainClient interface for a wallet loader.
std::unique_ptr< Wallet > MakeWallet(wallet::WalletContext &context, const std::shared_ptr< wallet::CWallet > &wallet)
Return implementation of Wallet interface.
std::unique_ptr< WalletDatabase > CreateMockableWalletDatabase()
bool AddWallet(WalletContext &context, const std::shared_ptr< CWallet > &wallet)
@ WALLET_FLAG_DESCRIPTORS
Indicate that this wallet supports DescriptorScriptPubKeyMan.
@ WALLET_FLAG_DISABLE_PRIVATE_KEYS
bool RemoveWallet(WalletContext &context, const std::shared_ptr< CWallet > &wallet, std::optional< bool > load_on_start, std::vector< bilingual_str > &warnings)
util::Result< PartiallySignedTransaction > DecodeRawPSBT(std::span< const std::byte > tx_data)
Decode a raw (binary blob) PSBT into a PartiallySignedTransaction.
void ConfirmMessage(QString *text, std::chrono::milliseconds msec)
Press "Ok" button in message box dialog.
CScript GetScriptForRawPubKey(const CPubKey &pubKey)
Generate a P2PK script for the given pubkey.
auto MakeByteSpan(const V &v) noexcept
Testing fixture that pre-creates a 100-block REGTEST-mode block chain.
CBlock CreateAndProcessBlock(const std::vector< CMutableTransaction > &txns, const CScript &scriptPubKey)
Create a new block with just given transactions, coinbase paying to scriptPubKey, and try to add it t...
interfaces::WalletLoader * wallet_loader
std::unique_ptr< interfaces::Chain > chain
uint256 last_scanned_block
Hash and height of most recent block that was successfully scanned.
enum wallet::ScanResult::@19 status
uint256 last_failed_block
Height of the most recent block that could not be scanned due to read errors or pruning.
WalletContext struct containing references to state shared between CWallet instances,...
#define WITH_LOCK(cs, code)
Run code while locking a mutex.
FuzzedDataProvider provider
ChangeType
General change type (added, updated, removed).
std::optional< std::vector< unsigned char > > DecodeBase64(std::string_view str)