mirror of
https://git.wownero.com/wowlet/wowlet.git
synced 2024-08-15 01:03:14 +00:00
This commit introduces a websocket server via the --daemon
argument.
``` ./wowlet --daemon 127.0.0.1:1234 --daemon-password "sekrit" ``` The wallet will start in the background and expose a websocket port that you can connect to using a websocket client. This way, you will be able to control the wallet via websockets. The commands are defined in wsserver.cpp, in the `processBinaryMessage()` function. - `openWallet` - opens a wallet by path/password. - `closeWallet` - close current wallet. - `addressList` - Returns a list of receive addresses. - `sendTransaction` - Creates and sends a transaction. - `createWallet` - Create a wallet by path/password. - `transactionHistory` - Returns the complete list of transactions - `addressBook` - Returns the complete list of address book entries. Messages sent back and forth between the server and client are JSON. There is a Python example client available over at https://git.wownero.com/wownero/wowlet-ws-client
This commit is contained in:
parent
40a575a5d6
commit
8968a8cbce
24 changed files with 732 additions and 80 deletions
|
@ -23,6 +23,7 @@ QMap<QString, QString> AppContext::txDescriptionCache;
|
||||||
QMap<QString, QString> AppContext::txCache;
|
QMap<QString, QString> AppContext::txCache;
|
||||||
|
|
||||||
AppContext::AppContext(QCommandLineParser *cmdargs) {
|
AppContext::AppContext(QCommandLineParser *cmdargs) {
|
||||||
|
this->m_walletKeysFilesModel = new WalletKeysFilesModel(this, this);
|
||||||
this->network = new QNetworkAccessManager();
|
this->network = new QNetworkAccessManager();
|
||||||
this->networkClearnet = new QNetworkAccessManager();
|
this->networkClearnet = new QNetworkAccessManager();
|
||||||
this->cmdargs = cmdargs;
|
this->cmdargs = cmdargs;
|
||||||
|
@ -193,7 +194,7 @@ void AppContext::onSweepOutput(const QString &keyImage, QString address, bool ch
|
||||||
this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority);
|
this->currentWallet->createTransactionSingleAsync(keyImage, address, outputs, this->tx_priority);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AppContext::onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all) {
|
void AppContext::onCreateTransaction(QString address, quint64 amount, QString description, bool all) {
|
||||||
// tx creation
|
// tx creation
|
||||||
this->tmpTxDescription = description;
|
this->tmpTxDescription = description;
|
||||||
|
|
||||||
|
@ -342,7 +343,7 @@ void AppContext::onWalletOpened(Wallet *wallet) {
|
||||||
connect(this->currentWallet, &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed);
|
connect(this->currentWallet, &Wallet::heightRefreshed, this, &AppContext::onHeightRefreshed);
|
||||||
connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated);
|
connect(this->currentWallet, &Wallet::transactionCreated, this, &AppContext::onTransactionCreated);
|
||||||
|
|
||||||
emit walletOpened();
|
emit walletOpened(wallet);
|
||||||
|
|
||||||
connect(this->currentWallet, &Wallet::connectionStatusChanged, [this]{
|
connect(this->currentWallet, &Wallet::connectionStatusChanged, [this]{
|
||||||
this->nodes->autoConnect();
|
this->nodes->autoConnect();
|
||||||
|
@ -770,6 +771,31 @@ void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QStr
|
||||||
// Let UI know that the transaction was constructed
|
// Let UI know that the transaction was constructed
|
||||||
emit endTransaction();
|
emit endTransaction();
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
}
|
||||||
|
|
||||||
// tx created, but not sent yet. ask user to verify first.
|
// tx created, but not sent yet. ask user to verify first.
|
||||||
emit createTransactionSuccess(tx, address);
|
emit createTransactionSuccess(tx, address);
|
||||||
}
|
}
|
||||||
|
|
|
@ -89,6 +89,12 @@ public:
|
||||||
static QMap<QString, QString> txCache;
|
static QMap<QString, QString> txCache;
|
||||||
static TxFiatHistory *txFiatHistory;
|
static TxFiatHistory *txFiatHistory;
|
||||||
|
|
||||||
|
QList<WalletKeysFiles> listWallets() {
|
||||||
|
// return listing of wallet .keys items
|
||||||
|
m_walletKeysFilesModel->refresh();
|
||||||
|
return m_walletKeysFilesModel->listWallets();
|
||||||
|
}
|
||||||
|
|
||||||
// libwalletqt
|
// libwalletqt
|
||||||
bool refreshed = false;
|
bool refreshed = false;
|
||||||
WalletManager *walletManager;
|
WalletManager *walletManager;
|
||||||
|
@ -111,7 +117,7 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onOpenWallet(const QString& path, const QString &password);
|
void onOpenWallet(const QString& path, const QString &password);
|
||||||
void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all);
|
void onCreateTransaction(QString address, quint64 amount, const QString description, bool all);
|
||||||
void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
|
void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
|
||||||
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
|
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
|
||||||
void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const;
|
void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const;
|
||||||
|
@ -150,7 +156,7 @@ signals:
|
||||||
void synchronized();
|
void synchronized();
|
||||||
void blockHeightWSUpdated(QMap<QString, int> heights);
|
void blockHeightWSUpdated(QMap<QString, int> heights);
|
||||||
void walletRefreshed();
|
void walletRefreshed();
|
||||||
void walletOpened();
|
void walletOpened(Wallet *wallet);
|
||||||
void walletCreatedError(const QString &msg);
|
void walletCreatedError(const QString &msg);
|
||||||
void walletCreated(Wallet *wallet);
|
void walletCreated(Wallet *wallet);
|
||||||
void walletOpenedError(QString msg);
|
void walletOpenedError(QString msg);
|
||||||
|
@ -177,6 +183,7 @@ signals:
|
||||||
void setTitle(const QString &title); // set window title
|
void setTitle(const QString &title); // set window title
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
WalletKeysFilesModel *m_walletKeysFilesModel;
|
||||||
const int m_donationBoundary = 15;
|
const int m_donationBoundary = 15;
|
||||||
QTimer m_storeTimer;
|
QTimer m_storeTimer;
|
||||||
QUrl m_wsUrl = QUrl(QStringLiteral("ws://feathercitimllbmdktu6cmjo3fizgmyfrntntqzu6xguqa2rlq5cgid.onion/ws"));
|
QUrl m_wsUrl = QUrl(QStringLiteral("ws://feathercitimllbmdktu6cmjo3fizgmyfrntntqzu6xguqa2rlq5cgid.onion/ws"));
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
// Copyright (c) 2020-2021, The Monero Project.
|
// Copyright (c) 2020-2021, The Monero Project.
|
||||||
|
|
||||||
#include "cli.h"
|
|
||||||
|
|
||||||
// libwalletqt
|
// libwalletqt
|
||||||
#include "libwalletqt/TransactionHistory.h"
|
#include "libwalletqt/TransactionHistory.h"
|
||||||
#include "model/AddressBookModel.h"
|
#include "model/AddressBookModel.h"
|
||||||
#include "model/TransactionHistoryModel.h"
|
#include "model/TransactionHistoryModel.h"
|
||||||
|
|
||||||
|
#include "cli.h"
|
||||||
|
|
||||||
CLI::CLI(AppContext *ctx, QObject *parent) :
|
CLI::CLI(AppContext *ctx, QObject *parent) :
|
||||||
QObject(parent),
|
QObject(parent),
|
||||||
ctx(ctx) {
|
ctx(ctx) {
|
||||||
|
@ -22,6 +22,8 @@ void CLI::run() {
|
||||||
if(!ctx->cmdargs->isSet("wallet-file")) return this->finishedError("--wallet-file argument missing");
|
if(!ctx->cmdargs->isSet("wallet-file")) return this->finishedError("--wallet-file argument missing");
|
||||||
if(!ctx->cmdargs->isSet("password")) return this->finishedError("--password argument missing");
|
if(!ctx->cmdargs->isSet("password")) return this->finishedError("--password argument missing");
|
||||||
ctx->onOpenWallet(ctx->cmdargs->value("wallet-file"), ctx->cmdargs->value("password"));
|
ctx->onOpenWallet(ctx->cmdargs->value("wallet-file"), ctx->cmdargs->value("password"));
|
||||||
|
} else if(mode == CLIMode::CLIDaemonize) {
|
||||||
|
m_wsServer = new WSServer(ctx, QHostAddress(this->backgroundWebsocketAddress), this->backgroundWebsocketPort, this->backgroundWebsocketPassword, true, this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,12 @@
|
||||||
|
|
||||||
#include <QtCore>
|
#include <QtCore>
|
||||||
#include "appcontext.h"
|
#include "appcontext.h"
|
||||||
|
#include <utils/wsserver.h>
|
||||||
|
|
||||||
enum CLIMode {
|
enum CLIMode {
|
||||||
CLIModeExportContacts,
|
CLIModeExportContacts,
|
||||||
CLIModeExportTxHistory
|
CLIModeExportTxHistory,
|
||||||
|
CLIDaemonize
|
||||||
};
|
};
|
||||||
|
|
||||||
class CLI : public QObject
|
class CLI : public QObject
|
||||||
|
@ -20,6 +22,10 @@ public:
|
||||||
explicit CLI(AppContext *ctx, QObject *parent = nullptr);
|
explicit CLI(AppContext *ctx, QObject *parent = nullptr);
|
||||||
~CLI() override;
|
~CLI() override;
|
||||||
|
|
||||||
|
QString backgroundWebsocketAddress;
|
||||||
|
quint16 backgroundWebsocketPort;
|
||||||
|
QString backgroundWebsocketPassword;
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void run();
|
void run();
|
||||||
|
|
||||||
|
@ -30,6 +36,7 @@ public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
AppContext *ctx;
|
AppContext *ctx;
|
||||||
|
WSServer *m_wsServer;
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void finished(const QString &msg);
|
void finished(const QString &msg);
|
||||||
|
|
|
@ -109,6 +109,25 @@ bool AddressBook::deleteRow(int rowId)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool AddressBook::deleteByAddress(const QString &address) {
|
||||||
|
bool result;
|
||||||
|
QWriteLocker locker(&m_lock);
|
||||||
|
|
||||||
|
const QMap<QString, size_t>::const_iterator it = m_addresses.find(address);
|
||||||
|
if (it == m_addresses.end())
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
result = m_addressBookImpl->deleteRow(*it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch new data from wallet2.
|
||||||
|
if (result)
|
||||||
|
getAll();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
quint64 AddressBook::count() const
|
quint64 AddressBook::count() const
|
||||||
{
|
{
|
||||||
QReadLocker locker(&m_lock);
|
QReadLocker locker(&m_lock);
|
||||||
|
|
|
@ -24,6 +24,7 @@ public:
|
||||||
Q_INVOKABLE bool getRow(int index, std::function<void (AddressBookInfo &)> callback) const;
|
Q_INVOKABLE bool getRow(int index, std::function<void (AddressBookInfo &)> callback) const;
|
||||||
Q_INVOKABLE bool addRow(const QString &address, const QString &payment_id, const QString &description);
|
Q_INVOKABLE bool addRow(const QString &address, const QString &payment_id, const QString &description);
|
||||||
Q_INVOKABLE bool deleteRow(int rowId);
|
Q_INVOKABLE bool deleteRow(int rowId);
|
||||||
|
Q_INVOKABLE bool deleteByAddress(const QString &description);
|
||||||
Q_INVOKABLE void setDescription(int index, const QString &label);
|
Q_INVOKABLE void setDescription(int index, const QString &label);
|
||||||
quint64 count() const;
|
quint64 count() const;
|
||||||
Q_INVOKABLE QString errorString() const;
|
Q_INVOKABLE QString errorString() const;
|
||||||
|
|
|
@ -202,3 +202,74 @@ bool TransactionHistory::writeCSV(const QString &path) {
|
||||||
data = QString("blockHeight,epoch,date,direction,amount,fiat,atomicAmount,fee,txid,label,subaddrAccount,paymentId\n%1").arg(data);
|
data = QString("blockHeight,epoch,date,direction,amount,fiat,atomicAmount,fee,txid,label,subaddrAccount,paymentId\n%1").arg(data);
|
||||||
return Utils::fileWrite(path, data);
|
return Utils::fileWrite(path, data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonArray TransactionHistory::toJsonArray(){
|
||||||
|
QJsonArray return_array;
|
||||||
|
|
||||||
|
for (const auto &tx : m_pimpl->getAll()) {
|
||||||
|
if (tx->subaddrAccount() != 0) { // only account 0
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
TransactionInfo info(tx, this);
|
||||||
|
|
||||||
|
// collect column data
|
||||||
|
QDateTime timeStamp = info.timestamp();
|
||||||
|
double amount = info.amount();
|
||||||
|
|
||||||
|
// calc historical fiat price
|
||||||
|
QString fiatAmount;
|
||||||
|
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString();
|
||||||
|
const double usd_price = AppContext::txFiatHistory->get(timeStamp.toString("yyyyMMdd"));
|
||||||
|
double fiat_price = usd_price * amount;
|
||||||
|
|
||||||
|
if(preferredCur != "USD")
|
||||||
|
fiat_price = AppContext::prices->convert("USD", preferredCur, fiat_price);
|
||||||
|
double fiat_rounded = ceil(Utils::roundSignificant(fiat_price, 3) * 100.0) / 100.0;
|
||||||
|
if(fiat_price != 0)
|
||||||
|
fiatAmount = QString("%1 %2").arg(QString::number(fiat_rounded)).arg(preferredCur);
|
||||||
|
|
||||||
|
// collect some more column data
|
||||||
|
quint64 atomicAmount = info.atomicAmount();
|
||||||
|
quint32 subaddrAccount = info.subaddrAccount();
|
||||||
|
QString fee = info.fee();
|
||||||
|
QString direction = QString("");
|
||||||
|
TransactionInfo::Direction _direction = info.direction();
|
||||||
|
if(_direction == TransactionInfo::Direction_In)
|
||||||
|
direction = QString("in");
|
||||||
|
else if(_direction == TransactionInfo::Direction_Out)
|
||||||
|
direction = QString("out");
|
||||||
|
else
|
||||||
|
continue; // skip TransactionInfo::Direction_Both
|
||||||
|
|
||||||
|
QString label = info.label();
|
||||||
|
quint64 blockHeight = info.blockHeight();
|
||||||
|
QString date = info.date() + " " + info.time();
|
||||||
|
uint epoch = timeStamp.toTime_t();
|
||||||
|
QString displayAmount = info.displayAmount();
|
||||||
|
QString paymentId = info.paymentId();
|
||||||
|
if(paymentId == "0000000000000000")
|
||||||
|
paymentId = "";
|
||||||
|
|
||||||
|
QJsonObject tx_item;
|
||||||
|
tx_item["timestamp"] = (int) epoch;
|
||||||
|
tx_item["date"] = date;
|
||||||
|
tx_item["preferred_currency"] = preferredCur;
|
||||||
|
tx_item["direction"] = direction;
|
||||||
|
tx_item["blockheight"] = (int) blockHeight;
|
||||||
|
tx_item["description"] = label;
|
||||||
|
tx_item["subaddress_account"] = (int) subaddrAccount;
|
||||||
|
tx_item["payment_id"] = paymentId;
|
||||||
|
|
||||||
|
tx_item["amount"] = amount;
|
||||||
|
tx_item["amount_display"] = displayAmount;
|
||||||
|
tx_item["amount_fiat"] = fiatAmount;
|
||||||
|
tx_item["fiat_rounded"] = fiat_rounded;
|
||||||
|
tx_item["fiat_price"] = fiat_price;
|
||||||
|
tx_item["fee"] = fee;
|
||||||
|
|
||||||
|
return_array.append(tx_item);
|
||||||
|
}
|
||||||
|
|
||||||
|
return return_array;
|
||||||
|
}
|
|
@ -32,6 +32,7 @@ public:
|
||||||
Q_INVOKABLE void refresh(quint32 accountIndex);
|
Q_INVOKABLE void refresh(quint32 accountIndex);
|
||||||
Q_INVOKABLE void setTxNote(const QString &txid, const QString ¬e);
|
Q_INVOKABLE void setTxNote(const QString &txid, const QString ¬e);
|
||||||
Q_INVOKABLE bool writeCSV(const QString &path);
|
Q_INVOKABLE bool writeCSV(const QString &path);
|
||||||
|
Q_INVOKABLE QJsonArray toJsonArray();
|
||||||
quint64 count() const;
|
quint64 count() const;
|
||||||
QDateTime firstDateTime() const;
|
QDateTime firstDateTime() const;
|
||||||
QDateTime lastDateTime() const;
|
QDateTime lastDateTime() const;
|
||||||
|
|
|
@ -1206,6 +1206,18 @@ quint64 Wallet::getBytesSent() const {
|
||||||
return m_walletImpl->getBytesSent();
|
return m_walletImpl->getBytesSent();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject Wallet::toJsonObject() {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["path"] = path();
|
||||||
|
obj["password"] = getPassword();
|
||||||
|
obj["address"] = address(0, 0);
|
||||||
|
obj["seed"] = getSeed();
|
||||||
|
obj["seedLanguage"] = getSeedLanguage();
|
||||||
|
obj["networkType"] = nettype();
|
||||||
|
obj["walletCreationHeight"] = (int) getWalletCreationHeight();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
|
void Wallet::onPassphraseEntered(const QString &passphrase, bool enter_on_device, bool entry_abort)
|
||||||
{
|
{
|
||||||
if (m_walletListener != nullptr)
|
if (m_walletListener != nullptr)
|
||||||
|
|
|
@ -450,6 +450,9 @@ public:
|
||||||
Q_INVOKABLE quint64 getBytesReceived() const;
|
Q_INVOKABLE quint64 getBytesReceived() const;
|
||||||
Q_INVOKABLE quint64 getBytesSent() const;
|
Q_INVOKABLE quint64 getBytesSent() const;
|
||||||
|
|
||||||
|
// return as json object
|
||||||
|
QJsonObject toJsonObject();
|
||||||
|
|
||||||
// TODO: setListenter() when it implemented in API
|
// TODO: setListenter() when it implemented in API
|
||||||
signals:
|
signals:
|
||||||
// emitted on every event happened with wallet
|
// emitted on every event happened with wallet
|
||||||
|
|
33
src/main.cpp
33
src/main.cpp
|
@ -78,6 +78,12 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
QCommandLineOption exportTxHistoryOption(QStringList() << "export-txhistory", "Output wallet transaction history as CSV to specified path.", "file");
|
QCommandLineOption exportTxHistoryOption(QStringList() << "export-txhistory", "Output wallet transaction history as CSV to specified path.", "file");
|
||||||
parser.addOption(exportTxHistoryOption);
|
parser.addOption(exportTxHistoryOption);
|
||||||
|
|
||||||
|
QCommandLineOption backgroundOption(QStringList() << "daemon", "Start Feather in the background and start a websocket server (IPv4:port)", "backgroundAddress");
|
||||||
|
parser.addOption(backgroundOption);
|
||||||
|
|
||||||
|
QCommandLineOption backgroundPasswordOption(QStringList() << "daemon-password", "Password for connecting to the wowlet websocket service", "backgroundPassword");
|
||||||
|
parser.addOption(backgroundPasswordOption);
|
||||||
|
|
||||||
auto parsed = parser.parse(argv_);
|
auto parsed = parser.parse(argv_);
|
||||||
if(!parsed) {
|
if(!parsed) {
|
||||||
qCritical() << parser.errorText();
|
qCritical() << parser.errorText();
|
||||||
|
@ -92,7 +98,10 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
bool quiet = parser.isSet(quietModeOption);
|
bool quiet = parser.isSet(quietModeOption);
|
||||||
bool exportContacts = parser.isSet(exportContactsOption);
|
bool exportContacts = parser.isSet(exportContactsOption);
|
||||||
bool exportTxHistory = parser.isSet(exportTxHistoryOption);
|
bool exportTxHistory = parser.isSet(exportTxHistoryOption);
|
||||||
bool cliMode = exportContacts || exportTxHistory;
|
bool backgroundAddressEnabled = parser.isSet(backgroundOption);
|
||||||
|
bool cliMode = exportContacts || exportTxHistory || backgroundAddressEnabled;
|
||||||
|
|
||||||
|
qRegisterMetaType<QVector<QString>>();
|
||||||
|
|
||||||
if(cliMode) {
|
if(cliMode) {
|
||||||
QCoreApplication cli_app(argc, argv);
|
QCoreApplication cli_app(argc, argv);
|
||||||
|
@ -116,6 +125,27 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
if(!quiet)
|
if(!quiet)
|
||||||
qInfo() << "CLI mode: Transaction history export";
|
qInfo() << "CLI mode: Transaction history export";
|
||||||
cli->mode = CLIMode::CLIModeExportTxHistory;
|
cli->mode = CLIMode::CLIModeExportTxHistory;
|
||||||
|
QTimer::singleShot(0, cli, &CLI::run);
|
||||||
|
} else if(backgroundAddressEnabled) {
|
||||||
|
if(!quiet)
|
||||||
|
qInfo() << "CLI mode: daemonize";
|
||||||
|
cli->mode = CLIMode::CLIDaemonize;
|
||||||
|
|
||||||
|
auto backgroundHostPort = parser.value(backgroundOption);
|
||||||
|
if(!backgroundHostPort.contains(":")) {
|
||||||
|
qCritical() << "the format is: --background ipv4:port";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto spl = backgroundHostPort.split(":");
|
||||||
|
cli->backgroundWebsocketAddress = spl.at(0);
|
||||||
|
cli->backgroundWebsocketPort = (quint16) spl.at(1).toInt();
|
||||||
|
cli->backgroundWebsocketPassword = parser.value(backgroundPasswordOption);
|
||||||
|
if(cli->backgroundWebsocketPassword.isEmpty()) {
|
||||||
|
qCritical() << "--daemon-password needs to be set when using --daemon";
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
QTimer::singleShot(0, cli, &CLI::run);
|
QTimer::singleShot(0, cli, &CLI::run);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -161,7 +191,6 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
qInstallMessageHandler(Utils::applicationLogHandler);
|
qInstallMessageHandler(Utils::applicationLogHandler);
|
||||||
qRegisterMetaType<QVector<QString>>();
|
|
||||||
|
|
||||||
auto *mainWindow = new MainWindow(ctx);
|
auto *mainWindow = new MainWindow(ctx);
|
||||||
return QApplication::exec();
|
return QApplication::exec();
|
||||||
|
|
|
@ -139,7 +139,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
|
||||||
ui->fiatTickerLayout->addWidget(m_balanceWidget);
|
ui->fiatTickerLayout->addWidget(m_balanceWidget);
|
||||||
|
|
||||||
// Send widget
|
// Send widget
|
||||||
connect(ui->sendWidget, &SendWidget::createTransaction, m_ctx, QOverload<const QString &, quint64, const QString &, bool>::of(&AppContext::onCreateTransaction));
|
connect(ui->sendWidget, &SendWidget::createTransaction, m_ctx, QOverload<const QString, quint64, const QString, bool>::of(&AppContext::onCreateTransaction));
|
||||||
connect(ui->sendWidget, &SendWidget::createTransactionMultiDest, m_ctx, &AppContext::onCreateTransactionMultiDest);
|
connect(ui->sendWidget, &SendWidget::createTransactionMultiDest, m_ctx, &AppContext::onCreateTransactionMultiDest);
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
|
@ -578,7 +578,7 @@ void MainWindow::onWalletCreated(Wallet *wallet) {
|
||||||
m_ctx->walletManager->walletOpened(wallet);
|
m_ctx->walletManager->walletOpened(wallet);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onWalletOpened() {
|
void MainWindow::onWalletOpened(Wallet *wallet) {
|
||||||
qDebug() << Q_FUNC_INFO;
|
qDebug() << Q_FUNC_INFO;
|
||||||
if(m_wizard != nullptr) {
|
if(m_wizard != nullptr) {
|
||||||
m_wizard->hide();
|
m_wizard->hide();
|
||||||
|
@ -710,59 +710,37 @@ void MainWindow::onConnectionStatusChanged(int status)
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
|
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
|
||||||
auto tx_status = tx->status();
|
const auto &description = m_ctx->tmpTxDescription;
|
||||||
auto err = QString("Can't create transaction: ");
|
|
||||||
|
|
||||||
if(tx_status != PendingTransaction::Status_Ok){
|
// Show advanced dialog on multi-destination transactions
|
||||||
auto tx_err = tx->errorString();
|
if (address.size() > 1) {
|
||||||
qCritical() << tx_err;
|
auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this);
|
||||||
|
dialog_adv->setTransaction(tx);
|
||||||
if (m_ctx->currentWallet->connectionStatus() == Wallet::ConnectionStatus_WrongVersion)
|
dialog_adv->exec();
|
||||||
err = QString("%1 Wrong daemon version: %2").arg(err).arg(tx_err);
|
dialog_adv->deleteLater();
|
||||||
else
|
return;
|
||||||
err = QString("%1 %2").arg(err).arg(tx_err);
|
|
||||||
|
|
||||||
qDebug() << Q_FUNC_INFO << err;
|
|
||||||
QMessageBox::warning(this, "Transactions error", err);
|
|
||||||
m_ctx->currentWallet->disposeTransaction(tx);
|
|
||||||
} else if (tx->txCount() == 0) {
|
|
||||||
err = QString("%1 %2").arg(err).arg("No unmixable outputs to sweep.");
|
|
||||||
qDebug() << Q_FUNC_INFO << err;
|
|
||||||
QMessageBox::warning(this, "Transaction error", err);
|
|
||||||
m_ctx->currentWallet->disposeTransaction(tx);
|
|
||||||
} else {
|
|
||||||
const auto &description = m_ctx->tmpTxDescription;
|
|
||||||
|
|
||||||
// Show advanced dialog on multi-destination transactions
|
|
||||||
if (address.size() > 1) {
|
|
||||||
auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this);
|
|
||||||
dialog_adv->setTransaction(tx);
|
|
||||||
dialog_adv->exec();
|
|
||||||
dialog_adv->deleteLater();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *dialog = new TxConfDialog(m_ctx, tx, address[0], description, this);
|
|
||||||
switch (dialog->exec()) {
|
|
||||||
case QDialog::Rejected:
|
|
||||||
{
|
|
||||||
if (!dialog->showAdvanced)
|
|
||||||
m_ctx->onCancelTransaction(tx, address);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case QDialog::Accepted:
|
|
||||||
m_ctx->currentWallet->commitTransactionAsync(tx);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dialog->showAdvanced) {
|
|
||||||
auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this);
|
|
||||||
dialog_adv->setTransaction(tx);
|
|
||||||
dialog_adv->exec();
|
|
||||||
dialog_adv->deleteLater();
|
|
||||||
}
|
|
||||||
dialog->deleteLater();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto *dialog = new TxConfDialog(m_ctx, tx, address[0], description, this);
|
||||||
|
switch (dialog->exec()) {
|
||||||
|
case QDialog::Rejected:
|
||||||
|
{
|
||||||
|
if (!dialog->showAdvanced)
|
||||||
|
m_ctx->onCancelTransaction(tx, address);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case QDialog::Accepted:
|
||||||
|
m_ctx->currentWallet->commitTransactionAsync(tx);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dialog->showAdvanced) {
|
||||||
|
auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this);
|
||||||
|
dialog_adv->setTransaction(tx);
|
||||||
|
dialog_adv->exec();
|
||||||
|
dialog_adv->deleteLater();
|
||||||
|
}
|
||||||
|
dialog->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
|
void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
|
||||||
|
|
|
@ -138,7 +138,7 @@ public slots:
|
||||||
// libwalletqt
|
// libwalletqt
|
||||||
void onBalanceUpdated(quint64 balance, quint64 spendable);
|
void onBalanceUpdated(quint64 balance, quint64 spendable);
|
||||||
void onSynchronized();
|
void onSynchronized();
|
||||||
void onWalletOpened();
|
void onWalletOpened(Wallet *wallet);
|
||||||
void onWalletClosed(WalletWizard::Page page = WalletWizard::Page_Menu);
|
void onWalletClosed(WalletWizard::Page page = WalletWizard::Page_Menu);
|
||||||
void onConnectionStatusChanged(int status);
|
void onConnectionStatusChanged(int status);
|
||||||
void onCreateTransactionError(const QString &message);
|
void onCreateTransactionError(const QString &message);
|
||||||
|
|
|
@ -154,6 +154,22 @@ int AddressBookModel::lookupPaymentID(const QString &payment_id) const
|
||||||
return m_addressBook->lookupPaymentID(payment_id);
|
return m_addressBook->lookupPaymentID(payment_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonArray AddressBookModel::toJsonArray(){
|
||||||
|
QJsonArray arr;
|
||||||
|
for(int i = 0; i < this->rowCount(); i++) {
|
||||||
|
QJsonObject item;
|
||||||
|
QModelIndex index = this->index(i, 0);
|
||||||
|
const auto description = this->data(index.siblingAtColumn(AddressBookModel::Description), Qt::UserRole).toString().replace("\"", "");
|
||||||
|
const auto address = this->data(index.siblingAtColumn(AddressBookModel::Address), Qt::UserRole).toString();
|
||||||
|
if(address.isEmpty()) continue;
|
||||||
|
|
||||||
|
item["description"] = description;
|
||||||
|
item["address"] = address;
|
||||||
|
arr << item;
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
|
||||||
bool AddressBookModel::writeCSV(const QString &path) {
|
bool AddressBookModel::writeCSV(const QString &path) {
|
||||||
QString csv = "";
|
QString csv = "";
|
||||||
for(int i = 0; i < this->rowCount(); i++) {
|
for(int i = 0; i < this->rowCount(); i++) {
|
||||||
|
|
|
@ -30,6 +30,8 @@ public:
|
||||||
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
Qt::ItemFlags flags(const QModelIndex &index) const override;
|
||||||
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
bool setData(const QModelIndex &index, const QVariant &value, int role) override;
|
||||||
|
|
||||||
|
QJsonArray toJsonArray();
|
||||||
|
|
||||||
Q_INVOKABLE bool deleteRow(int row);
|
Q_INVOKABLE bool deleteRow(int row);
|
||||||
Q_INVOKABLE int lookupPaymentID(const QString &payment_id) const;
|
Q_INVOKABLE int lookupPaymentID(const QString &payment_id) const;
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,7 @@ struct FeatherSeed {
|
||||||
: lookup(lookup), coin(coin), language(language), mnemonic(mnemonic)
|
: lookup(lookup), coin(coin), language(language), mnemonic(mnemonic)
|
||||||
{
|
{
|
||||||
// Generate a new mnemonic if none was given
|
// Generate a new mnemonic if none was given
|
||||||
if (mnemonic.length() == 0) {
|
if (this->mnemonic.length() == 0) {
|
||||||
this->time = std::time(nullptr);
|
this->time = std::time(nullptr);
|
||||||
monero_seed seed(this->time, coin.toStdString());
|
monero_seed seed(this->time, coin.toStdString());
|
||||||
|
|
||||||
|
@ -49,10 +49,10 @@ struct FeatherSeed {
|
||||||
this->setRestoreHeight();
|
this->setRestoreHeight();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mnemonic.length() == 25) {
|
if (this->mnemonic.length() == 25) {
|
||||||
this->seedType = SeedType::MONERO;
|
this->seedType = SeedType::MONERO;
|
||||||
}
|
}
|
||||||
else if (mnemonic.length() == 14) {
|
else if (this->mnemonic.length() == 14) {
|
||||||
this->seedType = SeedType::TEVADOR;
|
this->seedType = SeedType::TEVADOR;
|
||||||
} else {
|
} else {
|
||||||
this->errorString = "Mnemonic seed does not match known type";
|
this->errorString = "Mnemonic seed does not match known type";
|
||||||
|
@ -61,7 +61,7 @@ struct FeatherSeed {
|
||||||
|
|
||||||
if (seedType == SeedType::TEVADOR) {
|
if (seedType == SeedType::TEVADOR) {
|
||||||
try {
|
try {
|
||||||
monero_seed seed(mnemonic.join(" ").toStdString(), coin.toStdString());
|
monero_seed seed(this->mnemonic.join(" ").toStdString(), coin.toStdString());
|
||||||
|
|
||||||
this->time = seed.date();
|
this->time = seed.date();
|
||||||
this->setRestoreHeight();
|
this->setRestoreHeight();
|
||||||
|
|
|
@ -47,6 +47,16 @@ int WalletKeysFiles::networkType() const {
|
||||||
return m_networkType;
|
return m_networkType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QJsonObject WalletKeysFiles::toJsonObject() const {
|
||||||
|
auto item = QJsonObject();
|
||||||
|
item["fileName"] = m_fileName;
|
||||||
|
item["modified"] = m_modified;
|
||||||
|
item["path"] = m_path;
|
||||||
|
item["networkType"] = m_networkType;
|
||||||
|
item["address"] = m_address;
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
WalletKeysFilesModel::WalletKeysFilesModel(AppContext *ctx, QObject *parent)
|
WalletKeysFilesModel::WalletKeysFilesModel(AppContext *ctx, QObject *parent)
|
||||||
: QAbstractTableModel(parent)
|
: QAbstractTableModel(parent)
|
||||||
, m_ctx(ctx)
|
, m_ctx(ctx)
|
||||||
|
|
|
@ -19,6 +19,8 @@ public:
|
||||||
int networkType() const;
|
int networkType() const;
|
||||||
QString address() const;
|
QString address() const;
|
||||||
|
|
||||||
|
QJsonObject toJsonObject() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString m_fileName;
|
QString m_fileName;
|
||||||
qint64 m_modified;
|
qint64 m_modified;
|
||||||
|
@ -52,6 +54,10 @@ public:
|
||||||
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
QStringList walletDirectories;
|
QStringList walletDirectories;
|
||||||
|
|
||||||
|
QList<WalletKeysFiles> listWallets() {
|
||||||
|
return m_walletKeyFiles;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateDirectories();
|
void updateDirectories();
|
||||||
|
|
||||||
|
|
|
@ -77,7 +77,7 @@ void WSClient::onError(QAbstractSocket::SocketError error) {
|
||||||
|
|
||||||
void WSClient::onbinaryMessageReceived(const QByteArray &message) {
|
void WSClient::onbinaryMessageReceived(const QByteArray &message) {
|
||||||
#ifdef QT_DEBUG
|
#ifdef QT_DEBUG
|
||||||
qDebug() << "WebSocket received:" << message;
|
qDebug() << "WebSocket (client) received:" << message;
|
||||||
#endif
|
#endif
|
||||||
if (!Utils::validateJSON(message)) {
|
if (!Utils::validateJSON(message)) {
|
||||||
qCritical() << "Could not interpret WebSocket message as JSON";
|
qCritical() << "Could not interpret WebSocket message as JSON";
|
||||||
|
|
398
src/utils/wsserver.cpp
Normal file
398
src/utils/wsserver.cpp
Normal file
|
@ -0,0 +1,398 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// Copyright (c) 2020-2021, The Monero Project.
|
||||||
|
|
||||||
|
#include "QtWebSockets/qwebsocketserver.h"
|
||||||
|
#include "QtWebSockets/qwebsocket.h"
|
||||||
|
#include <QtCore/QDebug>
|
||||||
|
#include <QtNetwork>
|
||||||
|
#include <QScreen>
|
||||||
|
#include <QMessageBox>
|
||||||
|
#include <QtNetwork>
|
||||||
|
#include <QClipboard>
|
||||||
|
#include <QCompleter>
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QTextCodec>
|
||||||
|
|
||||||
|
#include <model/SubaddressModel.h>
|
||||||
|
#include <model/SubaddressProxyModel.h>
|
||||||
|
#include <model/CoinsModel.h>
|
||||||
|
#include <model/CoinsProxyModel.h>
|
||||||
|
#include "model/AddressBookModel.h"
|
||||||
|
#include "model/TransactionHistoryModel.h"
|
||||||
|
#include "libwalletqt/AddressBook.h"
|
||||||
|
#include "libwalletqt/TransactionHistory.h"
|
||||||
|
#include <globals.h>
|
||||||
|
|
||||||
|
#include "wsserver.h"
|
||||||
|
#include "appcontext.h"
|
||||||
|
#include "utils/utils.h"
|
||||||
|
|
||||||
|
WSServer::WSServer(AppContext *ctx, const QHostAddress &host, const quint16 port, const QString &password, bool debug, QObject *parent) :
|
||||||
|
QObject(parent),
|
||||||
|
m_debug(debug),
|
||||||
|
m_ctx(ctx),
|
||||||
|
m_password(password),
|
||||||
|
m_pWebSocketServer(
|
||||||
|
new QWebSocketServer(QStringLiteral("Feather Daemon WS"),
|
||||||
|
QWebSocketServer::NonSecureMode, this)) {
|
||||||
|
if (!m_pWebSocketServer->listen(QHostAddress::Any, port))
|
||||||
|
return;
|
||||||
|
|
||||||
|
qDebug() << "websocket server listening on port" << port;
|
||||||
|
|
||||||
|
connect(m_pWebSocketServer, &QWebSocketServer::newConnection, this, &WSServer::onNewConnection);
|
||||||
|
connect(m_pWebSocketServer, &QWebSocketServer::closed, this, &WSServer::closed);
|
||||||
|
|
||||||
|
connect(m_ctx, &AppContext::walletClosed, this, &WSServer::onWalletClosed);
|
||||||
|
connect(m_ctx, &AppContext::balanceUpdated, this, &WSServer::onBalanceUpdated);
|
||||||
|
connect(m_ctx, &AppContext::walletOpened, this, &WSServer::onWalletOpened);
|
||||||
|
connect(m_ctx, &AppContext::walletOpenedError, this, &WSServer::onWalletOpenedError);
|
||||||
|
connect(m_ctx, &AppContext::walletCreatedError, this, &WSServer::onWalletCreatedError);
|
||||||
|
connect(m_ctx, &AppContext::walletCreated, this, &WSServer::onWalletCreated);
|
||||||
|
connect(m_ctx, &AppContext::synchronized, this, &WSServer::onSynchronized);
|
||||||
|
connect(m_ctx, &AppContext::blockchainSync, this, &WSServer::onBlockchainSync);
|
||||||
|
connect(m_ctx, &AppContext::refreshSync, this, &WSServer::onRefreshSync);
|
||||||
|
connect(m_ctx, &AppContext::createTransactionError, this, &WSServer::onCreateTransactionError);
|
||||||
|
connect(m_ctx, &AppContext::createTransactionSuccess, this, &WSServer::onCreateTransactionSuccess);
|
||||||
|
connect(m_ctx, &AppContext::transactionCommitted, this, &WSServer::onTransactionCommitted);
|
||||||
|
connect(m_ctx, &AppContext::walletOpenPasswordNeeded, this, &WSServer::onWalletOpenPasswordRequired);
|
||||||
|
connect(m_ctx, &AppContext::initiateTransaction, this, &WSServer::onInitiateTransaction);
|
||||||
|
|
||||||
|
m_walletDir = m_ctx->defaultWalletDir;
|
||||||
|
|
||||||
|
// Bootstrap Tor/websockets
|
||||||
|
m_ctx->initTor();
|
||||||
|
m_ctx->initWS();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString WSServer::connectionId(QWebSocket *pSocket) {
|
||||||
|
return QString("%1#%2").arg(pSocket->peerAddress().toString()).arg(pSocket->peerPort());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onNewConnection() {
|
||||||
|
QWebSocket *pSocket = m_pWebSocketServer->nextPendingConnection();
|
||||||
|
|
||||||
|
connect(pSocket, &QWebSocket::binaryMessageReceived, this, &WSServer::processBinaryMessage);
|
||||||
|
connect(pSocket, &QWebSocket::disconnected, this, &WSServer::socketDisconnected);
|
||||||
|
|
||||||
|
m_clients << pSocket;
|
||||||
|
m_clients_auth[this->connectionId(pSocket)] = false;
|
||||||
|
|
||||||
|
// blast wallet listing on connect
|
||||||
|
QJsonArray arr;
|
||||||
|
for(const WalletKeysFiles &wallet: m_ctx->listWallets())
|
||||||
|
arr << wallet.toJsonObject();
|
||||||
|
auto welcomeWalletMessage = WSServer::createWSMessage("walletList", arr);
|
||||||
|
pSocket->sendBinaryMessage(welcomeWalletMessage);
|
||||||
|
|
||||||
|
// and the current state of appcontext
|
||||||
|
QJsonObject obj;
|
||||||
|
|
||||||
|
if(this->m_ctx->currentWallet == nullptr) {
|
||||||
|
obj["state"] = "walletClosed";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
obj["state"] = "walletOpened";
|
||||||
|
obj["walletPath"] = m_ctx->currentWallet->path();
|
||||||
|
}
|
||||||
|
this->sendAll("state", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::processBinaryMessage(QByteArray buffer) {
|
||||||
|
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
|
||||||
|
const QString cid = this->connectionId(pClient);
|
||||||
|
|
||||||
|
if (m_debug)
|
||||||
|
qDebug() << "Websocket (server) received:" << buffer;
|
||||||
|
if (!pClient)
|
||||||
|
return;
|
||||||
|
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(buffer);
|
||||||
|
QJsonObject object = doc.object();
|
||||||
|
|
||||||
|
QString cmd = object.value("cmd").toString();
|
||||||
|
|
||||||
|
if(m_clients_auth.contains(cid) && !m_clients_auth[cid]) {
|
||||||
|
if (cmd == "password") {
|
||||||
|
auto data = object.value("data").toObject();
|
||||||
|
auto passwd = data.value("password").toString();
|
||||||
|
if(passwd != this->m_password) {
|
||||||
|
this->sendAll("passwordIncorrect", "authentication failed.");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
this->m_clients_auth[cid] = true;
|
||||||
|
this->sendAll("passwordSuccess", "authentication OK.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this->sendAll("passwordIncorrect", "authentication failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(cmd == "openWallet") {
|
||||||
|
auto data = object.value("data").toObject();
|
||||||
|
auto path = data.value("path").toString();
|
||||||
|
auto passwd = data.value("password").toString();
|
||||||
|
|
||||||
|
m_ctx->onOpenWallet(path, passwd);
|
||||||
|
} else if (cmd == "closeWallet") {
|
||||||
|
if (m_ctx->currentWallet == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_ctx->closeWallet(true, true);
|
||||||
|
} else if(cmd == "addressList") {
|
||||||
|
auto data = object.value("data").toObject();
|
||||||
|
auto accountIndex = data.value("accountIndex").toInt();
|
||||||
|
auto addressIndex = data.value("addressIndex").toInt();
|
||||||
|
|
||||||
|
auto limit = data.value("limit").toInt(50);
|
||||||
|
auto offset = data.value("offset").toInt(0);
|
||||||
|
|
||||||
|
QJsonArray arr;
|
||||||
|
for(int i = offset; i != limit; i++) {
|
||||||
|
arr << m_ctx->currentWallet->address((quint32) accountIndex, (quint32) addressIndex + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["accountIndex"] = accountIndex;
|
||||||
|
obj["addressIndex"] = addressIndex;
|
||||||
|
obj["offset"] = offset;
|
||||||
|
obj["limit"] = limit;
|
||||||
|
obj["addresses"] = arr;
|
||||||
|
this->sendAll("addressList", arr);
|
||||||
|
} else if(cmd == "sendTransaction") {
|
||||||
|
auto data = object.value("data").toObject();
|
||||||
|
auto address = data.value("address").toString();
|
||||||
|
auto amount = data.value("amount").toDouble(0);
|
||||||
|
auto description = data.value("description").toString();
|
||||||
|
bool all = data.value("all").toBool(false);
|
||||||
|
|
||||||
|
if(!WalletManager::addressValid(address, m_ctx->currentWallet->nettype())){
|
||||||
|
this->sendAll("transactionError", "Could not validate address");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(amount <= 0) {
|
||||||
|
this->sendAll("transactionError", "y u send 0");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ctx->onCreateTransaction(address, (quint64) amount, description, all);
|
||||||
|
} else if(cmd == "createWallet") {
|
||||||
|
auto data = object.value("data").toObject();
|
||||||
|
|
||||||
|
auto name = data.value("name").toString();
|
||||||
|
auto path = data.value("path").toString();
|
||||||
|
auto password = data.value("password").toString();
|
||||||
|
QString walletPath;
|
||||||
|
|
||||||
|
if(name.isEmpty()){
|
||||||
|
this->sendAll("walletCreatedError", "Supply a name for your wallet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(path.isEmpty()) {
|
||||||
|
walletPath = QDir(m_walletDir).filePath(name + ".keys");
|
||||||
|
if(Utils::fileExists(walletPath)) {
|
||||||
|
auto err = QString("Filepath already exists: %1").arg(walletPath);
|
||||||
|
this->sendAll("walletCreatedError", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FeatherSeed seed = FeatherSeed(m_ctx->restoreHeights[m_ctx->networkType], m_ctx->coinName, m_ctx->seedLanguage);
|
||||||
|
m_ctx->createWallet(seed, walletPath, password);
|
||||||
|
} else if(cmd == "transactionHistory") {
|
||||||
|
m_ctx->currentWallet->history()->refresh(m_ctx->currentWallet->currentSubaddressAccount());
|
||||||
|
auto *model = m_ctx->currentWallet->history();
|
||||||
|
|
||||||
|
QJsonArray arr = model->toJsonArray();
|
||||||
|
this->sendAll("transactionHistory", arr);
|
||||||
|
} else if (cmd == "addressBook") {
|
||||||
|
QJsonArray arr = m_ctx->currentWallet->addressBookModel()->toJsonArray();
|
||||||
|
this->sendAll("addressBook", arr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::socketDisconnected() {
|
||||||
|
QWebSocket *pClient = qobject_cast<QWebSocket *>(sender());
|
||||||
|
QString cid = connectionId(pClient);
|
||||||
|
|
||||||
|
m_clients_auth[cid] = false;
|
||||||
|
|
||||||
|
if (m_debug)
|
||||||
|
qDebug() << "socketDisconnected:" << pClient;
|
||||||
|
if (pClient) {
|
||||||
|
m_clients.removeAll(pClient);
|
||||||
|
pClient->deleteLater();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// templates are forbidden!
|
||||||
|
QByteArray WSServer::createWSMessage(const QString &cmd, const QJsonArray &arr) {
|
||||||
|
QJsonObject jsonObject = QJsonObject();
|
||||||
|
jsonObject["cmd"] = cmd;
|
||||||
|
jsonObject["data"] = arr;
|
||||||
|
QJsonDocument doc = QJsonDocument(jsonObject);
|
||||||
|
return doc.toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
QByteArray WSServer::createWSMessage(const QString &cmd, const QJsonObject &obj) {
|
||||||
|
QJsonObject jsonObject = QJsonObject();
|
||||||
|
jsonObject["cmd"] = cmd;
|
||||||
|
jsonObject["data"] = obj;
|
||||||
|
QJsonDocument doc = QJsonDocument(jsonObject);
|
||||||
|
return doc.toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray WSServer::createWSMessage(const QString &cmd, const int val) {
|
||||||
|
QJsonObject jsonObject = QJsonObject();
|
||||||
|
jsonObject["cmd"] = cmd;
|
||||||
|
jsonObject["data"] = val;
|
||||||
|
QJsonDocument doc = QJsonDocument(jsonObject);
|
||||||
|
return doc.toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray WSServer::createWSMessage(const QString &cmd, const QString &val) {
|
||||||
|
QJsonObject jsonObject = QJsonObject();
|
||||||
|
jsonObject["cmd"] = cmd;
|
||||||
|
jsonObject["data"] = val;
|
||||||
|
QJsonDocument doc = QJsonDocument(jsonObject);
|
||||||
|
return doc.toJson(QJsonDocument::Compact);
|
||||||
|
}
|
||||||
|
|
||||||
|
WSServer::~WSServer() {
|
||||||
|
m_pWebSocketServer->close();
|
||||||
|
qDeleteAll(m_clients.begin(), m_clients.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::sendAll(const QString &cmd, const QJsonObject &obj) {
|
||||||
|
for(QWebSocket *pSocket: m_clients) {
|
||||||
|
pSocket->sendBinaryMessage(WSServer::createWSMessage(cmd, obj));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::sendAll(const QString &cmd, const QJsonArray &arr) {
|
||||||
|
for(QWebSocket *pSocket: m_clients) {
|
||||||
|
pSocket->sendBinaryMessage(WSServer::createWSMessage(cmd, arr));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::sendAll(const QString &cmd, int val) {
|
||||||
|
for(QWebSocket *pSocket: m_clients) {
|
||||||
|
pSocket->sendBinaryMessage(WSServer::createWSMessage(cmd, val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::sendAll(const QString &cmd, const QString &val) {
|
||||||
|
for(QWebSocket *pSocket: m_clients) {
|
||||||
|
pSocket->sendBinaryMessage(WSServer::createWSMessage(cmd, val));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ======================================================================
|
||||||
|
|
||||||
|
void WSServer::onWalletOpened(Wallet *wallet) {
|
||||||
|
connect(m_ctx->currentWallet, &Wallet::connectionStatusChanged, this, &WSServer::onConnectionStatusChanged);
|
||||||
|
|
||||||
|
auto obj = wallet->toJsonObject();
|
||||||
|
sendAll("walletOpened", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onBlockchainSync(int height, int target) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["height"] = height;
|
||||||
|
obj["target"] = target;
|
||||||
|
sendAll("blockchainSync", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onRefreshSync(int height, int target) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["height"] = height;
|
||||||
|
obj["target"] = target;
|
||||||
|
sendAll("refreshSync", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onWalletClosed() {
|
||||||
|
QJsonObject obj;
|
||||||
|
sendAll("walletClosed", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onBalanceUpdated(quint64 balance, quint64 spendable) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["balance"] = balance / globals::cdiv;
|
||||||
|
obj["spendable"] = spendable / globals::cdiv;
|
||||||
|
sendAll("balanceUpdated", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onWalletOpenedError(const QString &err) {
|
||||||
|
sendAll("walletOpenedError", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onWalletCreatedError(const QString &err) {
|
||||||
|
sendAll("walletCreatedError", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onWalletCreated(Wallet *wallet) {
|
||||||
|
auto obj = wallet->toJsonObject();
|
||||||
|
sendAll("walletCreated", obj);
|
||||||
|
|
||||||
|
// emit signal on behalf of walletManager
|
||||||
|
m_ctx->walletManager->walletOpened(wallet);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onSynchronized() {
|
||||||
|
QJsonObject obj;
|
||||||
|
sendAll("synchronized", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onWalletOpenPasswordRequired(bool invalidPassword, const QString &path) {
|
||||||
|
QJsonObject obj;
|
||||||
|
obj["invalidPassword"] = invalidPassword;
|
||||||
|
obj["path"] = path;
|
||||||
|
sendAll("synchronized", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onConnectionStatusChanged(int status) {
|
||||||
|
sendAll("connectionStatusChanged", status);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onInitiateTransaction() {
|
||||||
|
QJsonObject obj;
|
||||||
|
sendAll("transactionStarted", obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onCreateTransactionError(const QString &message) {
|
||||||
|
sendAll("transactionError", message);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
|
||||||
|
// auto-commit all tx's
|
||||||
|
m_ctx->currentWallet->commitTransactionAsync(tx);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WSServer::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList &txid) {
|
||||||
|
QString preferredCur = config()->get(Config::preferredFiatCurrency).toString();
|
||||||
|
|
||||||
|
auto convert = [preferredCur](double amount){
|
||||||
|
return QString::number(AppContext::prices->convert("WOW", preferredCur, amount), 'f', 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
QJsonObject obj;
|
||||||
|
QJsonArray txids;
|
||||||
|
|
||||||
|
for(const QString &id: txid)
|
||||||
|
txids << id;
|
||||||
|
|
||||||
|
obj["txid"] = txids;
|
||||||
|
obj["status"] = status;
|
||||||
|
obj["amount"] = tx->amount() / globals::cdiv;
|
||||||
|
obj["fee"] = tx->fee() / globals::cdiv;
|
||||||
|
obj["total"] = (tx->amount() + tx->fee()) / globals::cdiv;
|
||||||
|
|
||||||
|
obj["amount_fiat"] = convert(tx->amount() / globals::cdiv);
|
||||||
|
obj["fee_fiat"] = convert(tx->fee() / globals::cdiv);
|
||||||
|
obj["total_fiat"] = convert((tx->amount() + tx->fee()) / globals::cdiv);
|
||||||
|
|
||||||
|
sendAll("transactionSent", obj);
|
||||||
|
}
|
74
src/utils/wsserver.h
Normal file
74
src/utils/wsserver.h
Normal file
|
@ -0,0 +1,74 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// Copyright (c) 2020-2021, The Monero Project.
|
||||||
|
|
||||||
|
#ifndef FEATHER_WSSERVER_H
|
||||||
|
#define FEATHER_WSSERVER_H
|
||||||
|
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QList>
|
||||||
|
#include <QtNetwork>
|
||||||
|
|
||||||
|
#include "appcontext.h"
|
||||||
|
#include "utils/keysfiles.h"
|
||||||
|
#include "qrcode/QrCode.h"
|
||||||
|
|
||||||
|
#include "libwalletqt/WalletManager.h"
|
||||||
|
|
||||||
|
QT_FORWARD_DECLARE_CLASS(QWebSocketServer)
|
||||||
|
QT_FORWARD_DECLARE_CLASS(QWebSocket)
|
||||||
|
|
||||||
|
class WSServer : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
explicit WSServer(AppContext *ctx, const QHostAddress &host, const quint16 port, const QString &password, bool debug = false, QObject *parent = nullptr);
|
||||||
|
~WSServer();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void closed();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onNewConnection();
|
||||||
|
void processBinaryMessage(QByteArray buffer);
|
||||||
|
void socketDisconnected();
|
||||||
|
|
||||||
|
// libwalletqt
|
||||||
|
void onBalanceUpdated(quint64 balance, quint64 spendable);
|
||||||
|
void onSynchronized();
|
||||||
|
void onWalletOpened(Wallet *wallet);
|
||||||
|
void onWalletClosed();
|
||||||
|
void onConnectionStatusChanged(int status);
|
||||||
|
void onCreateTransactionError(const QString &message);
|
||||||
|
void onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
|
||||||
|
void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid);
|
||||||
|
void onBlockchainSync(int height, int target);
|
||||||
|
void onRefreshSync(int height, int target);
|
||||||
|
void onWalletOpenedError(const QString &err);
|
||||||
|
void onWalletCreatedError(const QString &err);
|
||||||
|
void onWalletCreated(Wallet *wallet);
|
||||||
|
void onWalletOpenPasswordRequired(bool invalidPassword, const QString &path);
|
||||||
|
void onInitiateTransaction();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QWebSocketServer *m_pWebSocketServer;
|
||||||
|
QList<QWebSocket *> m_clients;
|
||||||
|
QMap<QString, bool> m_clients_auth;
|
||||||
|
bool m_debug;
|
||||||
|
QString m_walletDir;
|
||||||
|
AppContext *m_ctx;
|
||||||
|
QString m_password;
|
||||||
|
|
||||||
|
QString connectionId(QWebSocket *pSocket);
|
||||||
|
|
||||||
|
QByteArray createWSMessage(const QString &cmd, const QJsonObject &obj);
|
||||||
|
QByteArray createWSMessage(const QString &cmd, const QJsonArray &arr);
|
||||||
|
QByteArray createWSMessage(const QString &cmd, const int val);
|
||||||
|
QByteArray createWSMessage(const QString &cmd, const QString &val);
|
||||||
|
void sendAll(const QString &cmd, const QJsonArray &arr);
|
||||||
|
void sendAll(const QString &cmd, const QJsonObject &obj);
|
||||||
|
void sendAll(const QString &cmd, int val);
|
||||||
|
void sendAll(const QString &cmd, const QString &val);
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_WSSERVER_H
|
|
@ -102,7 +102,7 @@ void XMRigWidget::onWalletClosed() {
|
||||||
ui->lineEdit_address->setText("");
|
ui->lineEdit_address->setText("");
|
||||||
}
|
}
|
||||||
|
|
||||||
void XMRigWidget::onWalletOpened(){
|
void XMRigWidget::onWalletOpened(Wallet *wallet){
|
||||||
// Xmrig username
|
// Xmrig username
|
||||||
auto username = m_ctx->currentWallet->getCacheAttribute("feather.xmrig_username");
|
auto username = m_ctx->currentWallet->getCacheAttribute("feather.xmrig_username");
|
||||||
if(!username.isEmpty())
|
if(!username.isEmpty())
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void onWalletClosed();
|
void onWalletClosed();
|
||||||
void onWalletOpened();
|
void onWalletOpened(Wallet *wallet);
|
||||||
void onStartClicked();
|
void onStartClicked();
|
||||||
void onStopClicked();
|
void onStopClicked();
|
||||||
void onClearClicked();
|
void onClearClicked();
|
||||||
|
|
|
@ -80,16 +80,6 @@
|
||||||
</property>
|
</property>
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="enabled">
|
|
||||||
<bool>false</bool>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>by dsc & tobtoht</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<resources/>
|
<resources/>
|
||||||
|
|
Loading…
Reference in a new issue