Bitcoin Core  29.1.0
P2P Digital Currency
walletcontroller.cpp
Go to the documentation of this file.
1 // Copyright (c) 2019-2022 The Bitcoin Core developers
2 // Distributed under the MIT software license, see the accompanying
3 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
4 
5 #include <qt/walletcontroller.h>
6 
8 #include <qt/clientmodel.h>
10 #include <qt/guiconstants.h>
11 #include <qt/guiutil.h>
12 #include <qt/walletmodel.h>
13 
14 #include <external_signer.h>
15 #include <interfaces/handler.h>
16 #include <interfaces/node.h>
17 #include <util/string.h>
18 #include <util/threadnames.h>
19 #include <util/translation.h>
20 #include <wallet/wallet.h>
21 
22 #include <algorithm>
23 #include <chrono>
24 
25 #include <QApplication>
26 #include <QMessageBox>
27 #include <QMetaObject>
28 #include <QMutexLocker>
29 #include <QThread>
30 #include <QTimer>
31 #include <QWindow>
32 
33 using util::Join;
38 
39 WalletController::WalletController(ClientModel& client_model, const PlatformStyle* platform_style, QObject* parent)
40  : QObject(parent)
41  , m_activity_thread(new QThread(this))
42  , m_activity_worker(new QObject)
43  , m_client_model(client_model)
44  , m_node(client_model.node())
45  , m_platform_style(platform_style)
46  , m_options_model(client_model.getOptionsModel())
47 {
48  m_handler_load_wallet = m_node.walletLoader().handleLoadWallet([this](std::unique_ptr<interfaces::Wallet> wallet) {
49  getOrCreateWallet(std::move(wallet));
50  });
51 
52  m_activity_worker->moveToThread(m_activity_thread);
53  m_activity_thread->start();
54  QTimer::singleShot(0, m_activity_worker, []() {
55  util::ThreadRename("qt-walletctrl");
56  });
57 }
58 
59 // Not using the default destructor because not all member types definitions are
60 // available in the header, just forward declared.
62 {
63  m_activity_thread->quit();
64  m_activity_thread->wait();
65  delete m_activity_worker;
66 }
67 
68 std::map<std::string, std::pair<bool, std::string>> WalletController::listWalletDir() const
69 {
70  QMutexLocker locker(&m_mutex);
71  std::map<std::string, std::pair<bool, std::string>> wallets;
72  for (const auto& [name, format] : m_node.walletLoader().listWalletDir()) {
73  wallets[name] = std::make_pair(false, format);
74  }
75  for (WalletModel* wallet_model : m_wallets) {
76  auto it = wallets.find(wallet_model->wallet().getWalletName());
77  if (it != wallets.end()) it->second.first = true;
78  }
79  return wallets;
80 }
81 
83 {
84  // Once the wallet is successfully removed from the node, the model will emit the 'WalletModel::unload' signal.
85  // This signal is already connected and will complete the removal of the view from the GUI.
86  // Look at 'WalletController::getOrCreateWallet' for the signal connection.
87  wallet_model->wallet().remove();
88 }
89 
90 void WalletController::closeWallet(WalletModel* wallet_model, QWidget* parent)
91 {
92  QMessageBox box(parent);
93  box.setWindowTitle(tr("Close wallet"));
94  box.setText(tr("Are you sure you wish to close the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(wallet_model->getDisplayName())));
95  box.setInformativeText(tr("Closing the wallet for too long can result in having to resync the entire chain if pruning is enabled."));
96  box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
97  box.setDefaultButton(QMessageBox::Yes);
98  if (box.exec() != QMessageBox::Yes) return;
99 
100  removeWallet(wallet_model);
101 }
102 
104 {
105  QMessageBox::StandardButton button = QMessageBox::question(parent, tr("Close all wallets"),
106  tr("Are you sure you wish to close all wallets?"),
107  QMessageBox::Yes|QMessageBox::Cancel,
108  QMessageBox::Yes);
109  if (button != QMessageBox::Yes) return;
110 
111  QMutexLocker locker(&m_mutex);
112  for (WalletModel* wallet_model : m_wallets) {
113  removeWallet(wallet_model);
114  }
115 }
116 
117 WalletModel* WalletController::getOrCreateWallet(std::unique_ptr<interfaces::Wallet> wallet)
118 {
119  QMutexLocker locker(&m_mutex);
120 
121  // Return model instance if exists.
122  if (!m_wallets.empty()) {
123  std::string name = wallet->getWalletName();
124  for (WalletModel* wallet_model : m_wallets) {
125  if (wallet_model->wallet().getWalletName() == name) {
126  return wallet_model;
127  }
128  }
129  }
130 
131  // Instantiate model and register it.
132  WalletModel* wallet_model = new WalletModel(std::move(wallet), m_client_model, m_platform_style,
133  nullptr /* required for the following moveToThread() call */);
134 
135  // Move WalletModel object to the thread that created the WalletController
136  // object (GUI main thread), instead of the current thread, which could be
137  // an outside wallet thread or RPC thread sending a LoadWallet notification.
138  // This ensures queued signals sent to the WalletModel object will be
139  // handled on the GUI event loop.
140  wallet_model->moveToThread(thread());
141  // setParent(parent) must be called in the thread which created the parent object. More details in #18948.
142  QMetaObject::invokeMethod(this, [wallet_model, this] {
143  wallet_model->setParent(this);
145 
146  m_wallets.push_back(wallet_model);
147 
148  // WalletModel::startPollBalance needs to be called in a thread managed by
149  // Qt because of startTimer. Considering the current thread can be a RPC
150  // thread, better delegate the calling to Qt with Qt::AutoConnection.
151  const bool called = QMetaObject::invokeMethod(wallet_model, "startPollBalance");
152  assert(called);
153 
154  connect(wallet_model, &WalletModel::unload, this, [this, wallet_model] {
155  // Defer removeAndDeleteWallet when no modal widget is actively waiting for an action.
156  // TODO: remove this workaround by removing usage of QDialog::exec.
157  QWidget* active_dialog = QApplication::activeModalWidget();
158  if (active_dialog && dynamic_cast<QProgressDialog*>(active_dialog) == nullptr) {
159  connect(qApp, &QApplication::focusWindowChanged, wallet_model, [this, wallet_model]() {
160  if (!QApplication::activeModalWidget()) {
161  removeAndDeleteWallet(wallet_model);
162  }
163  }, Qt::QueuedConnection);
164  } else {
165  removeAndDeleteWallet(wallet_model);
166  }
167  }, Qt::QueuedConnection);
168 
169  // Re-emit coinsSent signal from wallet model.
170  connect(wallet_model, &WalletModel::coinsSent, this, &WalletController::coinsSent);
171 
172  Q_EMIT walletAdded(wallet_model);
173 
174  return wallet_model;
175 }
176 
178 {
179  // Unregister wallet model.
180  {
181  QMutexLocker locker(&m_mutex);
182  m_wallets.erase(std::remove(m_wallets.begin(), m_wallets.end(), wallet_model));
183  }
184  Q_EMIT walletRemoved(wallet_model);
185  // Currently this can trigger the unload since the model can hold the last
186  // CWallet shared pointer.
187  delete wallet_model;
188 }
189 
190 WalletControllerActivity::WalletControllerActivity(WalletController* wallet_controller, QWidget* parent_widget)
191  : QObject(wallet_controller)
192  , m_wallet_controller(wallet_controller)
193  , m_parent_widget(parent_widget)
194 {
195  connect(this, &WalletControllerActivity::finished, this, &QObject::deleteLater);
196 }
197 
198 void WalletControllerActivity::showProgressDialog(const QString& title_text, const QString& label_text, bool show_minimized)
199 {
200  auto progress_dialog = new QProgressDialog(m_parent_widget);
201  progress_dialog->setAttribute(Qt::WA_DeleteOnClose);
202  connect(this, &WalletControllerActivity::finished, progress_dialog, &QWidget::close);
203 
204  progress_dialog->setWindowTitle(title_text);
205  progress_dialog->setLabelText(label_text);
206  progress_dialog->setRange(0, 0);
207  progress_dialog->setCancelButton(nullptr);
208  progress_dialog->setWindowModality(Qt::ApplicationModal);
209  GUIUtil::PolishProgressDialog(progress_dialog);
210  // The setValue call forces QProgressDialog to start the internal duration estimation.
211  // See details in https://bugreports.qt.io/browse/QTBUG-47042.
212  progress_dialog->setValue(0);
213  // When requested, launch dialog minimized
214  if (show_minimized) progress_dialog->showMinimized();
215 }
216 
217 CreateWalletActivity::CreateWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
218  : WalletControllerActivity(wallet_controller, parent_widget)
219 {
221 }
222 
224 {
225  delete m_create_wallet_dialog;
226  delete m_passphrase_dialog;
227 }
228 
230 {
232  m_passphrase_dialog->setWindowModality(Qt::ApplicationModal);
233  m_passphrase_dialog->show();
234 
235  connect(m_passphrase_dialog, &QObject::destroyed, [this] {
236  m_passphrase_dialog = nullptr;
237  });
238  connect(m_passphrase_dialog, &QDialog::accepted, [this] {
239  createWallet();
240  });
241  connect(m_passphrase_dialog, &QDialog::rejected, [this] {
242  Q_EMIT finished();
243  });
244 }
245 
247 {
249  //: Title of window indicating the progress of creation of a new wallet.
250  tr("Create Wallet"),
251  /*: Descriptive text of the create wallet progress window which indicates
252  to the user which wallet is currently being created. */
253  tr("Creating Wallet <b>%1</b>…").arg(m_create_wallet_dialog->walletName().toHtmlEscaped()));
254 
255  std::string name = m_create_wallet_dialog->walletName().toStdString();
256  uint64_t flags = 0;
257  // Enable descriptors by default.
261  }
264  }
267  }
268 
269  QTimer::singleShot(500ms, worker(), [this, name, flags] {
271 
272  if (wallet) {
274  } else {
276  }
277 
278  QTimer::singleShot(500ms, this, &CreateWalletActivity::finish);
279  });
280 }
281 
283 {
284  if (!m_error_message.empty()) {
285  QMessageBox::critical(m_parent_widget, tr("Create wallet failed"), QString::fromStdString(m_error_message.translated));
286  } else if (!m_warning_message.empty()) {
287  QMessageBox::warning(m_parent_widget, tr("Create wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
288  }
289 
291 
292  Q_EMIT finished();
293 }
294 
296 {
298 
299  std::vector<std::unique_ptr<interfaces::ExternalSigner>> signers;
300  try {
301  signers = node().listExternalSigners();
302  } catch (const std::runtime_error& e) {
303  QMessageBox::critical(nullptr, tr("Can't list signers"), e.what());
304  }
305  if (signers.size() > 1) {
306  QMessageBox::critical(nullptr, tr("Too many external signers found"), QString::fromStdString("More than one external signer found. Please connect only one at a time."));
307  signers.clear();
308  }
310 
311  m_create_wallet_dialog->setWindowModality(Qt::ApplicationModal);
312  m_create_wallet_dialog->show();
313 
314  connect(m_create_wallet_dialog, &QObject::destroyed, [this] {
315  m_create_wallet_dialog = nullptr;
316  });
317  connect(m_create_wallet_dialog, &QDialog::rejected, [this] {
318  Q_EMIT finished();
319  });
320  connect(m_create_wallet_dialog, &QDialog::accepted, [this] {
322  askPassphrase();
323  } else {
324  createWallet();
325  }
326  });
327 }
328 
329 OpenWalletActivity::OpenWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
330  : WalletControllerActivity(wallet_controller, parent_widget)
331 {
332 }
333 
335 {
336  if (!m_error_message.empty()) {
337  QMessageBox::critical(m_parent_widget, tr("Open wallet failed"), QString::fromStdString(m_error_message.translated));
338  } else if (!m_warning_message.empty()) {
339  QMessageBox::warning(m_parent_widget, tr("Open wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
340  }
341 
342  if (m_wallet_model) Q_EMIT opened(m_wallet_model);
343 
344  Q_EMIT finished();
345 }
346 
347 void OpenWalletActivity::open(const std::string& path)
348 {
349  QString name = GUIUtil::WalletDisplayName(path);
350 
352  //: Title of window indicating the progress of opening of a wallet.
353  tr("Open Wallet"),
354  /*: Descriptive text of the open wallet progress window which indicates
355  to the user which wallet is currently being opened. */
356  tr("Opening Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
357 
358  QTimer::singleShot(0, worker(), [this, path] {
360 
361  if (wallet) {
363  } else {
365  }
366 
367  QTimer::singleShot(0, this, &OpenWalletActivity::finish);
368  });
369 }
370 
371 LoadWalletsActivity::LoadWalletsActivity(WalletController* wallet_controller, QWidget* parent_widget)
372  : WalletControllerActivity(wallet_controller, parent_widget)
373 {
374 }
375 
376 void LoadWalletsActivity::load(bool show_loading_minimized)
377 {
379  //: Title of progress window which is displayed when wallets are being loaded.
380  tr("Load Wallets"),
381  /*: Descriptive text of the load wallets progress window which indicates to
382  the user that wallets are currently being loaded.*/
383  tr("Loading wallets…"),
384  /*show_minimized=*/show_loading_minimized);
385 
386  QTimer::singleShot(0, worker(), [this] {
387  for (auto& wallet : node().walletLoader().getWallets()) {
389  }
390 
391  QTimer::singleShot(0, this, [this] { Q_EMIT finished(); });
392  });
393 }
394 
395 RestoreWalletActivity::RestoreWalletActivity(WalletController* wallet_controller, QWidget* parent_widget)
396  : WalletControllerActivity(wallet_controller, parent_widget)
397 {
398 }
399 
400 void RestoreWalletActivity::restore(const fs::path& backup_file, const std::string& wallet_name)
401 {
402  QString name = QString::fromStdString(wallet_name);
403 
405  //: Title of progress window which is displayed when wallets are being restored.
406  tr("Restore Wallet"),
407  /*: Descriptive text of the restore wallets progress window which indicates to
408  the user that wallets are currently being restored.*/
409  tr("Restoring Wallet <b>%1</b>…").arg(name.toHtmlEscaped()));
410 
411  QTimer::singleShot(0, worker(), [this, backup_file, wallet_name] {
412  auto wallet{node().walletLoader().restoreWallet(backup_file, wallet_name, m_warning_message)};
413 
414  if (wallet) {
416  } else {
418  }
419 
420  QTimer::singleShot(0, this, &RestoreWalletActivity::finish);
421  });
422 }
423 
425 {
426  if (!m_error_message.empty()) {
427  //: Title of message box which is displayed when the wallet could not be restored.
428  QMessageBox::critical(m_parent_widget, tr("Restore wallet failed"), QString::fromStdString(m_error_message.translated));
429  } else if (!m_warning_message.empty()) {
430  //: Title of message box which is displayed when the wallet is restored with some warning.
431  QMessageBox::warning(m_parent_widget, tr("Restore wallet warning"), QString::fromStdString(Join(m_warning_message, Untranslated("\n")).translated));
432  } else {
433  //: Title of message box which is displayed when the wallet is successfully restored.
434  QMessageBox::information(m_parent_widget, tr("Restore wallet message"), QString::fromStdString(Untranslated("Wallet restored successfully \n").translated));
435  }
436 
438 
439  Q_EMIT finished();
440 }
441 
442 void MigrateWalletActivity::migrate(const std::string& name)
443 {
444  // Warn the user about migration
445  QMessageBox box(m_parent_widget);
446  box.setWindowTitle(tr("Migrate wallet"));
447  box.setText(tr("Are you sure you wish to migrate the wallet <i>%1</i>?").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(name))));
448  box.setInformativeText(tr("Migrating the wallet will convert this wallet to one or more descriptor wallets. A new wallet backup will need to be made.\n"
449  "If this wallet contains any watchonly scripts, a new wallet will be created which contains those watchonly scripts.\n"
450  "If this wallet contains any solvable but not watched scripts, a different and new wallet will be created which contains those scripts.\n\n"
451  "The migration process will create a backup of the wallet before migrating. This backup file will be named "
452  "<wallet name>-<timestamp>.legacy.bak and can be found in the directory for this wallet. In the event of "
453  "an incorrect migration, the backup can be restored with the \"Restore Wallet\" functionality."));
454  box.setStandardButtons(QMessageBox::Yes|QMessageBox::Cancel);
455  box.setDefaultButton(QMessageBox::Yes);
456  if (box.exec() != QMessageBox::Yes) return;
457 
458  SecureString passphrase;
459  if (node().walletLoader().isEncrypted(name)) {
460  // Get the passphrase for the wallet
462  if (dlg.exec() == QDialog::Rejected) return;
463  }
464 
465  showProgressDialog(tr("Migrate Wallet"), tr("Migrating Wallet <b>%1</b>…").arg(GUIUtil::HtmlEscape(name)));
466 
467  QTimer::singleShot(0, worker(), [this, name, passphrase] {
468  auto res{node().walletLoader().migrateWallet(name, passphrase)};
469 
470  if (res) {
471  m_success_message = tr("The wallet '%1' was migrated successfully.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->wallet->getWalletName())));
472  if (res->watchonly_wallet_name) {
473  m_success_message += QChar(' ') + tr("Watchonly scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->watchonly_wallet_name.value())));
474  }
475  if (res->solvables_wallet_name) {
476  m_success_message += QChar(' ') + tr("Solvable but not watched scripts have been migrated to a new wallet named '%1'.").arg(GUIUtil::HtmlEscape(GUIUtil::WalletDisplayName(res->solvables_wallet_name.value())));
477  }
478  m_wallet_model = m_wallet_controller->getOrCreateWallet(std::move(res->wallet));
479  } else {
481  }
482 
483  QTimer::singleShot(0, this, &MigrateWalletActivity::finish);
484  });
485 }
486 
488 {
489  if (!m_error_message.empty()) {
490  QMessageBox::critical(m_parent_widget, tr("Migration failed"), QString::fromStdString(m_error_message.translated));
491  } else {
492  QMessageBox::information(m_parent_widget, tr("Migration Successful"), m_success_message);
493  }
494 
496 
497  Q_EMIT finished();
498 }
bool isExternalSignerChecked() const
std::vector< WalletModel * > m_wallets
static const int MAX_PASSPHRASE_SIZE
Definition: guiconstants.h:20
interfaces::Wallet & wallet() const
Definition: walletmodel.h:138
void coinsSent(WalletModel *wallet, SendCoinsRecipient recipient, QByteArray transaction)
void showProgressDialog(const QString &title_text, const QString &label_text, bool show_minimized=false)
assert(!tx.IsCoinBase())
interfaces::Node & node() const
bool isEncryptWalletChecked() const
Ask passphrase for unlocking during migration.
virtual util::Result< std::unique_ptr< Wallet > > loadWallet(const std::string &name, std::vector< bilingual_str > &warnings)=0
Load existing wallet.
node::NodeContext m_node
Definition: bitcoin-gui.cpp:42
bool empty() const
Definition: translation.h:35
CreateWalletDialog * m_create_wallet_dialog
void closeAllWallets(QWidget *parent=nullptr)
bilingual_str Untranslated(std::string original)
Mark a bilingual_str as untranslated.
Definition: translation.h:82
void opened(WalletModel *wallet_model)
const PlatformStyle *const m_platform_style
CreateWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
void unload()
std::basic_string< char, std::char_traits< char >, secure_allocator< char > > SecureString
Definition: secure.h:58
Ask passphrase twice and encrypt.
QString walletName() const
Qt::ConnectionType blockingGUIThreadConnection()
Get connection type to call object slot in GUI thread with invokeMethod.
Definition: guiutil.cpp:378
OpenWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
Controller between interfaces::Node, WalletModel instances and the GUI.
LoadWalletsActivity(WalletController *wallet_controller, QWidget *parent_widget)
QString HtmlEscape(const QString &str, bool fMultiLine)
Definition: guiutil.cpp:249
std::string translated
Definition: translation.h:26
bool isDisablePrivateKeysChecked() const
void format(std::ostream &out, FormatStringCheck< sizeof...(Args)> fmt, const Args &... args)
Format list of arguments to the stream according to given format string.
Definition: tinyformat.h:1079
void ThreadRename(const std::string &)
Rename a thread both in terms of an internal (in-memory) name as well as its system thread name...
Definition: threadnames.cpp:57
QObject *const m_activity_worker
Flag set when a wallet contains no HD seed and no private keys, scripts, addresses, and other watch only things, and is therefore "blank.".
Definition: walletutil.h:71
void coinsSent(WalletModel *wallet_model, SendCoinsRecipient recipient, QByteArray transaction)
Indicates that the wallet needs an external signer.
Definition: walletutil.h:77
virtual std::vector< std::pair< std::string, std::string > > listWalletDir()=0
Return available wallets in wallet directory.
virtual util::Result< std::unique_ptr< Wallet > > createWallet(const std::string &name, const SecureString &passphrase, uint64_t wallet_creation_flags, std::vector< bilingual_str > &warnings)=0
Create new wallet.
RestoreWalletActivity(WalletController *wallet_controller, QWidget *parent_widget)
void migrate(const std::string &path)
bool isMakeBlankWalletChecked() const
void load(bool show_loading_minimized)
void created(WalletModel *wallet_model)
void closeWallet(WalletModel *wallet_model, QWidget *parent=nullptr)
void removeWallet(WalletModel *wallet_model)
Starts the wallet closure procedure.
const char * name
Definition: rest.cpp:49
void restore(const fs::path &backup_file, const std::string &wallet_name)
virtual void remove()=0
virtual util::Result< std::unique_ptr< Wallet > > restoreWallet(const fs::path &backup_file, const std::string &wallet_name, std::vector< bilingual_str > &warnings)=0
Restore backup wallet.
QWidget *const m_parent_widget
void open(const std::string &path)
Indicate that this wallet supports DescriptorScriptPubKeyMan.
Definition: walletutil.h:74
virtual bool isEncrypted(const std::string &wallet_name)=0
Returns true if wallet stores encryption keys.
void migrated(WalletModel *wallet_model)
std::vector< bilingual_str > m_warning_message
ClientModel & m_client_model
interfaces::Node & m_node
void walletAdded(WalletModel *wallet_model)
void PolishProgressDialog(QProgressDialog *dialog)
Definition: guiutil.cpp:901
Model for Bitcoin network client.
Definition: clientmodel.h:56
void restored(WalletModel *wallet_model)
Definition: messages.h:20
AskPassphraseDialog * m_passphrase_dialog
int flags
Definition: bitcoin-tx.cpp:536
void setSigners(const std::vector< std::unique_ptr< interfaces::ExternalSigner >> &signers)
QString WalletDisplayName(const QString &name)
Definition: guiutil.cpp:1011
virtual std::vector< std::unique_ptr< ExternalSigner > > listExternalSigners()=0
Return list of external signers (attached devices which can sign transactions).
Interface to Bitcoin wallet from Qt view code.
Definition: walletmodel.h:47
virtual WalletLoader & walletLoader()=0
Get wallet loader.
Multifunctional dialog to ask for passphrases.
std::map< std::string, std::pair< bool, std::string > > listWalletDir() const
Returns all wallet names in the wallet dir mapped to whether the wallet is loaded.
Dialog for creating wallets.
WalletModel * getOrCreateWallet(std::unique_ptr< interfaces::Wallet > wallet)
virtual std::unique_ptr< Handler > handleLoadWallet(LoadWalletFn fn)=0
void walletRemoved(WalletModel *wallet_model)
QObject * worker() const
bilingual_str ErrorString(const Result< T > &result)
Definition: result.h:93
WalletControllerActivity(WalletController *wallet_controller, QWidget *parent_widget)
QString getDisplayName() const
QThread *const m_activity_thread
WalletController(ClientModel &client_model, const PlatformStyle *platform_style, QObject *parent)
std::unique_ptr< interfaces::Handler > m_handler_load_wallet
Path class wrapper to block calls to the fs::path(std::string) implicit constructor and the fs::path:...
Definition: fs.h:32
auto Join(const C &container, const S &separator, UnaryOp unary_op)
Join all container items.
Definition: string.h:192
void removeAndDeleteWallet(WalletModel *wallet_model)
WalletController *const m_wallet_controller
virtual util::Result< WalletMigrationResult > migrateWallet(const std::string &name, const SecureString &passphrase)=0
Migrate a wallet.