2020-10-07 10:36:04 +00:00
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
2020-12-26 19:56:06 +00:00
|
|
|
// Copyright (c) 2020-2021, The Monero Project.
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
#include <QDir>
|
|
|
|
#include <QMessageBox>
|
|
|
|
|
|
|
|
#include "appcontext.h"
|
2020-10-21 14:52:34 +00:00
|
|
|
#include "globals.h"
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// libwalletqt
|
|
|
|
#include "libwalletqt/TransactionHistory.h"
|
|
|
|
#include "libwalletqt/Subaddress.h"
|
|
|
|
#include "libwalletqt/Coins.h"
|
|
|
|
#include "model/TransactionHistoryModel.h"
|
|
|
|
#include "model/SubaddressModel.h"
|
|
|
|
|
|
|
|
|
|
|
|
Prices *AppContext::prices = nullptr;
|
|
|
|
WalletKeysFilesModel *AppContext::wallets = nullptr;
|
|
|
|
TxFiatHistory *AppContext::txFiatHistory = nullptr;
|
|
|
|
double AppContext::balance = 0;
|
|
|
|
QMap<QString, QString> AppContext::txDescriptionCache;
|
2020-12-14 22:07:23 +00:00
|
|
|
QMap<QString, QString> AppContext::txCache;
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
AppContext::AppContext(QCommandLineParser *cmdargs) {
|
2021-03-27 18:59:21 +00:00
|
|
|
this->m_walletKeysFilesModel = new WalletKeysFilesModel(this, this);
|
2020-10-07 10:36:04 +00:00
|
|
|
this->network = new QNetworkAccessManager();
|
|
|
|
this->networkClearnet = new QNetworkAccessManager();
|
|
|
|
this->cmdargs = cmdargs;
|
|
|
|
|
|
|
|
#if defined(Q_OS_MAC)
|
|
|
|
this->isTorSocks = qgetenv("DYLD_INSERT_LIBRARIES").indexOf("libtorsocks") >= 0;
|
|
|
|
#elif defined(Q_OS_LINUX)
|
|
|
|
this->isTorSocks = qgetenv("LD_PRELOAD").indexOf("libtorsocks") >= 0;
|
|
|
|
#elif defined(Q_OS_WIN)
|
|
|
|
this->isTorSocks = false;
|
|
|
|
#endif
|
|
|
|
|
|
|
|
this->isTails = TailsOS::detect();
|
|
|
|
this->isWhonix = WhonixOS::detect();
|
|
|
|
|
|
|
|
//Paths
|
|
|
|
this->configRoot = QDir::homePath();
|
|
|
|
if (isTails) { // #if defined(PORTABLE)
|
|
|
|
QString portablePath = []{
|
|
|
|
QString appImagePath = qgetenv("APPIMAGE");
|
|
|
|
if (appImagePath.isEmpty()) {
|
|
|
|
qDebug() << "Not an appimage, using currentPath()";
|
2021-02-22 06:51:09 +00:00
|
|
|
return QDir::currentPath() + "/.wowlet";
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
QFileInfo appImageDir(appImagePath);
|
2021-02-22 06:51:09 +00:00
|
|
|
return appImageDir.absoluteDir().path() + "/.wowlet";
|
2020-10-07 10:36:04 +00:00
|
|
|
}();
|
|
|
|
|
|
|
|
|
|
|
|
if (QDir().mkpath(portablePath)) {
|
|
|
|
this->configRoot = portablePath;
|
|
|
|
} else {
|
|
|
|
qCritical() << "Unable to create portable directory: " << portablePath;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this->accountName = Utils::getUnixAccountName();
|
|
|
|
this->homeDir = QDir::homePath();
|
|
|
|
|
2020-12-25 21:07:48 +00:00
|
|
|
QString walletDir = config()->get(Config::walletDirectory).toString();
|
|
|
|
if (walletDir.isEmpty()) {
|
2020-10-07 10:36:04 +00:00
|
|
|
#if defined(Q_OS_LINUX) or defined(Q_OS_MAC)
|
2020-12-21 20:34:36 +00:00
|
|
|
this->defaultWalletDir = QString("%1/Wownero/wallets").arg(this->configRoot);
|
|
|
|
this->defaultWalletDirRoot = QString("%1/Wownero").arg(this->configRoot);
|
2020-10-07 10:36:04 +00:00
|
|
|
#elif defined(Q_OS_WIN)
|
2020-12-21 20:34:36 +00:00
|
|
|
this->defaultWalletDir = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) + "/Wownero";
|
2020-12-25 21:07:48 +00:00
|
|
|
this->defaultWalletDirRoot = QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
|
2020-10-07 10:36:04 +00:00
|
|
|
#endif
|
2020-12-25 21:07:48 +00:00
|
|
|
} else {
|
|
|
|
this->defaultWalletDir = walletDir;
|
|
|
|
this->defaultWalletDirRoot = walletDir;
|
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2020-10-09 16:33:51 +00:00
|
|
|
// Create wallet dirs
|
2020-10-07 10:36:04 +00:00
|
|
|
if (!QDir().mkpath(defaultWalletDir))
|
|
|
|
qCritical() << "Unable to create dir: " << defaultWalletDir;
|
|
|
|
|
2021-02-22 06:51:09 +00:00
|
|
|
this->configDirectory = QString("%1/.config/wowlet/").arg(this->configRoot);
|
2020-10-07 10:36:04 +00:00
|
|
|
#if defined(Q_OS_UNIX)
|
|
|
|
if(!this->configDirectory.endsWith('/'))
|
|
|
|
this->configDirectory = QString("%1/").arg(this->configDirectory);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// Config
|
|
|
|
createConfigDirectory(this->configDirectory);
|
|
|
|
|
2020-12-21 20:34:36 +00:00
|
|
|
// if(this->cmdargs->isSet("stagenet"))
|
|
|
|
// this->networkType = NetworkType::STAGENET;
|
|
|
|
// else if(this->cmdargs->isSet("testnet"))
|
|
|
|
// this->networkType = NetworkType::TESTNET;
|
|
|
|
// else
|
|
|
|
this->networkType = NetworkType::MAINNET;
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// auto nodeSourceUInt = config()->get(Config::nodeSource).toUInt();
|
|
|
|
// AppContext::nodeSource = static_cast<NodeSource>(nodeSourceUInt);
|
|
|
|
this->nodes = new Nodes(this, this->networkClearnet);
|
|
|
|
connect(this, &AppContext::nodeSourceChanged, this->nodes, &Nodes::onNodeSourceChanged);
|
|
|
|
connect(this, &AppContext::setCustomNodes, this->nodes, &Nodes::setCustomNodes);
|
|
|
|
|
|
|
|
// Tor & socks proxy
|
|
|
|
this->ws = new WSClient(this, m_wsUrl);
|
|
|
|
connect(this->ws, &WSClient::WSMessage, this, &AppContext::onWSMessage);
|
2021-04-02 12:22:37 +00:00
|
|
|
connect(this->ws, &WSClient::connectionEstablished, this, &AppContext::wsConnected);
|
|
|
|
connect(this->ws, &WSClient::closed, this, &AppContext::wsDisconnected);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-01-19 22:39:31 +00:00
|
|
|
// Store the wallet every 2 minutes
|
|
|
|
m_storeTimer.start(2 * 60 * 1000);
|
2020-12-28 04:39:20 +00:00
|
|
|
connect(&m_storeTimer, &QTimer::timeout, [this](){
|
2021-01-19 22:39:31 +00:00
|
|
|
this->storeWallet();
|
2020-10-07 10:36:04 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// restore height lookup
|
|
|
|
this->initRestoreHeights();
|
|
|
|
|
|
|
|
// price history lookup
|
|
|
|
auto genesis_timestamp = this->restoreHeights[NetworkType::Type::MAINNET]->data.firstKey();
|
|
|
|
AppContext::txFiatHistory = new TxFiatHistory(genesis_timestamp, this->configDirectory);
|
|
|
|
connect(this->ws, &WSClient::connectionEstablished, AppContext::txFiatHistory, &TxFiatHistory::onUpdateDatabase);
|
2020-12-24 13:34:37 +00:00
|
|
|
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYear, [=](int year){
|
2020-10-07 10:36:04 +00:00
|
|
|
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1}})").arg(year).toUtf8();
|
|
|
|
this->ws->sendMsg(data);
|
|
|
|
});
|
2020-12-24 13:34:37 +00:00
|
|
|
connect(AppContext::txFiatHistory, &TxFiatHistory::requestYearMonth, [=](int year, int month) {
|
2020-10-07 10:36:04 +00:00
|
|
|
QByteArray data = QString(R"({"cmd": "txFiatHistory", "data": {"year": %1, "month": %2}})").arg(year).arg(month).toUtf8();
|
|
|
|
this->ws->sendMsg(data);
|
|
|
|
});
|
|
|
|
|
|
|
|
// fiat/crypto lookup
|
|
|
|
AppContext::prices = new Prices();
|
|
|
|
|
2020-10-17 21:14:56 +00:00
|
|
|
// XMRig
|
|
|
|
#ifdef HAS_XMRIG
|
|
|
|
this->XMRig = new XmRig(this->configDirectory, this);
|
2020-11-25 15:55:29 +00:00
|
|
|
this->XMRig->prepare();
|
2020-10-17 21:14:56 +00:00
|
|
|
#endif
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
this->walletManager = WalletManager::instance();
|
|
|
|
QString logPath = QString("%1/daemon.log").arg(configDirectory);
|
|
|
|
Monero::Utils::onStartup();
|
2021-03-30 09:52:29 +00:00
|
|
|
Monero::Wallet::init("", "wowlet", logPath.toStdString(), true);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
bool logLevelFromEnv;
|
|
|
|
int logLevel = qEnvironmentVariableIntValue("MONERO_LOG_LEVEL", &logLevelFromEnv);
|
|
|
|
if(this->cmdargs->isSet("quiet"))
|
|
|
|
this->walletManager->setLogLevel(-1);
|
|
|
|
else if (logLevelFromEnv && logLevel >= 0 && logLevel <= Monero::WalletManagerFactory::LogLevel_Max)
|
|
|
|
Monero::WalletManagerFactory::setLogLevel(logLevel);
|
|
|
|
|
|
|
|
connect(this, &AppContext::createTransactionError, this, &AppContext::onCreateTransactionError);
|
|
|
|
|
|
|
|
// libwallet connects
|
|
|
|
connect(this->walletManager, &WalletManager::walletOpened, this, &AppContext::onWalletOpened);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::initTor() {
|
2020-10-09 00:13:08 +00:00
|
|
|
this->tor = new Tor(this, this);
|
2020-10-07 10:36:04 +00:00
|
|
|
this->tor->start();
|
|
|
|
|
2020-11-11 13:26:06 +00:00
|
|
|
if (!(isWhonix)) {
|
2020-12-28 04:39:20 +00:00
|
|
|
this->networkProxy = new QNetworkProxy(QNetworkProxy::Socks5Proxy, Tor::torHost, Tor::torPort);
|
2020-10-07 10:36:04 +00:00
|
|
|
this->network->setProxy(*networkProxy);
|
|
|
|
if (m_wsUrl.host().endsWith(".onion"))
|
|
|
|
this->ws->webSocket.setProxy(*networkProxy);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::initWS() {
|
|
|
|
this->ws->start();
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:55:27 +00:00
|
|
|
void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address) {
|
2020-10-07 10:36:04 +00:00
|
|
|
// tx cancelled by user
|
2020-10-21 14:52:34 +00:00
|
|
|
double amount = tx->amount() / globals::cdiv;
|
2020-10-07 10:36:04 +00:00
|
|
|
emit createTransactionCancelled(address, amount);
|
|
|
|
this->currentWallet->disposeTransaction(tx);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const {
|
|
|
|
if(this->currentWallet == nullptr){
|
|
|
|
qCritical() << "Cannot create transaction; no wallet loaded";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (churn) {
|
|
|
|
address = this->currentWallet->address(0, 0); // primary address
|
|
|
|
}
|
|
|
|
|
|
|
|
qCritical() << "Creating transaction";
|
|
|
|
this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority);
|
|
|
|
}
|
|
|
|
|
2021-03-27 18:59:21 +00:00
|
|
|
void AppContext::onCreateTransaction(QString address, quint64 amount, QString description, bool all) {
|
2020-10-07 10:36:04 +00:00
|
|
|
// tx creation
|
|
|
|
this->tmpTxDescription = description;
|
|
|
|
|
|
|
|
if(this->currentWallet == nullptr) {
|
|
|
|
emit createTransactionError("Cannot create transaction; no wallet loaded");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-31 03:26:03 +00:00
|
|
|
if (!all && amount == 0) {
|
2020-10-07 10:36:04 +00:00
|
|
|
emit createTransactionError("Cannot send nothing");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-31 03:26:03 +00:00
|
|
|
auto balance = this->currentWallet->balance();
|
|
|
|
auto unlocked_balance = this->currentWallet->unlockedBalance();
|
2020-10-07 10:36:04 +00:00
|
|
|
if(!all && amount > unlocked_balance) {
|
|
|
|
emit createTransactionError("Not enough money to spend");
|
|
|
|
return;
|
|
|
|
} else if(unlocked_balance == 0) {
|
|
|
|
emit createTransactionError("No money to spend");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << "creating tx";
|
2020-12-31 03:26:03 +00:00
|
|
|
if (all)
|
2020-10-07 10:36:04 +00:00
|
|
|
this->currentWallet->createTransactionAllAsync(address, "", this->tx_mixin, this->tx_priority);
|
|
|
|
else
|
2020-12-31 03:26:03 +00:00
|
|
|
this->currentWallet->createTransactionAsync(address, "", amount, this->tx_mixin, this->tx_priority);
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
emit initiateTransaction();
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:55:27 +00:00
|
|
|
void AppContext::onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description) {
|
|
|
|
this->tmpTxDescription = description;
|
|
|
|
|
|
|
|
if (this->currentWallet == nullptr) {
|
|
|
|
emit createTransactionError("Cannot create transaction; no wallet loaded");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
quint64 total_amount = 0;
|
|
|
|
for (auto &amount : amounts) {
|
|
|
|
total_amount += amount;
|
|
|
|
}
|
|
|
|
|
|
|
|
auto unlocked_balance = this->currentWallet->unlockedBalance();
|
|
|
|
if (total_amount > unlocked_balance) {
|
|
|
|
emit createTransactionError("Not enough money to spend");
|
|
|
|
}
|
|
|
|
|
|
|
|
qDebug() << "Creating tx";
|
|
|
|
this->currentWallet->createTransactionMultiDestAsync(addresses, amounts, this->tx_priority);
|
|
|
|
|
|
|
|
emit initiateTransaction();
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
void AppContext::onCreateTransactionError(const QString &msg) {
|
|
|
|
this->tmpTxDescription = "";
|
|
|
|
emit endTransaction();
|
|
|
|
}
|
|
|
|
|
2021-01-19 22:39:31 +00:00
|
|
|
void AppContext::closeWallet(bool emitClosedSignal, bool storeWallet) {
|
|
|
|
if (this->currentWallet == nullptr)
|
|
|
|
return;
|
|
|
|
|
|
|
|
emit walletAboutToClose();
|
|
|
|
|
|
|
|
if (storeWallet) {
|
|
|
|
this->storeWallet();
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
this->currentWallet->disconnect();
|
|
|
|
this->walletManager->closeWallet();
|
2021-01-19 22:39:31 +00:00
|
|
|
this->currentWallet = nullptr;
|
|
|
|
|
|
|
|
if (emitClosedSignal)
|
2020-10-07 10:36:04 +00:00
|
|
|
emit walletClosed();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onOpenWallet(const QString &path, const QString &password){
|
|
|
|
if(this->currentWallet != nullptr){
|
|
|
|
emit walletOpenedError("There is an active wallet opened.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!Utils::fileExists(path)) {
|
|
|
|
emit walletOpenedError(QString("Wallet not found: %1").arg(path));
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-11-24 13:40:17 +00:00
|
|
|
if (password.isEmpty()) {
|
|
|
|
this->walletPassword = "";
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
config()->set(Config::firstRun, false);
|
|
|
|
|
|
|
|
this->walletPath = path;
|
|
|
|
this->walletManager->openWalletAsync(path, password, this->networkType, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onPreferredFiatCurrencyChanged(const QString &symbol) {
|
|
|
|
if(this->currentWallet) {
|
|
|
|
auto *model = this->currentWallet->transactionHistoryModel();
|
|
|
|
if(model != nullptr) {
|
|
|
|
model->preferredFiatSymbol = symbol;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onWalletOpened(Wallet *wallet) {
|
|
|
|
auto state = wallet->status();
|
|
|
|
if (state != Wallet::Status_Ok) {
|
|
|
|
auto errMsg = wallet->errorString();
|
|
|
|
if(errMsg == QString("basic_string::_M_replace_aux") || errMsg == QString("std::bad_alloc")) {
|
|
|
|
qCritical() << errMsg;
|
|
|
|
this->walletManager->clearWalletCache(this->walletPath);
|
2021-02-22 06:51:09 +00:00
|
|
|
errMsg = QString("%1\n\nAttempted to clean wallet cache. Please restart WOWlet.").arg(errMsg);
|
2021-01-19 22:39:31 +00:00
|
|
|
this->closeWallet(false);
|
2020-10-07 10:36:04 +00:00
|
|
|
emit walletOpenedError(errMsg);
|
|
|
|
} else if(errMsg.contains("wallet cannot be opened as")) {
|
2021-01-19 22:39:31 +00:00
|
|
|
this->closeWallet(false);
|
2020-10-07 10:36:04 +00:00
|
|
|
emit walletOpenedError(errMsg);
|
|
|
|
} else if(errMsg.contains("is opened by another wallet program")) {
|
2021-01-19 22:39:31 +00:00
|
|
|
this->closeWallet(false);
|
2020-10-07 10:36:04 +00:00
|
|
|
emit walletOpenedError(errMsg);
|
|
|
|
} else {
|
2021-01-19 22:39:31 +00:00
|
|
|
this->closeWallet(false);
|
2020-11-24 13:40:17 +00:00
|
|
|
emit walletOpenPasswordNeeded(!this->walletPassword.isEmpty(), wallet->path());
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-12-30 03:58:17 +00:00
|
|
|
this->refreshed = false;
|
2020-10-07 10:36:04 +00:00
|
|
|
this->currentWallet = wallet;
|
|
|
|
this->walletPath = this->currentWallet->path() + ".keys";
|
2021-04-02 12:22:37 +00:00
|
|
|
QFileInfo fileInfo(this->currentWallet->path());
|
|
|
|
this->walletName = fileInfo.fileName();
|
2020-10-14 20:12:32 +00:00
|
|
|
this->walletViewOnly = this->currentWallet->viewOnly();
|
2020-10-07 10:36:04 +00:00
|
|
|
config()->set(Config::walletPath, this->walletPath);
|
|
|
|
|
|
|
|
connect(this->currentWallet, &Wallet::moneySpent, this, &AppContext::onMoneySpent);
|
|
|
|
connect(this->currentWallet, &Wallet::moneyReceived, this, &AppContext::onMoneyReceived);
|
|
|
|
connect(this->currentWallet, &Wallet::unconfirmedMoneyReceived, this, &AppContext::onUnconfirmedMoneyReceived);
|
|
|
|
connect(this->currentWallet, &Wallet::newBlock, this, &AppContext::onWalletNewBlock);
|
|
|
|
connect(this->currentWallet, &Wallet::updated, this, &AppContext::onWalletUpdate);
|
|
|
|
connect(this->currentWallet, &Wallet::refreshed, this, &AppContext::onWalletRefreshed);
|
|
|
|
connect(this->currentWallet, &Wallet::transactionCommitted, this, &AppContext::onTransactionCommitted);
|
|
|
|
connect(this->currentWallet, &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed);
|
|
|
|
connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated);
|
|
|
|
|
2021-03-27 18:59:21 +00:00
|
|
|
emit walletOpened(wallet);
|
2020-10-15 00:19:13 +00:00
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
connect(this->currentWallet, &Wallet::connectionStatusChanged, [this]{
|
|
|
|
this->nodes->autoConnect();
|
|
|
|
});
|
2020-10-15 00:19:13 +00:00
|
|
|
this->nodes->connectToNode();
|
2020-10-07 10:36:04 +00:00
|
|
|
this->updateBalance();
|
|
|
|
|
|
|
|
#ifdef DONATE_BEG
|
|
|
|
this->donateBeg();
|
|
|
|
#endif
|
|
|
|
|
|
|
|
// force trigger preferredFiat signal for history model
|
|
|
|
this->onPreferredFiatCurrencyChanged(config()->get(Config::preferredFiatCurrency).toString());
|
2020-10-14 20:12:32 +00:00
|
|
|
this->setWindowTitle();
|
|
|
|
}
|
2020-10-12 22:01:06 +00:00
|
|
|
|
2020-10-14 20:12:32 +00:00
|
|
|
void AppContext::setWindowTitle(bool mining) {
|
2020-10-12 22:01:06 +00:00
|
|
|
QFileInfo fileInfo(this->walletPath);
|
2021-02-22 06:51:09 +00:00
|
|
|
auto title = QString("WOWlet - [%1]").arg(fileInfo.fileName());
|
2020-10-14 20:12:32 +00:00
|
|
|
if(this->walletViewOnly)
|
2020-10-12 22:01:06 +00:00
|
|
|
title += " [view-only]";
|
2020-10-14 20:12:32 +00:00
|
|
|
if(mining)
|
|
|
|
title += " [mining]";
|
|
|
|
|
2020-10-12 22:01:06 +00:00
|
|
|
emit setTitle(title);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onWSMessage(const QJsonObject &msg) {
|
|
|
|
QString cmd = msg.value("cmd").toString();
|
|
|
|
|
|
|
|
if(cmd == "blockheights") {
|
|
|
|
auto heights = msg.value("data").toObject();
|
|
|
|
auto mainnet = heights.value("mainnet").toInt();
|
|
|
|
auto stagenet = heights.value("stagenet").toInt();
|
|
|
|
auto changed = false;
|
|
|
|
|
|
|
|
if(!this->heights.contains("mainnet")) {
|
2020-12-24 13:34:37 +00:00
|
|
|
this->heights["mainnet"] = mainnet;
|
2020-10-07 10:36:04 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (mainnet > this->heights["mainnet"]) {
|
2020-12-24 13:34:37 +00:00
|
|
|
this->heights["mainnet"] = mainnet;
|
2020-10-07 10:36:04 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(!this->heights.contains("stagenet")) {
|
2020-12-24 13:34:37 +00:00
|
|
|
this->heights["stagenet"] = stagenet;
|
2020-10-07 10:36:04 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (stagenet > this->heights["stagenet"]) {
|
2020-12-24 13:34:37 +00:00
|
|
|
this->heights["stagenet"] = stagenet;
|
2020-10-07 10:36:04 +00:00
|
|
|
changed = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(changed)
|
|
|
|
emit blockHeightWSUpdated(this->heights);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if(cmd == "nodes") {
|
|
|
|
this->onWSNodes(msg.value("data").toArray());
|
|
|
|
}
|
2020-10-17 21:14:56 +00:00
|
|
|
#if defined(HAS_XMRIG)
|
2020-10-14 20:12:32 +00:00
|
|
|
else if(cmd == "xmrig") {
|
|
|
|
this->XMRigDownloads(msg.value("data").toObject());
|
|
|
|
}
|
|
|
|
#endif
|
2020-10-07 10:36:04 +00:00
|
|
|
else if(cmd == "crypto_rates") {
|
|
|
|
QJsonArray crypto_rates = msg.value("data").toArray();
|
|
|
|
AppContext::prices->cryptoPricesReceived(crypto_rates);
|
|
|
|
}
|
|
|
|
|
|
|
|
else if(cmd == "fiat_rates") {
|
|
|
|
QJsonObject fiat_rates = msg.value("data").toObject();
|
|
|
|
AppContext::prices->fiatPricesReceived(fiat_rates);
|
|
|
|
}
|
|
|
|
else if(cmd == "reddit") {
|
|
|
|
QJsonArray reddit_data = msg.value("data").toArray();
|
|
|
|
this->onWSReddit(reddit_data);
|
|
|
|
}
|
|
|
|
|
2020-12-21 20:34:36 +00:00
|
|
|
else if(cmd == "wfs") {
|
2020-10-07 10:36:04 +00:00
|
|
|
auto ccs_data = msg.value("data").toArray();
|
|
|
|
this->onWSCCS(ccs_data);
|
|
|
|
}
|
|
|
|
|
2021-01-06 00:24:08 +00:00
|
|
|
else if(cmd == "suchwow") {
|
|
|
|
QJsonArray such_data = msg.value("data").toArray();
|
|
|
|
emit suchWowUpdated(such_data);
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
else if(cmd == "txFiatHistory") {
|
|
|
|
auto txFiatHistory_data = msg.value("data").toObject();
|
|
|
|
AppContext::txFiatHistory->onWSData(txFiatHistory_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onWSNodes(const QJsonArray &nodes) {
|
2021-03-30 09:52:29 +00:00
|
|
|
QList<QSharedPointer<WowletNode>> l;
|
2020-10-07 10:36:04 +00:00
|
|
|
for (auto &&entry: nodes) {
|
|
|
|
auto obj = entry.toObject();
|
|
|
|
auto nettype = obj.value("nettype");
|
|
|
|
auto type = obj.value("type");
|
|
|
|
|
|
|
|
// filter remote node network types
|
|
|
|
if(nettype == "mainnet" && this->networkType != NetworkType::MAINNET)
|
|
|
|
continue;
|
|
|
|
if(nettype == "stagenet" && this->networkType != NetworkType::STAGENET)
|
|
|
|
continue;
|
|
|
|
if(nettype == "testnet" && this->networkType != NetworkType::TESTNET)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if(type == "clearnet" && (this->isTails || this->isWhonix || this->isTorSocks))
|
|
|
|
continue;
|
|
|
|
if(type == "tor" && (!(this->isTails || this->isWhonix || this->isTorSocks)))
|
|
|
|
continue;
|
|
|
|
|
2021-03-30 09:52:29 +00:00
|
|
|
auto node = new WowletNode(
|
2020-10-07 10:36:04 +00:00
|
|
|
obj.value("address").toString(),
|
2020-11-14 21:42:41 +00:00
|
|
|
obj.value("height").toInt(),
|
|
|
|
obj.value("target_height").toInt(),
|
2020-10-07 10:36:04 +00:00
|
|
|
obj.value("online").toBool());
|
2021-03-30 09:52:29 +00:00
|
|
|
QSharedPointer<WowletNode> r = QSharedPointer<WowletNode>(node);
|
2020-10-07 10:36:04 +00:00
|
|
|
l.append(r);
|
|
|
|
}
|
|
|
|
this->nodes->onWSNodesReceived(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onWSReddit(const QJsonArray& reddit_data) {
|
|
|
|
QList<QSharedPointer<RedditPost>> l;
|
|
|
|
|
|
|
|
for (auto &&entry: reddit_data) {
|
|
|
|
auto obj = entry.toObject();
|
|
|
|
auto redditPost = new RedditPost(
|
|
|
|
obj.value("title").toString(),
|
|
|
|
obj.value("author").toString(),
|
2020-12-30 02:48:10 +00:00
|
|
|
obj.value("permalink").toString(),
|
2020-10-07 10:36:04 +00:00
|
|
|
obj.value("comments").toInt());
|
|
|
|
QSharedPointer<RedditPost> r = QSharedPointer<RedditPost>(redditPost);
|
|
|
|
l.append(r);
|
|
|
|
}
|
|
|
|
|
|
|
|
emit redditUpdated(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onWSCCS(const QJsonArray &ccs_data) {
|
|
|
|
QList<QSharedPointer<CCSEntry>> l;
|
|
|
|
|
|
|
|
|
|
|
|
QStringList fonts = {"state", "address", "author", "date",
|
|
|
|
"title", "target_amount", "raised_amount",
|
|
|
|
"percentage_funded", "contributions"};
|
|
|
|
|
|
|
|
for (auto &&entry: ccs_data) {
|
|
|
|
auto obj = entry.toObject();
|
|
|
|
auto c = QSharedPointer<CCSEntry>(new CCSEntry());
|
|
|
|
|
|
|
|
if (obj.value("state").toString() != "FUNDING-REQUIRED")
|
|
|
|
continue;
|
|
|
|
|
|
|
|
c->state = obj.value("state").toString();
|
|
|
|
c->address = obj.value("address").toString();
|
|
|
|
c->author = obj.value("author").toString();
|
|
|
|
c->date = obj.value("date").toString();
|
|
|
|
c->title = obj.value("title").toString();
|
|
|
|
c->url = obj.value("url").toString();
|
|
|
|
c->target_amount = obj.value("target_amount").toDouble();
|
|
|
|
c->raised_amount = obj.value("raised_amount").toDouble();
|
|
|
|
c->percentage_funded = obj.value("percentage_funded").toDouble();
|
|
|
|
c->contributions = obj.value("contributions").toInt();
|
|
|
|
l.append(c);
|
|
|
|
}
|
|
|
|
|
|
|
|
emit ccsUpdated(l);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::createConfigDirectory(const QString &dir) {
|
|
|
|
QString config_dir_tor = QString("%1%2").arg(dir).arg("tor");
|
|
|
|
QString config_dir_tordata = QString("%1%2").arg(dir).arg("tor/data");
|
2020-10-17 21:14:56 +00:00
|
|
|
|
2020-12-11 13:36:08 +00:00
|
|
|
QStringList createDirs({dir, config_dir_tor, config_dir_tordata});
|
2020-10-17 21:14:56 +00:00
|
|
|
for(const auto &d: createDirs) {
|
|
|
|
if(!Utils::dirExists(d)) {
|
|
|
|
qDebug() << QString("Creating directory: %1").arg(d);
|
|
|
|
if (!QDir().mkpath(d))
|
|
|
|
throw std::runtime_error("Could not create directory " + d.toStdString());
|
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-02 12:22:37 +00:00
|
|
|
void AppContext::createWalletWithoutSpecifyingSeed(const QString &name, const QString &password) {
|
|
|
|
WowletSeed seed = WowletSeed(this->restoreHeights[this->networkType], this->coinName, this->seedLanguage);
|
|
|
|
auto path = QDir(this->defaultWalletDir).filePath(name);
|
|
|
|
this->createWallet(seed, path, password);
|
|
|
|
}
|
|
|
|
|
2021-03-30 09:52:29 +00:00
|
|
|
void AppContext::createWallet(WowletSeed seed, const QString &path, const QString &password) {
|
2020-10-07 10:36:04 +00:00
|
|
|
if(Utils::fileExists(path)) {
|
|
|
|
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
|
|
|
|
qCritical() << err;
|
|
|
|
emit walletCreatedError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-20 21:13:51 +00:00
|
|
|
if(seed.mnemonic.isEmpty()) {
|
2020-10-11 15:08:34 +00:00
|
|
|
emit walletCreatedError("Mnemonic seed error. Failed to write wallet.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-01-20 21:13:51 +00:00
|
|
|
Wallet *wallet = nullptr;
|
|
|
|
if (seed.seedType == SeedType::TEVADOR) {
|
|
|
|
wallet = this->walletManager->createDeterministicWalletFromSpendKey(path, password, seed.language, this->networkType, seed.spendKey, seed.restoreHeight, this->kdfRounds);
|
2021-03-30 09:52:29 +00:00
|
|
|
wallet->setCacheAttribute("wowlet.seed", seed.mnemonic.join(" "));
|
2021-01-20 21:13:51 +00:00
|
|
|
}
|
|
|
|
if (seed.seedType == SeedType::MONERO) {
|
|
|
|
wallet = this->walletManager->recoveryWallet(path, password, seed.mnemonic.join(" "), "", this->networkType, seed.restoreHeight, this->kdfRounds);
|
|
|
|
}
|
|
|
|
|
|
|
|
this->currentWallet = wallet;
|
2020-10-07 10:36:04 +00:00
|
|
|
if(this->currentWallet == nullptr) {
|
|
|
|
emit walletCreatedError("Failed to write wallet");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-12 22:01:06 +00:00
|
|
|
this->createWalletFinish(password);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::createWalletViewOnly(const QString &path, const QString &password, const QString &address, const QString &viewkey, const QString &spendkey, quint64 restoreHeight) {
|
|
|
|
if(Utils::fileExists(path)) {
|
|
|
|
auto err = QString("Failed to write wallet to path: \"%1\"; file already exists.").arg(path);
|
|
|
|
qCritical() << err;
|
|
|
|
emit walletCreatedError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!this->walletManager->addressValid(address, this->networkType)) {
|
|
|
|
auto err = QString("Failed to create wallet. Invalid address provided.").arg(path);
|
|
|
|
qCritical() << err;
|
|
|
|
emit walletCreatedError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!this->walletManager->keyValid(viewkey, address, true, this->networkType)) {
|
|
|
|
auto err = QString("Failed to create wallet. Invalid viewkey provided.").arg(path);
|
|
|
|
qCritical() << err;
|
|
|
|
emit walletCreatedError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(!spendkey.isEmpty() && !this->walletManager->keyValid(spendkey, address, false, this->networkType)) {
|
|
|
|
auto err = QString("Failed to create wallet. Invalid spendkey provided.").arg(path);
|
|
|
|
qCritical() << err;
|
|
|
|
emit walletCreatedError(err);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->currentWallet = this->walletManager->createWalletFromKeys(path, this->seedLanguage, this->networkType, address, viewkey, spendkey, restoreHeight);
|
|
|
|
this->createWalletFinish(password);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::createWalletFinish(const QString &password) {
|
2020-10-07 10:36:04 +00:00
|
|
|
this->currentWallet->setPassword(password);
|
|
|
|
this->currentWallet->store();
|
|
|
|
this->walletPassword = password;
|
|
|
|
emit walletCreated(this->currentWallet);
|
2021-04-02 12:22:37 +00:00
|
|
|
|
|
|
|
// emit signal on behalf of walletManager, open wallet
|
|
|
|
this->walletManager->walletOpened(this->currentWallet);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::initRestoreHeights() {
|
2020-12-21 20:34:36 +00:00
|
|
|
restoreHeights[NetworkType::TESTNET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_wownero_mainnet.txt", NetworkType::TESTNET);
|
|
|
|
restoreHeights[NetworkType::STAGENET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_wownero_mainnet.txt", NetworkType::STAGENET);
|
|
|
|
restoreHeights[NetworkType::MAINNET] = RestoreHeightLookup::fromFile(":/assets/restore_heights_wownero_mainnet.txt", NetworkType::MAINNET);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2020-12-24 13:34:37 +00:00
|
|
|
void AppContext::onSetRestoreHeight(quint64 height){
|
2021-03-30 09:52:29 +00:00
|
|
|
auto seed = this->currentWallet->getCacheAttribute("wowlet.seed");
|
2020-10-07 10:36:04 +00:00
|
|
|
if(!seed.isEmpty()) {
|
|
|
|
const auto msg = "This wallet has a 14 word mnemonic seed which has the restore height embedded.";
|
|
|
|
emit setRestoreHeightError(msg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this->currentWallet->setWalletCreationHeight(height);
|
2020-11-14 09:57:06 +00:00
|
|
|
this->currentWallet->setPassword(this->currentWallet->getPassword()); // trigger .keys write
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// nuke wallet cache
|
|
|
|
const auto fn = this->currentWallet->path();
|
|
|
|
this->walletManager->clearWalletCache(fn);
|
|
|
|
|
|
|
|
emit customRestoreHeightSet(height);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onOpenAliasResolve(const QString &openAlias) {
|
|
|
|
// @TODO: calling this freezes for about 1-2 seconds :/
|
|
|
|
const auto result = this->walletManager->resolveOpenAlias(openAlias);
|
|
|
|
const auto spl = result.split("|");
|
|
|
|
auto msg = QString("");
|
|
|
|
if(spl.count() != 2) {
|
|
|
|
msg = "Internal error";
|
|
|
|
emit openAliasResolveError(msg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const auto &status = spl.at(0);
|
|
|
|
const auto &address = spl.at(1);
|
|
|
|
const auto valid = this->walletManager->addressValid(address, this->networkType);
|
|
|
|
if(status == "false"){
|
|
|
|
if(valid){
|
|
|
|
msg = "Address found, but the DNSSEC signatures could not be verified, so this address may be spoofed";
|
|
|
|
emit openAliasResolveError(msg);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
msg = "No valid address found at this OpenAlias address, but the DNSSEC signatures could not be verified, so this may be spoofed";
|
|
|
|
emit openAliasResolveError(msg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
} else if(status != "true") {
|
|
|
|
msg = "Internal error";
|
|
|
|
emit openAliasResolveError(msg);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(valid){
|
|
|
|
emit openAliasResolved(address, openAlias);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
msg = QString("Address validation error.");
|
|
|
|
if(!address.isEmpty())
|
|
|
|
msg += QString(" Perhaps it is of the wrong network type."
|
|
|
|
"\n\nOpenAlias: %1\nAddress: %2").arg(openAlias).arg(address);
|
|
|
|
emit openAliasResolveError(msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::donateBeg() {
|
2020-10-12 22:01:06 +00:00
|
|
|
if(this->currentWallet == nullptr) return;
|
|
|
|
if(this->networkType != NetworkType::Type::MAINNET) return;
|
|
|
|
if(this->currentWallet->viewOnly()) return;
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
auto donationCounter = config()->get(Config::donateBeg).toInt();
|
|
|
|
if(donationCounter == -1)
|
|
|
|
return; // previously donated
|
|
|
|
|
|
|
|
donationCounter += 1;
|
|
|
|
if (donationCounter % m_donationBoundary == 0)
|
|
|
|
emit donationNag();
|
|
|
|
config()->set(Config::donateBeg, donationCounter);
|
|
|
|
}
|
|
|
|
|
2021-01-19 22:39:31 +00:00
|
|
|
AppContext::~AppContext() {}
|
2020-10-07 10:36:04 +00:00
|
|
|
|
|
|
|
// ############################################## LIBWALLET QT #########################################################
|
|
|
|
|
|
|
|
void AppContext::onMoneySpent(const QString &txId, quint64 amount) {
|
2020-10-21 14:52:34 +00:00
|
|
|
auto amount_num = amount / globals::cdiv;
|
2020-10-07 10:36:04 +00:00
|
|
|
qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onMoneyReceived(const QString &txId, quint64 amount) {
|
|
|
|
// Incoming tx included in a block.
|
2020-10-21 14:52:34 +00:00
|
|
|
auto amount_num = amount / globals::cdiv;
|
2020-10-07 10:36:04 +00:00
|
|
|
qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num);
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onUnconfirmedMoneyReceived(const QString &txId, quint64 amount) {
|
|
|
|
// Incoming transaction in pool
|
2020-10-21 14:52:34 +00:00
|
|
|
auto amount_num = amount / globals::cdiv;
|
2020-10-07 10:36:04 +00:00
|
|
|
qDebug() << Q_FUNC_INFO << txId << " " << QString::number(amount_num);
|
|
|
|
|
|
|
|
if(this->currentWallet->synchronized()) {
|
2020-12-21 20:34:36 +00:00
|
|
|
auto notify = QString("%1 WOW (pending)").arg(amount_num);
|
2020-10-07 10:36:04 +00:00
|
|
|
Utils::desktopNotify("Payment received", notify, 5000);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onWalletUpdate() {
|
|
|
|
if (this->currentWallet->synchronized()) {
|
|
|
|
this->refreshModels();
|
2021-01-19 22:39:31 +00:00
|
|
|
this->storeWallet();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
this->updateBalance();
|
|
|
|
}
|
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
void AppContext::onWalletRefreshed(bool success) {
|
2020-10-07 10:36:04 +00:00
|
|
|
if (!this->refreshed) {
|
|
|
|
refreshModels();
|
|
|
|
this->refreshed = true;
|
2020-12-30 03:58:17 +00:00
|
|
|
emit walletRefreshed();
|
2020-12-18 22:07:27 +00:00
|
|
|
// store wallet immediately upon finishing synchronization
|
|
|
|
this->currentWallet->store();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
qDebug() << "Wallet refresh status: " << success;
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
this->currentWallet->refreshHeightAsync();
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onWalletNewBlock(quint64 blockheight, quint64 targetHeight) {
|
|
|
|
this->syncStatusUpdated(blockheight, targetHeight);
|
|
|
|
|
|
|
|
if (this->currentWallet->synchronized()) {
|
|
|
|
this->currentWallet->coins()->refreshUnlocked();
|
|
|
|
this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount());
|
|
|
|
// Todo: only refresh tx confirmations
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight) {
|
|
|
|
qDebug() << Q_FUNC_INFO << walletHeight << daemonHeight << targetHeight;
|
|
|
|
|
2020-11-23 16:57:38 +00:00
|
|
|
if (this->currentWallet->connectionStatus() == Wallet::ConnectionStatus_Disconnected)
|
2020-10-07 10:36:04 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
if (daemonHeight < targetHeight) {
|
|
|
|
emit blockchainSync(daemonHeight, targetHeight);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this->syncStatusUpdated(walletHeight, daemonHeight);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-26 23:55:27 +00:00
|
|
|
void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QString> &address) {
|
|
|
|
for (auto &addr : address) {
|
|
|
|
if (addr == this->donationAddress) {
|
|
|
|
this->donationSending = true;
|
|
|
|
}
|
|
|
|
}
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2020-12-24 14:46:56 +00:00
|
|
|
// Let UI know that the transaction was constructed
|
|
|
|
emit endTransaction();
|
|
|
|
|
2021-03-27 18:59:21 +00:00
|
|
|
// Some validation
|
|
|
|
auto tx_status = tx->status();
|
|
|
|
auto err = QString("Can't create transaction: ");
|
|
|
|
|
|
|
|
if(tx_status != PendingTransaction::Status_Ok){
|
|
|
|
auto tx_err = tx->errorString();
|
|
|
|
qCritical() << tx_err;
|
|
|
|
|
|
|
|
if (this->currentWallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
|
|
|
|
err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err);
|
|
|
|
else
|
|
|
|
err = QString("%1 %2").arg(err).arg(tx_err);
|
|
|
|
|
|
|
|
qDebug() << Q_FUNC_INFO << err;
|
|
|
|
emit createTransactionError(err);
|
|
|
|
this->currentWallet->disposeTransaction(tx);
|
|
|
|
return;
|
|
|
|
} else if (tx->txCount() == 0) {
|
|
|
|
err = QString("%1 %2").arg(err).arg("No unmixable outputs to sweep.");
|
|
|
|
qDebug() << Q_FUNC_INFO << err;
|
|
|
|
emit createTransactionError(err);
|
|
|
|
this->currentWallet->disposeTransaction(tx);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
// tx created, but not sent yet. ask user to verify first.
|
2021-01-26 23:55:27 +00:00
|
|
|
emit createTransactionSuccess(tx, address);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){
|
|
|
|
this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount());
|
|
|
|
this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount());
|
2020-12-14 01:15:56 +00:00
|
|
|
|
|
|
|
// Store wallet immediately so we don't risk losing tx key if wallet crashes
|
|
|
|
this->currentWallet->store();
|
|
|
|
|
2020-12-31 02:22:37 +00:00
|
|
|
this->updateBalance();
|
|
|
|
|
2020-10-07 10:36:04 +00:00
|
|
|
emit transactionCommitted(status, tx, txid);
|
|
|
|
|
2021-03-30 09:52:29 +00:00
|
|
|
// this tx was a donation to WOWlet, stop our nagging
|
2020-12-14 00:59:32 +00:00
|
|
|
if(this->donationSending) {
|
|
|
|
this->donationSending = false;
|
2020-10-07 10:36:04 +00:00
|
|
|
config()->set(Config::donateBeg, -1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::storeWallet() {
|
2021-01-19 22:39:31 +00:00
|
|
|
// Do not store a synchronizing wallet: store() is NOT thread safe and may crash the wallet
|
|
|
|
if (this->currentWallet == nullptr || !this->currentWallet->synchronized())
|
2020-10-07 10:36:04 +00:00
|
|
|
return;
|
|
|
|
|
2021-01-19 22:39:31 +00:00
|
|
|
qDebug() << "Storing wallet";
|
|
|
|
this->currentWallet->store();
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::updateBalance() {
|
2020-12-25 14:20:39 +00:00
|
|
|
if (!this->currentWallet)
|
2020-11-02 10:03:08 +00:00
|
|
|
return;
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2020-12-25 14:20:39 +00:00
|
|
|
quint64 balance_u = this->currentWallet->balance();
|
|
|
|
AppContext::balance = balance_u / globals::cdiv;
|
|
|
|
double spendable = this->currentWallet->unlockedBalance();
|
2020-10-07 10:36:04 +00:00
|
|
|
|
2021-04-02 12:22:37 +00:00
|
|
|
// formatted
|
|
|
|
QString fmt_str = QString("Balance: %1 WOW").arg(Utils::balanceFormat(spendable));
|
|
|
|
if (balance > spendable)
|
|
|
|
fmt_str += QString(" (+%1 WOW unconfirmed)").arg(Utils::balanceFormat(balance - spendable));
|
|
|
|
|
2020-12-25 14:20:39 +00:00
|
|
|
emit balanceUpdated(balance_u, spendable);
|
2021-04-02 12:22:37 +00:00
|
|
|
emit balanceUpdatedFormatted(fmt_str);
|
2020-10-07 10:36:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::syncStatusUpdated(quint64 height, quint64 target) {
|
|
|
|
if (height < (target - 1)) {
|
|
|
|
emit refreshSync(height, target);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
this->updateBalance();
|
|
|
|
emit synchronized();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void AppContext::refreshModels() {
|
|
|
|
if (!this->currentWallet)
|
|
|
|
return;
|
|
|
|
|
|
|
|
this->currentWallet->history()->refresh(this->currentWallet->currentSubaddressAccount());
|
|
|
|
this->currentWallet->subaddress()->refresh(this->currentWallet->currentSubaddressAccount());
|
|
|
|
this->currentWallet->coins()->refresh(this->currentWallet->currentSubaddressAccount());
|
|
|
|
// Todo: set timer for refreshes
|
|
|
|
}
|