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,27 +710,6 @@ 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();
 | 
					 | 
				
			||||||
    auto err = QString("Can't create transaction: ");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if(tx_status != PendingTransaction::Status_Ok){
 | 
					 | 
				
			||||||
        auto tx_err = tx->errorString();
 | 
					 | 
				
			||||||
        qCritical() << tx_err;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        if (m_ctx->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;
 | 
					 | 
				
			||||||
        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;
 | 
					    const auto &description = m_ctx->tmpTxDescription;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // Show advanced dialog on multi-destination transactions
 | 
					    // Show advanced dialog on multi-destination transactions
 | 
				
			||||||
| 
						 | 
					@ -763,7 +742,6 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVecto
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    dialog->deleteLater();
 | 
					    dialog->deleteLater();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
 | 
					void MainWindow::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid) {
 | 
				
			||||||
    if(status) { // success
 | 
					    if(status) { // success
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -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…
	
	Add table
		Add a link
		
	
		Reference in a new issue