From 216b2e0c5ec5014cd3e34a94978b0aab91c44eda Mon Sep 17 00:00:00 2001 From: tobtoht Date: Fri, 16 Oct 2020 05:05:05 +0200 Subject: [PATCH] Offline transaction signing --- CMakeLists.txt | 2 +- monero | 2 +- src/dialog/broadcasttxdialog.cpp | 62 +++++ src/dialog/broadcasttxdialog.h | 35 +++ src/dialog/broadcasttxdialog.ui | 111 ++++++++ src/dialog/qrcodedialog.cpp | 11 +- src/dialog/qrcodedialog.h | 4 +- src/dialog/txconfadvdialog.cpp | 186 ++++++++++++++ src/dialog/txconfadvdialog.h | 54 ++++ src/dialog/txconfadvdialog.ui | 279 +++++++++++++++++++++ src/dialog/txconfdialog.cpp | 30 +-- src/dialog/txconfdialog.h | 10 +- src/dialog/txconfdialog.ui | 2 +- src/libwalletqt/ConstructionInfo.cpp | 53 ++++ src/libwalletqt/ConstructionInfo.h | 46 ++++ src/libwalletqt/Input.h | 29 +++ src/libwalletqt/PendingTransaction.cpp | 39 ++- src/libwalletqt/PendingTransaction.h | 16 +- src/libwalletqt/PendingTransactionInfo.cpp | 32 +++ src/libwalletqt/PendingTransactionInfo.h | 46 ++++ src/libwalletqt/Transfer.h | 1 + src/libwalletqt/UnsignedTransaction.cpp | 15 ++ src/libwalletqt/UnsignedTransaction.h | 9 +- src/libwalletqt/Wallet.cpp | 17 +- src/libwalletqt/Wallet.h | 6 + src/mainwindow.cpp | 80 +++++- src/mainwindow.h | 6 + src/mainwindow.ui | 47 ++++ src/receivewidget.cpp | 3 +- src/sendwidget.cpp | 1 - src/utils/daemonrpc.cpp | 89 +++++++ src/utils/daemonrpc.h | 49 ++++ src/utils/utils.cpp | 11 +- src/utils/utils.h | 1 + src/utils/xmrtoapi.cpp | 2 +- 35 files changed, 1334 insertions(+), 52 deletions(-) create mode 100644 src/dialog/broadcasttxdialog.cpp create mode 100644 src/dialog/broadcasttxdialog.h create mode 100644 src/dialog/broadcasttxdialog.ui create mode 100644 src/dialog/txconfadvdialog.cpp create mode 100644 src/dialog/txconfadvdialog.h create mode 100644 src/dialog/txconfadvdialog.ui create mode 100644 src/libwalletqt/ConstructionInfo.cpp create mode 100644 src/libwalletqt/ConstructionInfo.h create mode 100644 src/libwalletqt/Input.h create mode 100644 src/libwalletqt/PendingTransactionInfo.cpp create mode 100644 src/libwalletqt/PendingTransactionInfo.h create mode 100644 src/utils/daemonrpc.cpp create mode 100644 src/utils/daemonrpc.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c9851a3..e47127d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,7 +30,7 @@ if(DEBUG) set(CMAKE_VERBOSE_MAKEFILE ON) endif() -set(MONERO_HEAD "f6587d7943a19c55a5b78af1a89b22c130513b73") +set(MONERO_HEAD "c2b7b50fdea2e66a593f9c9109ebd742f69ad9d1") set(BUILD_GUI_DEPS ON) set(ARCH "x86-64") set(BUILD_64 ON) diff --git a/monero b/monero index f6587d7..c2b7b50 160000 --- a/monero +++ b/monero @@ -1 +1 @@ -Subproject commit f6587d7943a19c55a5b78af1a89b22c130513b73 +Subproject commit c2b7b50fdea2e66a593f9c9109ebd742f69ad9d1 diff --git a/src/dialog/broadcasttxdialog.cpp b/src/dialog/broadcasttxdialog.cpp new file mode 100644 index 0000000..f6c5959 --- /dev/null +++ b/src/dialog/broadcasttxdialog.cpp @@ -0,0 +1,62 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020, The Monero Project. + +#include "broadcasttxdialog.h" +#include "ui_broadcasttxdialog.h" + +#include + +BroadcastTxDialog::BroadcastTxDialog(QWidget *parent, AppContext *ctx) + : QDialog(parent) + , m_ctx(ctx) + , ui(new Ui::BroadcastTxDialog) +{ + ui->setupUi(this); + + m_network = new UtilsNetworking(m_ctx->network, this); + + auto node = ctx->nodes->connection(); + m_rpc = new DaemonRpc(this, m_network, node.full); + + connect(ui->btn_Broadcast, &QPushButton::clicked, this, &BroadcastTxDialog::broadcastTx); + connect(ui->btn_Close, &QPushButton::clicked, this, &BroadcastTxDialog::reject); + + connect(m_rpc, &DaemonRpc::ApiResponse, this, &BroadcastTxDialog::onApiResponse); + + this->adjustSize(); +} + +void BroadcastTxDialog::broadcastTx() { + QString tx = ui->transaction->toPlainText(); + + QString node = [this]{ + QString node; + if (ui->radio_useCustom->isChecked()) + node = ui->customNode->text(); + else + node = m_ctx->nodes->connection().full; + + if (!node.startsWith("http://")) + node = QString("http://%1").arg(node); + return node; + }(); + + m_rpc->setDaemonAddress(node); + m_rpc->sendRawTransaction(tx); +} + +void BroadcastTxDialog::onApiResponse(const DaemonRpc::DaemonResponse &resp) { + if (!resp.ok) { + QMessageBox::warning(this, "Transaction broadcast", resp.status); + return; + } + + if (resp.endpoint == DaemonRpc::Endpoint::SEND_RAW_TRANSACTION) { + QMessageBox::information(this, "Transaction broadcast", "Transaction submitted successfully.\n\n" + "If the transaction belongs to this wallet it may take several minutes before it shows up in the history tab."); + } +} + +BroadcastTxDialog::~BroadcastTxDialog() { + delete ui; +} diff --git a/src/dialog/broadcasttxdialog.h b/src/dialog/broadcasttxdialog.h new file mode 100644 index 0000000..3c158cc --- /dev/null +++ b/src/dialog/broadcasttxdialog.h @@ -0,0 +1,35 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020, The Monero Project. + +#ifndef FEATHER_BROADCASTTXDIALOG_H +#define FEATHER_BROADCASTTXDIALOG_H + +#include +#include "appcontext.h" +#include "utils/daemonrpc.h" + +namespace Ui { + class BroadcastTxDialog; +} + +class BroadcastTxDialog : public QDialog +{ + Q_OBJECT + +public: + explicit BroadcastTxDialog(QWidget *parent, AppContext *ctx); + ~BroadcastTxDialog() override; + +private slots: + void broadcastTx(); + void onApiResponse(const DaemonRpc::DaemonResponse &resp); + +private: + Ui::BroadcastTxDialog *ui; + UtilsNetworking *m_network; + AppContext *m_ctx; + DaemonRpc *m_rpc; +}; + + +#endif //FEATHER_BROADCASTTXDIALOG_H diff --git a/src/dialog/broadcasttxdialog.ui b/src/dialog/broadcasttxdialog.ui new file mode 100644 index 0000000..af12c00 --- /dev/null +++ b/src/dialog/broadcasttxdialog.ui @@ -0,0 +1,111 @@ + + + BroadcastTxDialog + + + + 0 + 0 + 797 + 575 + + + + Broadcast transaction + + + + + + Transaction: + + + + + + + + 500 + 0 + + + + Paste hex encoded signed tx string here + + + + + + + Node + + + + + + Use currently connected node + + + true + + + + + + + Use custom node + + + + + + + + + + + + + false + + + All transactions are broadcast over Tor + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Broadcast + + + + + + + Close + + + + + + + + + + diff --git a/src/dialog/qrcodedialog.cpp b/src/dialog/qrcodedialog.cpp index 0046904..334a002 100644 --- a/src/dialog/qrcodedialog.cpp +++ b/src/dialog/qrcodedialog.cpp @@ -9,15 +9,14 @@ #include #include -QrCodeDialog::QrCodeDialog(QWidget *parent, const QString &text, const QString &title) +QrCodeDialog::QrCodeDialog(QWidget *parent, const QrCode &qrCode, const QString &title) : QDialog(parent) , ui(new Ui::QrCodeDialog) { ui->setupUi(this); this->setWindowTitle(title); - m_qrc = new QrCode(text, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::HIGH); - m_pixmap = m_qrc->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio); + m_pixmap = qrCode.toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio); ui->QrCode->setPixmap(m_pixmap); connect(ui->btn_CopyImage, &QPushButton::clicked, this, &QrCodeDialog::copyImage); @@ -32,7 +31,11 @@ QrCodeDialog::QrCodeDialog(QWidget *parent, const QString &text, const QString & QrCodeDialog::~QrCodeDialog() { delete ui; - delete m_qrc; +} + +void QrCodeDialog::setQrCode(const QrCode &qrCode) { + m_pixmap = qrCode.toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio); + ui->QrCode->setPixmap(m_pixmap); } void QrCodeDialog::copyImage() { diff --git a/src/dialog/qrcodedialog.h b/src/dialog/qrcodedialog.h index cb0d1a2..200ec51 100644 --- a/src/dialog/qrcodedialog.h +++ b/src/dialog/qrcodedialog.h @@ -16,15 +16,15 @@ class QrCodeDialog : public QDialog Q_OBJECT public: - explicit QrCodeDialog(QWidget *parent, const QString &text, const QString &title = "Qr Code"); + explicit QrCodeDialog(QWidget *parent, const QrCode &qrCode, const QString &title = "Qr Code"); ~QrCodeDialog() override; + void setQrCode(const QrCode &qrCode); private: void copyImage(); void saveImage(); Ui::QrCodeDialog *ui; - QrCode *m_qrc; QPixmap m_pixmap; }; diff --git a/src/dialog/txconfadvdialog.cpp b/src/dialog/txconfadvdialog.cpp new file mode 100644 index 0000000..a7fa2f4 --- /dev/null +++ b/src/dialog/txconfadvdialog.cpp @@ -0,0 +1,186 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020, The Monero Project. + +#include "txconfadvdialog.h" +#include "ui_txconfadvdialog.h" +#include "libwalletqt/WalletManager.h" +#include "qrcode/QrCode.h" +#include "dialog/qrcodedialog.h" +#include "utils/utils.h" +#include "libwalletqt/PendingTransactionInfo.h" +#include "libwalletqt/Transfer.h" +#include "libwalletqt/Input.h" +#include "model/ModelUtils.h" + +#include +#include + +TxConfAdvDialog::TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent) + : QDialog(parent) + , ui(new Ui::TxConfAdvDialog) + , m_ctx(ctx) + , m_exportUnsignedMenu(new QMenu(this)) + , m_exportSignedMenu(new QMenu(this)) +{ + ui->setupUi(this); + + m_exportUnsignedMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::unsignedCopy); + m_exportUnsignedMenu->addAction("Show as QR code", this, &TxConfAdvDialog::unsignedQrCode); + m_exportUnsignedMenu->addAction("Save to file", this, &TxConfAdvDialog::unsignedSaveFile); + ui->btn_exportUnsigned->setMenu(m_exportUnsignedMenu); + + m_exportSignedMenu->addAction("Copy to clipboard", this, &TxConfAdvDialog::signedCopy); + m_exportSignedMenu->addAction("Save to file", this, &TxConfAdvDialog::signedSaveFile); + ui->btn_exportSigned->setMenu(m_exportSignedMenu); + + if (m_ctx->currentWallet->viewOnly()) { + ui->btn_exportSigned->hide(); + ui->btn_send->hide(); + } + + ui->label_description->setText(QString("Description: %1").arg(description)); + + connect(ui->btn_sign, &QPushButton::clicked, this, &TxConfAdvDialog::signTransaction); + connect(ui->btn_send, &QPushButton::clicked, this, &TxConfAdvDialog::broadcastTransaction); + connect(ui->btn_close, &QPushButton::clicked, this, &TxConfAdvDialog::closeDialog); + + ui->inputs->setFont(ModelUtils::getMonospaceFont()); + ui->outputs->setFont(ModelUtils::getMonospaceFont()); + + this->adjustSize(); +} + +void TxConfAdvDialog::setTransaction(PendingTransaction *tx) { + ui->btn_sign->hide(); + + m_tx = tx; + m_tx->refresh(); + PendingTransactionInfo *ptx = m_tx->transaction(0); + + ui->txid->setText(tx->txid().first()); + + ui->amount->setText(WalletManager::displayAmount(tx->amount())); + ui->fee->setText(WalletManager::displayAmount(ptx->fee())); + ui->total->setText(WalletManager::displayAmount(tx->amount() + ptx->fee())); + + auto size_str = [this]{ + if (m_ctx->currentWallet->viewOnly()) { + return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size())); + } else { + auto size = m_tx->signedTxToHex(0).size() / 2; + return QString("Size: %1 bytes (%2 bytes unsigned)").arg(QString::number(size), QString::number(m_tx->unsignedTxToBin().size())); + } + }(); + ui->label_size->setText(size_str); + + this->setupConstructionData(ptx); +} + +void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) { + m_utx = utx; + m_utx->refresh(); + + ui->btn_exportUnsigned->hide(); + ui->btn_exportSigned->hide(); + ui->btn_sign->show(); + ui->btn_send->hide(); + + ui->txid->setText("n/a"); + ui->label_size->setText("Size: n/a"); + + ui->amount->setText(WalletManager::displayAmount(utx->amount(0))); + ui->fee->setText(WalletManager::displayAmount(utx->fee(0))); + ui->total->setText(WalletManager::displayAmount(utx->amount(0) + utx->fee(0))); + + ConstructionInfo *ci = m_utx->constructionInfo(0); + this->setupConstructionData(ci); +} + +void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) { + QString inputs_str; + auto inputs = ci->inputs(); + for (const auto& i: inputs) { + inputs_str += QString("%1 %2\n").arg(i->pubKey(), WalletManager::displayAmount(i->amount())); + } + ui->inputs->setText(inputs_str); + ui->label_inputs->setText(QString("Inputs (%1)").arg(QString::number(inputs.size()))); + + QString outputs_str; + auto outputs = ci->outputs(); + for (const auto& o: outputs) { + outputs_str += QString("%1 %2\n").arg(o->address(), WalletManager::displayAmount(o->amount())); + } + ui->outputs->setText(outputs_str); + ui->label_outputs->setText(QString("Outputs (%1)").arg(QString::number(outputs.size()))); + + ui->label_ringSize->setText(QString("Ring size: %1").arg(QString::number(ci->minMixinCount() + 1))); + ui->label_unlockTime->setText(QString("Unlock time: %1 (height)").arg(QString::number(ci->unlockTime()))); +} + +void TxConfAdvDialog::signTransaction() { + QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch())); + QString fn = QFileDialog::getSaveFileName(this, "Save signed transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)"); + if(fn.isEmpty()) return; + + m_utx->sign(fn) ? QMessageBox::information(this, "Sign transaction", "Transaction saved successfully") + : QMessageBox::warning(this, "Sign transaction", "Failes to save transaction to file."); +} + +void TxConfAdvDialog::unsignedSaveFile() { + QString defaultName = QString("%1_unsigned_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch())); + QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*unsigned_monero_tx)"); + if(fn.isEmpty()) return; + + m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully") + : QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file."); +} + +void TxConfAdvDialog::signedSaveFile() { + QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch())); + QString fn = QFileDialog::getSaveFileName(this, "Save transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)"); + if(fn.isEmpty()) return; + + m_tx->saveToFile(fn) ? QMessageBox::information(this, "Transaction saved to file", "Transaction saved successfully") + : QMessageBox::warning(this, "Save transaction", "Failed to save transaction to file."); +} + +void TxConfAdvDialog::unsignedQrCode() { + if (m_tx->unsignedTxToBin().size() > 2953) { + QMessageBox::warning(this, "Unable to show QR code", "Transaction size exceeds the maximum size for QR codes (2953 bytes)."); + return; + } + + QrCode qr(m_tx->unsignedTxToBin(), QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::LOW); + auto *dialog = new QrCodeDialog(this, qr, "Unsigned Transaction"); + dialog->exec(); + dialog->deleteLater(); +} + +void TxConfAdvDialog::unsignedCopy() { + Utils::copyToClipboard(m_tx->unsignedTxToBase64()); +} + +void TxConfAdvDialog::signedCopy() { + Utils::copyToClipboard(m_tx->signedTxToHex(0)); +} + +void TxConfAdvDialog::signedQrCode() { +} + +void TxConfAdvDialog::broadcastTransaction() { + if (m_tx == nullptr) return; + m_ctx->currentWallet->commitTransactionAsync(m_tx); + QDialog::accept(); +} + +void TxConfAdvDialog::closeDialog() { + if (m_tx != nullptr) + m_ctx->currentWallet->disposeTransaction(m_tx); + if (m_utx != nullptr) + m_ctx->currentWallet->disposeTransaction(m_utx); + QDialog::reject(); +} + +TxConfAdvDialog::~TxConfAdvDialog() { + delete ui; +} diff --git a/src/dialog/txconfadvdialog.h b/src/dialog/txconfadvdialog.h new file mode 100644 index 0000000..63ed112 --- /dev/null +++ b/src/dialog/txconfadvdialog.h @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020, The Monero Project. + +#ifndef FEATHER_TXCONFADVDIALOG_H +#define FEATHER_TXCONFADVDIALOG_H + +#include +#include +#include +#include + +#include "libwalletqt/PendingTransaction.h" +#include "appcontext.h" + +namespace Ui { + class TxConfAdvDialog; +} + +class TxConfAdvDialog : public QDialog +{ +Q_OBJECT + +public: + explicit TxConfAdvDialog(AppContext *ctx, const QString &description, QWidget *parent = nullptr); + ~TxConfAdvDialog() override; + + void setTransaction(PendingTransaction *tx); + void setUnsignedTransaction(UnsignedTransaction *utx); + +private: + void setupConstructionData(ConstructionInfo *ci); + void signTransaction(); + void broadcastTransaction(); + void closeDialog(); + + void unsignedCopy(); + void unsignedQrCode(); + void unsignedSaveFile(); + + void signedCopy(); + void signedQrCode(); + void signedSaveFile(); + + Ui::TxConfAdvDialog *ui; + AppContext *m_ctx; + PendingTransaction *m_tx = nullptr; + UnsignedTransaction *m_utx = nullptr; + QMenu *m_exportUnsignedMenu; + QMenu *m_exportSignedMenu; +}; + + + +#endif //FEATHER_TXCONFADVDIALOG_H diff --git a/src/dialog/txconfadvdialog.ui b/src/dialog/txconfadvdialog.ui new file mode 100644 index 0000000..86df777 --- /dev/null +++ b/src/dialog/txconfadvdialog.ui @@ -0,0 +1,279 @@ + + + TxConfAdvDialog + + + + 0 + 0 + 800 + 542 + + + + + 800 + 0 + + + + Transaction + + + + + + Transaction ID: + + + + + + + txid + + + true + + + + + + + + + + + Amount: + + + + + + + TextLabel + + + + + + + Fee: + + + + + + + TextLabel + + + + + + + Qt::Horizontal + + + + + + + Total: + + + + + + + TextLabel + + + + + + + + + Qt::Vertical + + + + + + + + + Description: + + + + + + + Size: + + + + + + + Unlock time: + + + + + + + Ringsize: + + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Minimum + + + + 20 + 10 + + + + + + + + Inputs + + + + + + + + 0 + 0 + + + + + 16777215 + 100 + + + + true + + + + + + + Outputs + + + + + + + + 0 + 0 + + + + + 16777215 + 100 + + + + true + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + Export unsigned + + + QToolButton::InstantPopup + + + + + + + Export signed + + + QToolButton::InstantPopup + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Sign + + + + + + + Send + + + + + + + Close + + + + + + + + + + diff --git a/src/dialog/txconfdialog.cpp b/src/dialog/txconfdialog.cpp index b72c437..0724a98 100644 --- a/src/dialog/txconfdialog.cpp +++ b/src/dialog/txconfdialog.cpp @@ -6,12 +6,15 @@ #include "appcontext.h" #include "utils/config.h" #include "model/ModelUtils.h" +#include "libwalletqt/WalletManager.h" +#include "txconfadvdialog.h" #include -TxConfDialog::TxConfDialog(PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent) +TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent) : QDialog(parent) , ui(new Ui::TxConfDialog) + , m_ctx(ctx) , m_tx(tx) , m_address(address) , m_description(description) @@ -43,29 +46,16 @@ TxConfDialog::TxConfDialog(PendingTransaction *tx, const QString &address, const ui->label_address->setFont(ModelUtils::getMonospaceFont()); ui->label_address->setToolTip(address); - connect(ui->btn_Advanced, &QPushButton::clicked, this, &TxConfDialog::showAdvanced); + ui->buttonBox->button(QDialogButtonBox::Ok)->setText("Send"); + + connect(ui->btn_Advanced, &QPushButton::clicked, this, &TxConfDialog::setShowAdvanced); this->adjustSize(); } -void TxConfDialog::showAdvanced() { - const auto amount = m_tx->amount() / AppContext::cdiv; - const auto fee = m_tx->fee() / AppContext::cdiv; - - QString body = QString("Address: %2\n").arg(m_address.left(60)); - body += m_address.mid(60) + "\n"; - if(!m_description.isEmpty()) - body = QString("%1Description: %2\n").arg(body, m_description); - body = QString("%1Amount: %2 XMR\n").arg(body, QString::number(amount)); - body = QString("%1Fee: %2 XMR\n").arg(body, QString::number(fee)); - body = QString("%1Ringsize: %2").arg(body, QString::number(m_mixin + 1)); - - auto subaddrIndices = m_tx->subaddrIndices(); - for (int i = 0; i < subaddrIndices.count(); ++i){ - body = QString("%1\nSpending address index: %2").arg(body, QString::number(subaddrIndices.at(i).toInt())); - } - - QMessageBox::information(this, "Transaction information", body); +void TxConfDialog::setShowAdvanced() { + this->showAdvanced = true; + QDialog::reject(); } TxConfDialog::~TxConfDialog() { diff --git a/src/dialog/txconfdialog.h b/src/dialog/txconfdialog.h index 7a2b02a..4f74370 100644 --- a/src/dialog/txconfdialog.h +++ b/src/dialog/txconfdialog.h @@ -7,6 +7,7 @@ #include #include "libwalletqt/PendingTransaction.h" #include "libwalletqt/WalletManager.h" +#include "appcontext.h" namespace Ui { class TxConfDialog; @@ -17,13 +18,18 @@ class TxConfDialog : public QDialog Q_OBJECT public: - explicit TxConfDialog(PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent = nullptr); + explicit TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent = nullptr); ~TxConfDialog() override; + bool showAdvanced = false; + private: - void showAdvanced(); + void setShowAdvanced(); + void saveToFile(); + void copyToClipboard(); Ui::TxConfDialog *ui; + AppContext *m_ctx; PendingTransaction *m_tx; QString m_address; QString m_description; diff --git a/src/dialog/txconfdialog.ui b/src/dialog/txconfdialog.ui index d388d1d..acb7a6c 100644 --- a/src/dialog/txconfdialog.ui +++ b/src/dialog/txconfdialog.ui @@ -6,7 +6,7 @@ 0 0 - 655 + 844 248 diff --git a/src/libwalletqt/ConstructionInfo.cpp b/src/libwalletqt/ConstructionInfo.cpp new file mode 100644 index 0000000..ce163a8 --- /dev/null +++ b/src/libwalletqt/ConstructionInfo.cpp @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2020, The Monero Project. + +#include "ConstructionInfo.h" + +#include "Input.h" +#include "Transfer.h" + +quint64 ConstructionInfo::unlockTime() const { + return m_unlockTime; +} + +QSet ConstructionInfo::subaddressIndices() const { + return m_subaddressIndices; +} + +QVector ConstructionInfo::subaddresses() const { + return m_subaddresses; +} + +quint64 ConstructionInfo::minMixinCount() const { + return m_minMixinCount; +} + +QList ConstructionInfo::inputs() const { + return m_inputs; +} + +QList ConstructionInfo::outputs() const { + return m_outputs; +} + +ConstructionInfo::ConstructionInfo(const Monero::TransactionConstructionInfo *pimpl, QObject *parent) + : QObject(parent) + , m_unlockTime(pimpl->unlockTime()) + , m_minMixinCount(pimpl->minMixinCount()) +{ + for (auto const &i : pimpl->inputs()) + { + Input *input = new Input(i.amount, QString::fromStdString(i.pubkey), this); + m_inputs.append(input); + } + + for (auto const &o : pimpl->outputs()) + { + Transfer *output = new Transfer(o.amount, QString::fromStdString(o.address), this); + m_outputs.append(output); + } + for (uint32_t i : pimpl->subaddressIndices()) + { + m_subaddressIndices.insert(i); + } +} \ No newline at end of file diff --git a/src/libwalletqt/ConstructionInfo.h b/src/libwalletqt/ConstructionInfo.h new file mode 100644 index 0000000..e84a595 --- /dev/null +++ b/src/libwalletqt/ConstructionInfo.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2020, The Monero Project. + +#ifndef FEATHER_CONSTRUCTIONINFO_H +#define FEATHER_CONSTRUCTIONINFO_H + +#include +#include +#include + +class Input; +class Transfer; + +class ConstructionInfo : public QObject +{ + Q_OBJECT + Q_PROPERTY(quint64 unlockTime READ unlockTime) + Q_PROPERTY(QSet subaddressIndices READ subaddressIndices) + Q_PROPERTY(QVector subaddresses READ subaddresses) + Q_PROPERTY(quint64 minMixinCount READ minMixinCount) + Q_PROPERTY(QList inputs READ inputs) + Q_PROPERTY(QList outputs READ outputs) + +public: + quint64 unlockTime() const; + QSet subaddressIndices() const; + QVector subaddresses() const; + quint64 minMixinCount() const; + QList inputs() const; + QList outputs() const; + +private: + explicit ConstructionInfo(const Monero::TransactionConstructionInfo *pimpl, QObject *parent = nullptr); + + friend class PendingTransactionInfo; + friend class UnsignedTransaction; + quint64 m_unlockTime; + QSet m_subaddressIndices; + QVector m_subaddresses; + quint64 m_minMixinCount; + mutable QList m_inputs; + mutable QList m_outputs; +}; + + +#endif //FEATHER_CONSTRUCTIONINFO_H diff --git a/src/libwalletqt/Input.h b/src/libwalletqt/Input.h new file mode 100644 index 0000000..25ca4d6 --- /dev/null +++ b/src/libwalletqt/Input.h @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2020, The Monero Project. + +#ifndef FEATHER_INPUT_H +#define FEATHER_INPUT_H + +#include +#include +#include + +class Input : public QObject +{ + Q_OBJECT + Q_PROPERTY(quint64 amount READ amount) + Q_PROPERTY(QString pubKey READ pubKey) + +private: + explicit Input(uint64_t _amount, QString _address, QObject *parent = nullptr): QObject(parent), m_amount(_amount), m_pubkey(std::move(_address)) {}; + + friend class ConstructionInfo; + quint64 m_amount; + QString m_pubkey; +public: + quint64 amount() const { return m_amount; } + QString pubKey() const { return m_pubkey; } + +}; + +#endif //FEATHER_INPUT_H diff --git a/src/libwalletqt/PendingTransaction.cpp b/src/libwalletqt/PendingTransaction.cpp index 7c9aeb5..a6bb47d 100644 --- a/src/libwalletqt/PendingTransaction.cpp +++ b/src/libwalletqt/PendingTransaction.cpp @@ -16,10 +16,12 @@ QString PendingTransaction::errorString() const bool PendingTransaction::commit() { - // Save transaction to file if fileName is set. - if(!m_fileName.isEmpty()) - return m_pimpl->commit(m_fileName.toStdString()); - return m_pimpl->commit(m_fileName.toStdString()); + return m_pimpl->commit(); +} + +bool PendingTransaction::saveToFile(const QString &fileName) +{ + return m_pimpl->commit(fileName.toStdString()); } quint64 PendingTransaction::amount() const @@ -37,7 +39,6 @@ quint64 PendingTransaction::fee() const return m_pimpl->fee(); } - QStringList PendingTransaction::txid() const { QStringList list; @@ -63,9 +64,33 @@ QList PendingTransaction::subaddrIndices() const return result; } -void PendingTransaction::setFilename(const QString &fileName) +QByteArray PendingTransaction::unsignedTxToBin() const { + return QByteArray::fromStdString(m_pimpl->unsignedTxToBin()); +} + +QString PendingTransaction::unsignedTxToBase64() const { - m_fileName = fileName; + return QString::fromStdString(m_pimpl->unsignedTxToBase64()); +} + +QString PendingTransaction::signedTxToHex(int index) const +{ + return QString::fromStdString(m_pimpl->signedTxToHex(index)); +} + +PendingTransactionInfo * PendingTransaction::transaction(int index) const { + return m_pending_tx_info[index]; +} + +void PendingTransaction::refresh() +{ + qDeleteAll(m_pending_tx_info); + m_pending_tx_info.clear(); + + m_pimpl->refresh(); + for (const auto i : m_pimpl->getAll()) { + m_pending_tx_info.append(new PendingTransactionInfo(i, this)); + } } PendingTransaction::PendingTransaction(Monero::PendingTransaction *pt, QObject *parent) diff --git a/src/libwalletqt/PendingTransaction.h b/src/libwalletqt/PendingTransaction.h index 5b60bab..64cbfba 100644 --- a/src/libwalletqt/PendingTransaction.h +++ b/src/libwalletqt/PendingTransaction.h @@ -9,6 +9,8 @@ #include #include +#include "PendingTransactionInfo.h" + //namespace Monero { //class PendingTransaction; @@ -30,7 +32,7 @@ public: enum Status { Status_Ok = Monero::PendingTransaction::Status_Ok, Status_Error = Monero::PendingTransaction::Status_Error, - Status_Critical = Monero::PendingTransaction::Status_Critical + Status_Critical = Monero::PendingTransaction::Status_Critical }; Q_ENUM(Status) @@ -45,21 +47,27 @@ public: Status status() const; QString errorString() const; Q_INVOKABLE bool commit(); + bool saveToFile(const QString &fileName); quint64 amount() const; quint64 dust() const; quint64 fee() const; QStringList txid() const; quint64 txCount() const; QList subaddrIndices() const; - Q_INVOKABLE void setFilename(const QString &fileName); + QByteArray unsignedTxToBin() const; + QString unsignedTxToBase64() const; + QString signedTxToHex(int index) const; + void refresh(); + + PendingTransactionInfo * transaction(int index) const; private: - explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = 0); + explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = nullptr); private: friend class Wallet; Monero::PendingTransaction * m_pimpl; - QString m_fileName; + mutable QList m_pending_tx_info; }; #endif // PENDINGTRANSACTION_H diff --git a/src/libwalletqt/PendingTransactionInfo.cpp b/src/libwalletqt/PendingTransactionInfo.cpp new file mode 100644 index 0000000..6ae2776 --- /dev/null +++ b/src/libwalletqt/PendingTransactionInfo.cpp @@ -0,0 +1,32 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2020, The Monero Project. + +#include "PendingTransactionInfo.h" +#include "Input.h" +#include "Transfer.h" + +quint64 PendingTransactionInfo::fee() const { + return m_fee; +} + +quint64 PendingTransactionInfo::dust() const { + return m_dust; +} + +bool PendingTransactionInfo::dustAddedToFee() const { + return m_dustAddedToFee; +} + +QString PendingTransactionInfo::txKey() const { + return m_txKey; +} + +PendingTransactionInfo::PendingTransactionInfo(const Monero::PendingTransactionInfo *pimpl, QObject *parent) + : ConstructionInfo(pimpl->constructionData(), parent) + , m_fee(pimpl->fee()) + , m_dust(pimpl->dust()) + , m_dustAddedToFee(pimpl->dustAddedToFee()) + , m_txKey(QString::fromStdString(pimpl->txKey())) +{ + +} \ No newline at end of file diff --git a/src/libwalletqt/PendingTransactionInfo.h b/src/libwalletqt/PendingTransactionInfo.h new file mode 100644 index 0000000..b6318b6 --- /dev/null +++ b/src/libwalletqt/PendingTransactionInfo.h @@ -0,0 +1,46 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2014-2020, The Monero Project. + +#ifndef FEATHER_PENDINGTRANSACTIONINFO_H +#define FEATHER_PENDINGTRANSACTIONINFO_H + +#include +#include "ConstructionInfo.h" +#include +#include + +class Input; +class Transfer; + +class PendingTransactionInfo : public ConstructionInfo +{ + Q_OBJECT + Q_PROPERTY(quint64 fee READ fee) + Q_PROPERTY(quint64 dust READ dust) + Q_PROPERTY(bool dustAddedToFee READ dustAddedToFee) + Q_PROPERTY(QString txKey READ txKey) + Q_PROPERTY(quint64 unlockTime READ unlockTime) + Q_PROPERTY(QSet subaddressIndices READ subaddressIndices) + Q_PROPERTY(QVector subaddresses READ subaddresses) + Q_PROPERTY(quint64 minMixinCount READ minMixinCount) + Q_PROPERTY(QList inputs READ inputs) + Q_PROPERTY(QList outputs READ outputs) + +public: + quint64 fee() const; + quint64 dust() const; + bool dustAddedToFee() const; + QString txKey() const; + +private: + explicit PendingTransactionInfo(const Monero::PendingTransactionInfo *pimpl, QObject *parent = nullptr); + + friend class PendingTransaction; + quint64 m_fee; + quint64 m_dust; + bool m_dustAddedToFee; + QString m_txKey; +}; + + +#endif //FEATHER_PENDINGTRANSACTIONINFO_H diff --git a/src/libwalletqt/Transfer.h b/src/libwalletqt/Transfer.h index 3f81d0c..c9e462d 100644 --- a/src/libwalletqt/Transfer.h +++ b/src/libwalletqt/Transfer.h @@ -17,6 +17,7 @@ private: explicit Transfer(uint64_t _amount, QString _address, QObject *parent = 0): QObject(parent), m_amount(_amount), m_address(std::move(_address)) {}; private: friend class TransactionInfo; + friend class ConstructionInfo; quint64 m_amount; QString m_address; public: diff --git a/src/libwalletqt/UnsignedTransaction.cpp b/src/libwalletqt/UnsignedTransaction.cpp index 7cbe9b4..d1c9006 100644 --- a/src/libwalletqt/UnsignedTransaction.cpp +++ b/src/libwalletqt/UnsignedTransaction.cpp @@ -83,6 +83,21 @@ void UnsignedTransaction::setFilename(const QString &fileName) m_fileName = fileName; } +ConstructionInfo * UnsignedTransaction::constructionInfo(int index) const { + return m_construction_info[index]; +} + +void UnsignedTransaction::refresh() +{ + qDeleteAll(m_construction_info); + m_construction_info.clear(); + + m_pimpl->refresh(); + for (const auto i : m_pimpl->getAll()) { + m_construction_info.append(new ConstructionInfo(i, this)); + } +} + UnsignedTransaction::UnsignedTransaction(Monero::UnsignedTransaction *pt, Monero::Wallet *walletImpl, QObject *parent) : QObject(parent), m_pimpl(pt), m_walletImpl(walletImpl) { diff --git a/src/libwalletqt/UnsignedTransaction.h b/src/libwalletqt/UnsignedTransaction.h index c0906bf..892bdae 100644 --- a/src/libwalletqt/UnsignedTransaction.h +++ b/src/libwalletqt/UnsignedTransaction.h @@ -7,14 +7,13 @@ #include #include +#include "libwalletqt/PendingTransactionInfo.h" class UnsignedTransaction : public QObject { Q_OBJECT Q_PROPERTY(Status status READ status) Q_PROPERTY(QString errorString READ errorString) - // Q_PROPERTY(QList amount READ amount) - // Q_PROPERTY(QList fee READ fee) Q_PROPERTY(quint64 txCount READ txCount) Q_PROPERTY(QString confirmationMessage READ confirmationMessage) Q_PROPERTY(QStringList recipientAddress READ recipientAddress) @@ -41,15 +40,19 @@ public: quint64 minMixinCount() const; Q_INVOKABLE bool sign(const QString &fileName) const; Q_INVOKABLE void setFilename(const QString &fileName); + void refresh(); + + ConstructionInfo * constructionInfo(int index) const; private: - explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, Monero::Wallet *walletImpl, QObject *parent = 0); + explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, Monero::Wallet *walletImpl, QObject *parent = nullptr); ~UnsignedTransaction(); private: friend class Wallet; Monero::UnsignedTransaction * m_pimpl; QString m_fileName; Monero::Wallet * m_walletImpl; + mutable QList m_construction_info; }; #endif // UNSIGNEDTRANSACTION_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 53c5dac..e9ea81d 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -529,7 +529,7 @@ void Wallet::createTransactionSingleAsync(const QString &key_image, const QStrin { m_scheduler.run([this, key_image, dst_addr, outputs, priority] { PendingTransaction *tx = createTransactionSingle(key_image, dst_addr, outputs, priority); - emit transactionCreated(tx, dst_addr, "", 0); // todo: return true mixincount + emit transactionCreated(tx, dst_addr, "", 10); // todo: return true mixincount }); } @@ -556,6 +556,21 @@ UnsignedTransaction * Wallet::loadTxFile(const QString &fileName) return result; } +UnsignedTransaction * Wallet::loadTxFromBase64Str(const QString &unsigned_tx) +{ + Monero::UnsignedTransaction * ptImpl = m_walletImpl->loadUnsignedTxFromBase64Str(unsigned_tx.toStdString()); + UnsignedTransaction * result = new UnsignedTransaction(ptImpl, m_walletImpl, this); + return result; +} + +PendingTransaction * Wallet::loadSignedTxFile(const QString &fileName) +{ + qDebug() << "Tying to load " << fileName; + Monero::PendingTransaction * ptImpl = m_walletImpl->loadSignedTx(fileName.toStdString()); + PendingTransaction * result = new PendingTransaction(ptImpl, this); + return result; +} + bool Wallet::submitTxFile(const QString &fileName) const { qDebug() << "Trying to submit " << fileName; diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index ac2c5db..6cf1ef4 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -250,6 +250,12 @@ public: //! Sign a transfer from file Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName); + //! Load an unsigned transaction from a base64 encoded string + Q_INVOKABLE UnsignedTransaction * loadTxFromBase64Str(const QString &unsigned_tx); + + //! Load a signed transaction from file + Q_INVOKABLE PendingTransaction * loadSignedTxFile(const QString &fileName); + //! Submit a transfer from file Q_INVOKABLE bool submitTxFile(const QString &fileName) const; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index e4d247a..b6d9a35 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -13,12 +13,15 @@ #include "widgets/ccswidget.h" #include "widgets/redditwidget.h" #include "dialog/txconfdialog.h" +#include "dialog/txconfadvdialog.h" #include "dialog/debuginfodialog.h" #include "dialog/walletinfodialog.h" #include "dialog/torinfodialog.h" #include "dialog/viewonlydialog.h" +#include "dialog/broadcasttxdialog.h" #include "utils/utils.h" #include "utils/config.h" +#include "utils/daemonrpc.h" #include "components.h" #include "calcwindow.h" #include "ui_mainwindow.h" @@ -474,6 +477,10 @@ void MainWindow::initMenu() { // Tools connect(ui->actionSignVerify, &QAction::triggered, this, &MainWindow::menuSignVerifyClicked); connect(ui->actionVerifyTxProof, &QAction::triggered, this, &MainWindow::menuVerifyTxProof); + connect(ui->actionLoadUnsignedTxFromFile, &QAction::triggered, this, &MainWindow::loadUnsignedTx); + connect(ui->actionLoadUnsignedTxFromClipboard, &QAction::triggered, this, &MainWindow::loadUnsignedTxFromClipboard); + connect(ui->actionLoadSignedTxFromFile, &QAction::triggered, this, &MainWindow::loadSignedTx); + connect(ui->actionLoadSignedTxFromText, &QAction::triggered, this, &MainWindow::loadSignedTxFromText); // About screen connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked); @@ -702,15 +709,26 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin } else { const auto &description = m_ctx->tmpTxDescription; - auto *dialog = new TxConfDialog(tx, address, description, mixin, this); + auto *dialog = new TxConfDialog(m_ctx, tx, address, description, mixin, this); switch (dialog->exec()) { case QDialog::Rejected: - m_ctx->onCancelTransaction(tx, address); + { + 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(); } } @@ -1157,6 +1175,64 @@ void MainWindow::cleanupBeforeClose() { this->saveGeo(); } +void MainWindow::loadUnsignedTx() { + QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*unsigned_monero_tx)"); + if (fn.isEmpty()) return; + UnsignedTransaction *tx = m_ctx->currentWallet->loadTxFile(fn); + auto err = m_ctx->currentWallet->errorString(); + if (!err.isEmpty()) { + QMessageBox::warning(this, "Load transaction from file", QString("Failed to load transaction.\n\n%1").arg(err)); + return; + } + + this->createUnsignedTxDialog(tx); +} + +void MainWindow::loadUnsignedTxFromClipboard() { + QString unsigned_tx = Utils::copyFromClipboard(); + if (unsigned_tx.isEmpty()) { + QMessageBox::warning(this, "Load unsigned transaction from clipboard", "Clipboard is empty"); + return; + } + UnsignedTransaction *tx = m_ctx->currentWallet->loadTxFromBase64Str(unsigned_tx); + auto err = m_ctx->currentWallet->errorString(); + if (!err.isEmpty()) { + QMessageBox::warning(this, "Load unsigned transaction from clipboard", QString("Failed to load transaction.\n\n%1").arg(err)); + return; + } + + this->createUnsignedTxDialog(tx); +} + +void MainWindow::loadSignedTx() { + QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*signed_monero_tx)"); + if (fn.isEmpty()) return; + PendingTransaction *tx = m_ctx->currentWallet->loadSignedTxFile(fn); + auto err = m_ctx->currentWallet->errorString(); + if (!err.isEmpty()) { + QMessageBox::warning(this, "Load signed transaction from file", err); + return; + } + + auto *dialog = new TxConfAdvDialog(m_ctx, "", this); + dialog->setTransaction(tx); + dialog->exec(); + dialog->deleteLater(); +} + +void MainWindow::loadSignedTxFromText() { + auto dialog = new BroadcastTxDialog(this, m_ctx); + dialog->exec(); + dialog->deleteLater(); +} + +void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) { + auto *dialog = new TxConfAdvDialog(m_ctx, "", this); + dialog->setUnsignedTransaction(tx); + dialog->exec(); + dialog->deleteLater(); +} + MainWindow::~MainWindow() { delete ui; } diff --git a/src/mainwindow.h b/src/mainwindow.h index e039bb7..6832dc7 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -101,10 +101,15 @@ public slots: void onAddContact(const QString &address, const QString &name); void showRestoreHeightDialog(); + // offline tx signing void exportKeyImages(); void importKeyImages(); void exportOutputs(); void importOutputs(); + void loadUnsignedTx(); + void loadUnsignedTxFromClipboard(); + void loadSignedTx(); + void loadSignedTxFromText(); // libwalletqt void onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str); @@ -136,6 +141,7 @@ private: void showDebugInfo(); void showNodeExhaustedMessage(); void showWSNodeExhaustedMessage(); + void createUnsignedTxDialog(UnsignedTransaction *tx); WalletWizard *createWizard(WalletWizard::Page startPage); diff --git a/src/mainwindow.ui b/src/mainwindow.ui index 878ee41..0409137 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -375,9 +375,26 @@ Tools + + + Load unsigned transaction + + + + + + + Broadcast transaction + + + + + + + @@ -611,6 +628,36 @@ Show XMRig + + + Transaction + + + + + Submit transaction file + + + + + From file + + + + + From file + + + + + From clipboard + + + + + From text + + diff --git a/src/receivewidget.cpp b/src/receivewidget.cpp index 813eb17..6cbace7 100644 --- a/src/receivewidget.cpp +++ b/src/receivewidget.cpp @@ -141,7 +141,8 @@ void ReceiveWidget::setQrCode(const QString &address){ void ReceiveWidget::showQrCodeDialog() { QModelIndex index = ui->addresses->currentIndex(); QString address = index.model()->data(index.siblingAtColumn(SubaddressModel::Address), Qt::UserRole).toString(); - auto *dialog = new QrCodeDialog(this, address, "Address"); + QrCode qr(address, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::HIGH); + auto *dialog = new QrCodeDialog(this, qr, "Address"); dialog->exec(); dialog->deleteLater(); } diff --git a/src/sendwidget.cpp b/src/sendwidget.cpp index d08454c..dc90cb9 100644 --- a/src/sendwidget.cpp +++ b/src/sendwidget.cpp @@ -1,7 +1,6 @@ // SPDX-License-Identifier: BSD-3-Clause // Copyright (c) 2020, The Monero Project. - #include #include "sendwidget.h" #include "widgets/ccswidget.h" diff --git a/src/utils/daemonrpc.cpp b/src/utils/daemonrpc.cpp new file mode 100644 index 0000000..c48be2e --- /dev/null +++ b/src/utils/daemonrpc.cpp @@ -0,0 +1,89 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020, The Monero Project. + +#include "daemonrpc.h" + +#include + +DaemonRpc::DaemonRpc(QObject *parent, UtilsNetworking *network, QString daemonAddress) + : QObject(parent) + , m_network(network) + , m_daemonAddress(std::move(daemonAddress)) +{ +} + +void DaemonRpc::sendRawTransaction(const QString &tx_as_hex, bool do_not_relay, bool do_sanity_checks) { + QJsonObject req; + req["tx_as_hex"] = tx_as_hex; + req["do_not_relay"] = do_not_relay; + req["do_sanity_checks"] = do_sanity_checks; + + QString url = QString("%1/send_raw_transaction").arg(m_daemonAddress); + QNetworkReply *reply = m_network->postJson(url, req); + connect(reply, &QNetworkReply::finished, std::bind(&DaemonRpc::onResponse, this, reply, Endpoint::SEND_RAW_TRANSACTION)); +} + +void DaemonRpc::onResponse(QNetworkReply *reply, Endpoint endpoint) { + const auto ok = reply->error() == QNetworkReply::NoError; + const auto err = reply->errorString(); + + QByteArray data = reply->readAll(); + QJsonObject obj; + if (!data.isEmpty() && Utils::validateJSON(data)) { + auto doc = QJsonDocument::fromJson(data); + obj = doc.object(); + } + else if (!ok) { + emit ApiResponse(DaemonResponse(false, endpoint, err)); + return; + } + else { + emit ApiResponse(DaemonResponse(false, endpoint, "Invalid response from daemon")); + return; + } + + if (obj.value("status").toString() != "OK") { + QString failedMsg; + switch (endpoint) { + case SEND_RAW_TRANSACTION: + failedMsg = this->onSendRawTransactionFailed(obj); + break; + default: + failedMsg = obj.value("status").toString(); + } + + emit ApiResponse(DaemonResponse(false, endpoint, failedMsg, obj)); + return; + } + + reply->deleteLater(); + emit ApiResponse(DaemonResponse(true, endpoint, "", obj)); +} + +QString DaemonRpc::onSendRawTransactionFailed(const QJsonObject &obj) { + QString message = [&obj]{ + if (obj.value("double_spend").toBool()) + return "Transaction is a double spend"; + if (obj.value("fee_too_low").toBool()) + return "Fee is too low"; + if (obj.value("invalid_input").toBool()) + return "Output is invalid"; + if (obj.value("low_mixin").toBool()) + return "Mixin count is too low"; + if (obj.value("overspend").toBool()) + return "Transaction uses more money than available"; + if (obj.value("too_big").toBool()) + return "Transaction size is too big"; + return "Daemon returned an unknown error"; + }(); + + return QString("Transaction failed: %1").arg(message); +} + +void DaemonRpc::setDaemonAddress(const QString &daemonAddress) { + m_daemonAddress = daemonAddress; +} + +void DaemonRpc::setNetwork(UtilsNetworking *network) { + m_network = network; +} \ No newline at end of file diff --git a/src/utils/daemonrpc.h b/src/utils/daemonrpc.h new file mode 100644 index 0000000..8100ff7 --- /dev/null +++ b/src/utils/daemonrpc.h @@ -0,0 +1,49 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020, The Monero Project. + +#ifndef FEATHER_DAEMON_RPC_H +#define FEATHER_DAEMON_RPC_H + +#include + +#include "utils/networking.h" + +class DaemonRpc : public QObject { + Q_OBJECT + +public: + enum Endpoint { + SEND_RAW_TRANSACTION = 0 + }; + + struct DaemonResponse { + explicit DaemonResponse(bool ok, Endpoint endpoint, QString status, QJsonObject obj = {}) + : ok(ok), endpoint(endpoint), status(std::move(status)), obj(std::move(obj)) {}; + + bool ok; + DaemonRpc::Endpoint endpoint; + QString status; + QJsonObject obj; + }; + + explicit DaemonRpc(QObject *parent, UtilsNetworking *network, QString daemonAddress); + + void sendRawTransaction(const QString &tx_as_hex, bool do_not_relay = false, bool do_sanity_checks = true); + + void setDaemonAddress(const QString &daemonAddress); + void setNetwork(UtilsNetworking *network); + +signals: + void ApiResponse(DaemonResponse resp); + +private slots: + void onResponse(QNetworkReply *reply, Endpoint endpoint); + QString onSendRawTransactionFailed(const QJsonObject &obj); + +private: + UtilsNetworking *m_network; + QString m_daemonAddress; +}; + + +#endif //FEATHER_DAEMON_RPC_H diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index f5b417a..098f0f2 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -68,7 +68,7 @@ QByteArray Utils::fileOpenQRC(const QString &path) { bool Utils::fileWrite(const QString &path, const QString &data) { QFile file(path); if(file.open(QIODevice::WriteOnly)){ - QTextStream out(&file); out << data << endl; + QTextStream out(&file); out << data << Qt::endl; file.close(); return true; } @@ -362,6 +362,15 @@ void Utils::copyToClipboard(const QString &string){ #endif } +QString Utils::copyFromClipboard() { + QClipboard * clipboard = QApplication::clipboard(); + if (!clipboard) { + qWarning() << "Unable to access clipboard"; + return ""; + } + return clipboard->text(); +} + QString Utils::blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid) { if (blockExplorer == "exploremonero.com") { if (nettype == NetworkType::MAINNET) { diff --git a/src/utils/utils.h b/src/utils/utils.h index 42c4155..1595de6 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -85,6 +85,7 @@ public: static QStandardItem *qStandardItem(const QString &text); static QStandardItem *qStandardItem(const QString &text, QFont &font); static void copyToClipboard(const QString &string); + static QString copyFromClipboard(); static QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid); static QString getUnixAccountName(); static QString xdgDesktopEntry(); diff --git a/src/utils/xmrtoapi.cpp b/src/utils/xmrtoapi.cpp index 03bc6a3..83c4808 100644 --- a/src/utils/xmrtoapi.cpp +++ b/src/utils/xmrtoapi.cpp @@ -41,7 +41,6 @@ void XmrToApi::getOrderStatus(const QString &uuid) { void XmrToApi::onResponse(QNetworkReply *reply, Endpoint endpoint) { const auto ok = reply->error() == QNetworkReply::NoError; const auto err = reply->errorString(); - reply->deleteLater(); QByteArray data = reply->readAll(); QJsonObject obj; @@ -64,6 +63,7 @@ void XmrToApi::onResponse(QNetworkReply *reply, Endpoint endpoint) { return; } + reply->deleteLater(); emit ApiResponse(XmrToResponse(true, endpoint, "", obj)); }