Offline transaction signing

This commit is contained in:
tobtoht 2020-10-16 05:05:05 +02:00
parent 44a2fc30dc
commit 216b2e0c5e
35 changed files with 1334 additions and 52 deletions

View file

@ -30,7 +30,7 @@ if(DEBUG)
set(CMAKE_VERBOSE_MAKEFILE ON) set(CMAKE_VERBOSE_MAKEFILE ON)
endif() endif()
set(MONERO_HEAD "f6587d7943a19c55a5b78af1a89b22c130513b73") set(MONERO_HEAD "c2b7b50fdea2e66a593f9c9109ebd742f69ad9d1")
set(BUILD_GUI_DEPS ON) set(BUILD_GUI_DEPS ON)
set(ARCH "x86-64") set(ARCH "x86-64")
set(BUILD_64 ON) set(BUILD_64 ON)

2
monero

@ -1 +1 @@
Subproject commit f6587d7943a19c55a5b78af1a89b22c130513b73 Subproject commit c2b7b50fdea2e66a593f9c9109ebd742f69ad9d1

View file

@ -0,0 +1,62 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "broadcasttxdialog.h"
#include "ui_broadcasttxdialog.h"
#include <QMessageBox>
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;
}

View file

@ -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 <QDialog>
#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

View file

@ -0,0 +1,111 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BroadcastTxDialog</class>
<widget class="QDialog" name="BroadcastTxDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>797</width>
<height>575</height>
</rect>
</property>
<property name="windowTitle">
<string>Broadcast transaction</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction:</string>
</property>
</widget>
</item>
<item>
<widget class="QPlainTextEdit" name="transaction">
<property name="minimumSize">
<size>
<width>500</width>
<height>0</height>
</size>
</property>
<property name="placeholderText">
<string>Paste hex encoded signed tx string here</string>
</property>
</widget>
</item>
<item>
<widget class="QGroupBox" name="groupBox">
<property name="title">
<string>Node</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QRadioButton" name="radio_useDefault">
<property name="text">
<string>Use currently connected node</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QRadioButton" name="radio_useCustom">
<property name="text">
<string>Use custom node</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="customNode"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>All transactions are broadcast over Tor</string>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_Broadcast">
<property name="text">
<string>Broadcast</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_Close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -9,15 +9,14 @@
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
QrCodeDialog::QrCodeDialog(QWidget *parent, const QString &text, const QString &title) QrCodeDialog::QrCodeDialog(QWidget *parent, const QrCode &qrCode, const QString &title)
: QDialog(parent) : QDialog(parent)
, ui(new Ui::QrCodeDialog) , ui(new Ui::QrCodeDialog)
{ {
ui->setupUi(this); ui->setupUi(this);
this->setWindowTitle(title); this->setWindowTitle(title);
m_qrc = new QrCode(text, QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::HIGH); m_pixmap = qrCode.toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio);
m_pixmap = m_qrc->toPixmap(1).scaled(500, 500, Qt::KeepAspectRatio);
ui->QrCode->setPixmap(m_pixmap); ui->QrCode->setPixmap(m_pixmap);
connect(ui->btn_CopyImage, &QPushButton::clicked, this, &QrCodeDialog::copyImage); connect(ui->btn_CopyImage, &QPushButton::clicked, this, &QrCodeDialog::copyImage);
@ -32,7 +31,11 @@ QrCodeDialog::QrCodeDialog(QWidget *parent, const QString &text, const QString &
QrCodeDialog::~QrCodeDialog() QrCodeDialog::~QrCodeDialog()
{ {
delete ui; 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() { void QrCodeDialog::copyImage() {

View file

@ -16,15 +16,15 @@ class QrCodeDialog : public QDialog
Q_OBJECT Q_OBJECT
public: 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; ~QrCodeDialog() override;
void setQrCode(const QrCode &qrCode);
private: private:
void copyImage(); void copyImage();
void saveImage(); void saveImage();
Ui::QrCodeDialog *ui; Ui::QrCodeDialog *ui;
QrCode *m_qrc;
QPixmap m_pixmap; QPixmap m_pixmap;
}; };

View file

@ -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 <QFileDialog>
#include <QMessageBox>
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;
}

View file

@ -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 <QDialog>
#include <QStandardItemModel>
#include <QAbstractButton>
#include <QMenu>
#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

View file

@ -0,0 +1,279 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>TxConfAdvDialog</class>
<widget class="QDialog" name="TxConfAdvDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>542</height>
</rect>
</property>
<property name="minimumSize">
<size>
<width>800</width>
<height>0</height>
</size>
</property>
<property name="windowTitle">
<string>Transaction</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label">
<property name="text">
<string>Transaction ID:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="txid">
<property name="text">
<string>txid</string>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_amount">
<property name="text">
<string>Amount: </string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLabel" name="amount">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_fee">
<property name="text">
<string>Fee: </string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLabel" name="fee">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="Line" name="line_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Total:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLabel" name="total">
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<widget class="QLabel" name="label_description">
<property name="text">
<string>Description:</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_size">
<property name="text">
<string>Size: </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_unlockTime">
<property name="text">
<string>Unlock time: </string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_ringSize">
<property name="text">
<string>Ringsize:</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer_2">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Minimum</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>10</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_inputs">
<property name="text">
<string>Inputs</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="inputs">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_outputs">
<property name="text">
<string>Outputs</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="outputs">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QToolButton" name="btn_exportUnsigned">
<property name="text">
<string>Export unsigned</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="btn_exportSigned">
<property name="text">
<string>Export signed</string>
</property>
<property name="popupMode">
<enum>QToolButton::InstantPopup</enum>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="btn_sign">
<property name="text">
<string>Sign</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_send">
<property name="text">
<string>Send</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="btn_close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View file

@ -6,12 +6,15 @@
#include "appcontext.h" #include "appcontext.h"
#include "utils/config.h" #include "utils/config.h"
#include "model/ModelUtils.h" #include "model/ModelUtils.h"
#include "libwalletqt/WalletManager.h"
#include "txconfadvdialog.h"
#include <QMessageBox> #include <QMessageBox>
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) : QDialog(parent)
, ui(new Ui::TxConfDialog) , ui(new Ui::TxConfDialog)
, m_ctx(ctx)
, m_tx(tx) , m_tx(tx)
, m_address(address) , m_address(address)
, m_description(description) , m_description(description)
@ -43,29 +46,16 @@ TxConfDialog::TxConfDialog(PendingTransaction *tx, const QString &address, const
ui->label_address->setFont(ModelUtils::getMonospaceFont()); ui->label_address->setFont(ModelUtils::getMonospaceFont());
ui->label_address->setToolTip(address); 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(); this->adjustSize();
} }
void TxConfDialog::showAdvanced() { void TxConfDialog::setShowAdvanced() {
const auto amount = m_tx->amount() / AppContext::cdiv; this->showAdvanced = true;
const auto fee = m_tx->fee() / AppContext::cdiv; QDialog::reject();
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);
} }
TxConfDialog::~TxConfDialog() { TxConfDialog::~TxConfDialog() {

View file

@ -7,6 +7,7 @@
#include <QDialog> #include <QDialog>
#include "libwalletqt/PendingTransaction.h" #include "libwalletqt/PendingTransaction.h"
#include "libwalletqt/WalletManager.h" #include "libwalletqt/WalletManager.h"
#include "appcontext.h"
namespace Ui { namespace Ui {
class TxConfDialog; class TxConfDialog;
@ -17,13 +18,18 @@ class TxConfDialog : public QDialog
Q_OBJECT Q_OBJECT
public: 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; ~TxConfDialog() override;
bool showAdvanced = false;
private: private:
void showAdvanced(); void setShowAdvanced();
void saveToFile();
void copyToClipboard();
Ui::TxConfDialog *ui; Ui::TxConfDialog *ui;
AppContext *m_ctx;
PendingTransaction *m_tx; PendingTransaction *m_tx;
QString m_address; QString m_address;
QString m_description; QString m_description;

View file

@ -6,7 +6,7 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>655</width> <width>844</width>
<height>248</height> <height>248</height>
</rect> </rect>
</property> </property>

View file

@ -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<quint32> ConstructionInfo::subaddressIndices() const {
return m_subaddressIndices;
}
QVector<QString> ConstructionInfo::subaddresses() const {
return m_subaddresses;
}
quint64 ConstructionInfo::minMixinCount() const {
return m_minMixinCount;
}
QList<Input *> ConstructionInfo::inputs() const {
return m_inputs;
}
QList<Transfer *> 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);
}
}

View file

@ -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 <wallet/api/wallet2_api.h>
#include <QObject>
#include <QSet>
class Input;
class Transfer;
class ConstructionInfo : public QObject
{
Q_OBJECT
Q_PROPERTY(quint64 unlockTime READ unlockTime)
Q_PROPERTY(QSet<quint32> subaddressIndices READ subaddressIndices)
Q_PROPERTY(QVector<QString> subaddresses READ subaddresses)
Q_PROPERTY(quint64 minMixinCount READ minMixinCount)
Q_PROPERTY(QList<Input*> inputs READ inputs)
Q_PROPERTY(QList<Transfer*> outputs READ outputs)
public:
quint64 unlockTime() const;
QSet<quint32> subaddressIndices() const;
QVector<QString> subaddresses() const;
quint64 minMixinCount() const;
QList<Input*> inputs() const;
QList<Transfer*> outputs() const;
private:
explicit ConstructionInfo(const Monero::TransactionConstructionInfo *pimpl, QObject *parent = nullptr);
friend class PendingTransactionInfo;
friend class UnsignedTransaction;
quint64 m_unlockTime;
QSet<quint32> m_subaddressIndices;
QVector<QString> m_subaddresses;
quint64 m_minMixinCount;
mutable QList<Input*> m_inputs;
mutable QList<Transfer*> m_outputs;
};
#endif //FEATHER_CONSTRUCTIONINFO_H

29
src/libwalletqt/Input.h Normal file
View file

@ -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 <wallet/api/wallet2_api.h>
#include <QObject>
#include <utility>
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

View file

@ -16,10 +16,12 @@ QString PendingTransaction::errorString() const
bool PendingTransaction::commit() bool PendingTransaction::commit()
{ {
// Save transaction to file if fileName is set. return m_pimpl->commit();
if(!m_fileName.isEmpty()) }
return m_pimpl->commit(m_fileName.toStdString());
return m_pimpl->commit(m_fileName.toStdString()); bool PendingTransaction::saveToFile(const QString &fileName)
{
return m_pimpl->commit(fileName.toStdString());
} }
quint64 PendingTransaction::amount() const quint64 PendingTransaction::amount() const
@ -37,7 +39,6 @@ quint64 PendingTransaction::fee() const
return m_pimpl->fee(); return m_pimpl->fee();
} }
QStringList PendingTransaction::txid() const QStringList PendingTransaction::txid() const
{ {
QStringList list; QStringList list;
@ -63,9 +64,33 @@ QList<QVariant> PendingTransaction::subaddrIndices() const
return result; 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) PendingTransaction::PendingTransaction(Monero::PendingTransaction *pt, QObject *parent)

View file

@ -9,6 +9,8 @@
#include <QVariant> #include <QVariant>
#include <wallet/api/wallet2_api.h> #include <wallet/api/wallet2_api.h>
#include "PendingTransactionInfo.h"
//namespace Monero { //namespace Monero {
//class PendingTransaction; //class PendingTransaction;
@ -30,7 +32,7 @@ public:
enum Status { enum Status {
Status_Ok = Monero::PendingTransaction::Status_Ok, Status_Ok = Monero::PendingTransaction::Status_Ok,
Status_Error = Monero::PendingTransaction::Status_Error, Status_Error = Monero::PendingTransaction::Status_Error,
Status_Critical = Monero::PendingTransaction::Status_Critical Status_Critical = Monero::PendingTransaction::Status_Critical
}; };
Q_ENUM(Status) Q_ENUM(Status)
@ -45,21 +47,27 @@ public:
Status status() const; Status status() const;
QString errorString() const; QString errorString() const;
Q_INVOKABLE bool commit(); Q_INVOKABLE bool commit();
bool saveToFile(const QString &fileName);
quint64 amount() const; quint64 amount() const;
quint64 dust() const; quint64 dust() const;
quint64 fee() const; quint64 fee() const;
QStringList txid() const; QStringList txid() const;
quint64 txCount() const; quint64 txCount() const;
QList<QVariant> subaddrIndices() const; QList<QVariant> 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: private:
explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = 0); explicit PendingTransaction(Monero::PendingTransaction * pt, QObject *parent = nullptr);
private: private:
friend class Wallet; friend class Wallet;
Monero::PendingTransaction * m_pimpl; Monero::PendingTransaction * m_pimpl;
QString m_fileName; mutable QList<PendingTransactionInfo*> m_pending_tx_info;
}; };
#endif // PENDINGTRANSACTION_H #endif // PENDINGTRANSACTION_H

View file

@ -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()))
{
}

View file

@ -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 <wallet/api/wallet2_api.h>
#include "ConstructionInfo.h"
#include <QObject>
#include <QSet>
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<quint32> subaddressIndices READ subaddressIndices)
Q_PROPERTY(QVector<QString> subaddresses READ subaddresses)
Q_PROPERTY(quint64 minMixinCount READ minMixinCount)
Q_PROPERTY(QList<Input*> inputs READ inputs)
Q_PROPERTY(QList<Transfer*> 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

View file

@ -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)) {}; explicit Transfer(uint64_t _amount, QString _address, QObject *parent = 0): QObject(parent), m_amount(_amount), m_address(std::move(_address)) {};
private: private:
friend class TransactionInfo; friend class TransactionInfo;
friend class ConstructionInfo;
quint64 m_amount; quint64 m_amount;
QString m_address; QString m_address;
public: public:

View file

@ -83,6 +83,21 @@ void UnsignedTransaction::setFilename(const QString &fileName)
m_fileName = 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) UnsignedTransaction::UnsignedTransaction(Monero::UnsignedTransaction *pt, Monero::Wallet *walletImpl, QObject *parent)
: QObject(parent), m_pimpl(pt), m_walletImpl(walletImpl) : QObject(parent), m_pimpl(pt), m_walletImpl(walletImpl)
{ {

View file

@ -7,14 +7,13 @@
#include <QObject> #include <QObject>
#include <wallet/api/wallet2_api.h> #include <wallet/api/wallet2_api.h>
#include "libwalletqt/PendingTransactionInfo.h"
class UnsignedTransaction : public QObject class UnsignedTransaction : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(Status status READ status) Q_PROPERTY(Status status READ status)
Q_PROPERTY(QString errorString READ errorString) Q_PROPERTY(QString errorString READ errorString)
// Q_PROPERTY(QList<qulonglong> amount READ amount)
// Q_PROPERTY(QList<qulonglong> fee READ fee)
Q_PROPERTY(quint64 txCount READ txCount) Q_PROPERTY(quint64 txCount READ txCount)
Q_PROPERTY(QString confirmationMessage READ confirmationMessage) Q_PROPERTY(QString confirmationMessage READ confirmationMessage)
Q_PROPERTY(QStringList recipientAddress READ recipientAddress) Q_PROPERTY(QStringList recipientAddress READ recipientAddress)
@ -41,15 +40,19 @@ public:
quint64 minMixinCount() const; quint64 minMixinCount() const;
Q_INVOKABLE bool sign(const QString &fileName) const; Q_INVOKABLE bool sign(const QString &fileName) const;
Q_INVOKABLE void setFilename(const QString &fileName); Q_INVOKABLE void setFilename(const QString &fileName);
void refresh();
ConstructionInfo * constructionInfo(int index) const;
private: private:
explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, Monero::Wallet *walletImpl, QObject *parent = 0); explicit UnsignedTransaction(Monero::UnsignedTransaction * pt, Monero::Wallet *walletImpl, QObject *parent = nullptr);
~UnsignedTransaction(); ~UnsignedTransaction();
private: private:
friend class Wallet; friend class Wallet;
Monero::UnsignedTransaction * m_pimpl; Monero::UnsignedTransaction * m_pimpl;
QString m_fileName; QString m_fileName;
Monero::Wallet * m_walletImpl; Monero::Wallet * m_walletImpl;
mutable QList<ConstructionInfo*> m_construction_info;
}; };
#endif // UNSIGNEDTRANSACTION_H #endif // UNSIGNEDTRANSACTION_H

View file

@ -529,7 +529,7 @@ void Wallet::createTransactionSingleAsync(const QString &key_image, const QStrin
{ {
m_scheduler.run([this, key_image, dst_addr, outputs, priority] { m_scheduler.run([this, key_image, dst_addr, outputs, priority] {
PendingTransaction *tx = createTransactionSingle(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; 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 bool Wallet::submitTxFile(const QString &fileName) const
{ {
qDebug() << "Trying to submit " << fileName; qDebug() << "Trying to submit " << fileName;

View file

@ -250,6 +250,12 @@ public:
//! Sign a transfer from file //! Sign a transfer from file
Q_INVOKABLE UnsignedTransaction * loadTxFile(const QString &fileName); 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 //! Submit a transfer from file
Q_INVOKABLE bool submitTxFile(const QString &fileName) const; Q_INVOKABLE bool submitTxFile(const QString &fileName) const;

View file

@ -13,12 +13,15 @@
#include "widgets/ccswidget.h" #include "widgets/ccswidget.h"
#include "widgets/redditwidget.h" #include "widgets/redditwidget.h"
#include "dialog/txconfdialog.h" #include "dialog/txconfdialog.h"
#include "dialog/txconfadvdialog.h"
#include "dialog/debuginfodialog.h" #include "dialog/debuginfodialog.h"
#include "dialog/walletinfodialog.h" #include "dialog/walletinfodialog.h"
#include "dialog/torinfodialog.h" #include "dialog/torinfodialog.h"
#include "dialog/viewonlydialog.h" #include "dialog/viewonlydialog.h"
#include "dialog/broadcasttxdialog.h"
#include "utils/utils.h" #include "utils/utils.h"
#include "utils/config.h" #include "utils/config.h"
#include "utils/daemonrpc.h"
#include "components.h" #include "components.h"
#include "calcwindow.h" #include "calcwindow.h"
#include "ui_mainwindow.h" #include "ui_mainwindow.h"
@ -474,6 +477,10 @@ void MainWindow::initMenu() {
// Tools // Tools
connect(ui->actionSignVerify, &QAction::triggered, this, &MainWindow::menuSignVerifyClicked); connect(ui->actionSignVerify, &QAction::triggered, this, &MainWindow::menuSignVerifyClicked);
connect(ui->actionVerifyTxProof, &QAction::triggered, this, &MainWindow::menuVerifyTxProof); 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 // About screen
connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::menuAboutClicked);
@ -702,15 +709,26 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin
} else { } else {
const auto &description = m_ctx->tmpTxDescription; 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()) { switch (dialog->exec()) {
case QDialog::Rejected: case QDialog::Rejected:
m_ctx->onCancelTransaction(tx, address); {
if (!dialog->showAdvanced)
m_ctx->onCancelTransaction(tx, address);
break; break;
}
case QDialog::Accepted: case QDialog::Accepted:
m_ctx->currentWallet->commitTransactionAsync(tx); m_ctx->currentWallet->commitTransactionAsync(tx);
break; 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(); 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() { MainWindow::~MainWindow() {
delete ui; delete ui;
} }

View file

@ -101,10 +101,15 @@ public slots:
void onAddContact(const QString &address, const QString &name); void onAddContact(const QString &address, const QString &name);
void showRestoreHeightDialog(); void showRestoreHeightDialog();
// offline tx signing
void exportKeyImages(); void exportKeyImages();
void importKeyImages(); void importKeyImages();
void exportOutputs(); void exportOutputs();
void importOutputs(); void importOutputs();
void loadUnsignedTx();
void loadUnsignedTxFromClipboard();
void loadSignedTx();
void loadSignedTxFromText();
// libwalletqt // libwalletqt
void onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str); void onBalanceUpdated(double balance, double unlocked, const QString &balance_str, const QString &unlocked_str);
@ -136,6 +141,7 @@ private:
void showDebugInfo(); void showDebugInfo();
void showNodeExhaustedMessage(); void showNodeExhaustedMessage();
void showWSNodeExhaustedMessage(); void showWSNodeExhaustedMessage();
void createUnsignedTxDialog(UnsignedTransaction *tx);
WalletWizard *createWizard(WalletWizard::Page startPage); WalletWizard *createWizard(WalletWizard::Page startPage);

View file

@ -375,9 +375,26 @@
<property name="title"> <property name="title">
<string>Tools</string> <string>Tools</string>
</property> </property>
<widget class="QMenu" name="menuLoad_transaction">
<property name="title">
<string>Load unsigned transaction</string>
</property>
<addaction name="actionLoadUnsignedTxFromFile"/>
<addaction name="actionLoadUnsignedTxFromClipboard"/>
</widget>
<widget class="QMenu" name="menuLoad_signed_transaction">
<property name="title">
<string>Broadcast transaction</string>
</property>
<addaction name="actionLoadSignedTxFromFile"/>
<addaction name="actionLoadSignedTxFromText"/>
</widget>
<addaction name="actionSignVerify"/> <addaction name="actionSignVerify"/>
<addaction name="actionVerifyTxProof"/> <addaction name="actionVerifyTxProof"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="menuLoad_transaction"/>
<addaction name="menuLoad_signed_transaction"/>
<addaction name="separator"/>
<addaction name="actionCalculator"/> <addaction name="actionCalculator"/>
<addaction name="actionCreateDesktopEntry"/> <addaction name="actionCreateDesktopEntry"/>
</widget> </widget>
@ -611,6 +628,36 @@
<string>Show XMRig</string> <string>Show XMRig</string>
</property> </property>
</action> </action>
<action name="actionImportTransaction">
<property name="text">
<string>Transaction</string>
</property>
</action>
<action name="actionSubmitTransaction">
<property name="text">
<string>Submit transaction file</string>
</property>
</action>
<action name="actionLoadUnsignedTxFromFile">
<property name="text">
<string>From file</string>
</property>
</action>
<action name="actionLoadSignedTxFromFile">
<property name="text">
<string>From file</string>
</property>
</action>
<action name="actionLoadUnsignedTxFromClipboard">
<property name="text">
<string>From clipboard</string>
</property>
</action>
<action name="actionLoadSignedTxFromText">
<property name="text">
<string>From text</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

View file

@ -141,7 +141,8 @@ void ReceiveWidget::setQrCode(const QString &address){
void ReceiveWidget::showQrCodeDialog() { void ReceiveWidget::showQrCodeDialog() {
QModelIndex index = ui->addresses->currentIndex(); QModelIndex index = ui->addresses->currentIndex();
QString address = index.model()->data(index.siblingAtColumn(SubaddressModel::Address), Qt::UserRole).toString(); 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->exec();
dialog->deleteLater(); dialog->deleteLater();
} }

View file

@ -1,7 +1,6 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project. // Copyright (c) 2020, The Monero Project.
#include <QMessageBox> #include <QMessageBox>
#include "sendwidget.h" #include "sendwidget.h"
#include "widgets/ccswidget.h" #include "widgets/ccswidget.h"

89
src/utils/daemonrpc.cpp Normal file
View file

@ -0,0 +1,89 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2020, The Monero Project.
#include "daemonrpc.h"
#include <utility>
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;
}

49
src/utils/daemonrpc.h Normal file
View file

@ -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 <QObject>
#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

View file

@ -68,7 +68,7 @@ QByteArray Utils::fileOpenQRC(const QString &path) {
bool Utils::fileWrite(const QString &path, const QString &data) { bool Utils::fileWrite(const QString &path, const QString &data) {
QFile file(path); QFile file(path);
if(file.open(QIODevice::WriteOnly)){ if(file.open(QIODevice::WriteOnly)){
QTextStream out(&file); out << data << endl; QTextStream out(&file); out << data << Qt::endl;
file.close(); file.close();
return true; return true;
} }
@ -362,6 +362,15 @@ void Utils::copyToClipboard(const QString &string){
#endif #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) { QString Utils::blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid) {
if (blockExplorer == "exploremonero.com") { if (blockExplorer == "exploremonero.com") {
if (nettype == NetworkType::MAINNET) { if (nettype == NetworkType::MAINNET) {

View file

@ -85,6 +85,7 @@ public:
static QStandardItem *qStandardItem(const QString &text); static QStandardItem *qStandardItem(const QString &text);
static QStandardItem *qStandardItem(const QString &text, QFont &font); static QStandardItem *qStandardItem(const QString &text, QFont &font);
static void copyToClipboard(const QString &string); static void copyToClipboard(const QString &string);
static QString copyFromClipboard();
static QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid); static QString blockExplorerLink(const QString &blockExplorer, NetworkType::Type nettype, const QString &txid);
static QString getUnixAccountName(); static QString getUnixAccountName();
static QString xdgDesktopEntry(); static QString xdgDesktopEntry();

View file

@ -41,7 +41,6 @@ void XmrToApi::getOrderStatus(const QString &uuid) {
void XmrToApi::onResponse(QNetworkReply *reply, Endpoint endpoint) { void XmrToApi::onResponse(QNetworkReply *reply, Endpoint endpoint) {
const auto ok = reply->error() == QNetworkReply::NoError; const auto ok = reply->error() == QNetworkReply::NoError;
const auto err = reply->errorString(); const auto err = reply->errorString();
reply->deleteLater();
QByteArray data = reply->readAll(); QByteArray data = reply->readAll();
QJsonObject obj; QJsonObject obj;
@ -64,6 +63,7 @@ void XmrToApi::onResponse(QNetworkReply *reply, Endpoint endpoint) {
return; return;
} }
reply->deleteLater();
emit ApiResponse(XmrToResponse(true, endpoint, "", obj)); emit ApiResponse(XmrToResponse(true, endpoint, "", obj));
} }