mirror of
https://github.com/feather-wallet/feather.git
synced 2024-12-22 11:39:25 +00:00
[wip] airgapped tx signing with UR
This commit is contained in:
parent
135f1987c9
commit
5f43001c88
58 changed files with 2333 additions and 521 deletions
|
@ -86,6 +86,10 @@ message(STATUS "libsodium: libraries at ${SODIUM_LIBRARY}")
|
||||||
# QrEncode
|
# QrEncode
|
||||||
find_package(QREncode REQUIRED)
|
find_package(QREncode REQUIRED)
|
||||||
|
|
||||||
|
# bc-ur
|
||||||
|
find_library(BCUR_LIBRARY bcur)
|
||||||
|
message(STATUS "bcur: libraries at ${BCUR_LIBRARY}")
|
||||||
|
|
||||||
# Polyseed
|
# Polyseed
|
||||||
find_package(Polyseed REQUIRED)
|
find_package(Polyseed REQUIRED)
|
||||||
if(Polyseed_SUBMODULE)
|
if(Polyseed_SUBMODULE)
|
||||||
|
|
2
monero
2
monero
|
@ -1 +1 @@
|
||||||
Subproject commit 4e02392274e1b5f7a038171deee2e445128b2e35
|
Subproject commit 483fa3a11a0f996f827d8b64a3c4726ff7e2684a
|
|
@ -53,6 +53,8 @@ file(GLOB SOURCE_FILES
|
||||||
"widgets/*.cpp"
|
"widgets/*.cpp"
|
||||||
"wizard/*.h"
|
"wizard/*.h"
|
||||||
"wizard/*.cpp"
|
"wizard/*.cpp"
|
||||||
|
"wizard/offline_tx_signing/*.h"
|
||||||
|
"wizard/offline_tx_signing/*.cpp"
|
||||||
"wallet/*.h"
|
"wallet/*.h"
|
||||||
"wallet/*.cpp"
|
"wallet/*.cpp"
|
||||||
"qrcode/*.h"
|
"qrcode/*.h"
|
||||||
|
@ -149,6 +151,7 @@ target_include_directories(feather PUBLIC
|
||||||
${LIBZIP_INCLUDE_DIRS}
|
${LIBZIP_INCLUDE_DIRS}
|
||||||
${ZLIB_INCLUDE_DIRS}
|
${ZLIB_INCLUDE_DIRS}
|
||||||
${POLYSEED_INCLUDE_DIR}
|
${POLYSEED_INCLUDE_DIR}
|
||||||
|
/feather/contrib/depends/x86_64-linux-gnu/include
|
||||||
)
|
)
|
||||||
|
|
||||||
if(WITH_SCANNER)
|
if(WITH_SCANNER)
|
||||||
|
@ -254,6 +257,7 @@ target_link_libraries(feather
|
||||||
${ICU_LIBRARIES}
|
${ICU_LIBRARIES}
|
||||||
${LIBZIP_LIBRARIES}
|
${LIBZIP_LIBRARIES}
|
||||||
${ZLIB_LIBRARIES}
|
${ZLIB_LIBRARIES}
|
||||||
|
${BCUR_LIBRARY}
|
||||||
)
|
)
|
||||||
|
|
||||||
if(CHECK_UPDATES)
|
if(CHECK_UPDATES)
|
||||||
|
|
|
@ -11,6 +11,8 @@
|
||||||
#include "utils/Icons.h"
|
#include "utils/Icons.h"
|
||||||
#include "utils/Utils.h"
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
#include "wizard/offline_tx_signing/OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
CoinsWidget::CoinsWidget(Wallet *wallet, QWidget *parent)
|
CoinsWidget::CoinsWidget(Wallet *wallet, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, ui(new Ui::CoinsWidget)
|
, ui(new Ui::CoinsWidget)
|
||||||
|
@ -186,7 +188,14 @@ void CoinsWidget::spendSelected() {
|
||||||
|
|
||||||
QStringList keyimages;
|
QStringList keyimages;
|
||||||
for (QModelIndex index: list) {
|
for (QModelIndex index: list) {
|
||||||
keyimages << m_model->entryFromIndex(m_proxyModel->mapToSource(index))->keyImage();
|
QString keyImage = m_model->entryFromIndex(m_proxyModel->mapToSource(index))->keyImage();
|
||||||
|
|
||||||
|
if (keyImage == "0100000000000000000000000000000000000000000000000000000000000000") {
|
||||||
|
Utils::showError(this, "Unable to select output to spend", "Selected output has unknown key image");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyimages << keyImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
m_wallet->setSelectedInputs(keyimages);
|
m_wallet->setSelectedInputs(keyimages);
|
||||||
|
@ -238,6 +247,13 @@ void CoinsWidget::onSweepOutputs() {
|
||||||
int ret = dialog.exec();
|
int ret = dialog.exec();
|
||||||
if (!ret) return;
|
if (!ret) return;
|
||||||
|
|
||||||
|
OfflineTxSigningWizard wizard(this, m_wallet);
|
||||||
|
auto r = wizard.exec();
|
||||||
|
|
||||||
|
if (r == QDialog::Rejected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
m_wallet->sweepOutputs(keyImages, dialog.address(), dialog.churn(), dialog.outputs());
|
m_wallet->sweepOutputs(keyImages, dialog.address(), dialog.churn(), dialog.outputs());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "constants.h"
|
#include "constants.h"
|
||||||
#include "dialog/BalanceDialog.h"
|
#include "dialog/BalanceDialog.h"
|
||||||
#include "dialog/DebugInfoDialog.h"
|
#include "dialog/DebugInfoDialog.h"
|
||||||
|
#include "wizard/offline_tx_signing/OfflineTxSigningWizard.h"
|
||||||
#include "dialog/PasswordDialog.h"
|
#include "dialog/PasswordDialog.h"
|
||||||
#include "dialog/TorInfoDialog.h"
|
#include "dialog/TorInfoDialog.h"
|
||||||
#include "dialog/TxBroadcastDialog.h"
|
#include "dialog/TxBroadcastDialog.h"
|
||||||
|
@ -271,6 +272,7 @@ void MainWindow::initMenu() {
|
||||||
connect(ui->actionSeed, &QAction::triggered, this, &MainWindow::showSeedDialog);
|
connect(ui->actionSeed, &QAction::triggered, this, &MainWindow::showSeedDialog);
|
||||||
connect(ui->actionKeys, &QAction::triggered, this, &MainWindow::showKeysDialog);
|
connect(ui->actionKeys, &QAction::triggered, this, &MainWindow::showKeysDialog);
|
||||||
connect(ui->actionViewOnly, &QAction::triggered, this, &MainWindow::showViewOnlyDialog);
|
connect(ui->actionViewOnly, &QAction::triggered, this, &MainWindow::showViewOnlyDialog);
|
||||||
|
connect(ui->actionKeyImageSync, &QAction::triggered, this, &MainWindow::showKeyImageSyncWizard);
|
||||||
|
|
||||||
// [Wallet] -> [Advanced]
|
// [Wallet] -> [Advanced]
|
||||||
connect(ui->actionStore_wallet, &QAction::triggered, this, &MainWindow::tryStoreWallet);
|
connect(ui->actionStore_wallet, &QAction::triggered, this, &MainWindow::tryStoreWallet);
|
||||||
|
@ -279,14 +281,6 @@ void MainWindow::initMenu() {
|
||||||
connect(ui->actionRescan_spent, &QAction::triggered, this, &MainWindow::rescanSpent);
|
connect(ui->actionRescan_spent, &QAction::triggered, this, &MainWindow::rescanSpent);
|
||||||
connect(ui->actionWallet_cache_debug, &QAction::triggered, this, &MainWindow::showWalletCacheDebugDialog);
|
connect(ui->actionWallet_cache_debug, &QAction::triggered, this, &MainWindow::showWalletCacheDebugDialog);
|
||||||
|
|
||||||
// [Wallet] -> [Advanced] -> [Export]
|
|
||||||
connect(ui->actionExportOutputs, &QAction::triggered, this, &MainWindow::exportOutputs);
|
|
||||||
connect(ui->actionExportKeyImages, &QAction::triggered, this, &MainWindow::exportKeyImages);
|
|
||||||
|
|
||||||
// [Wallet] -> [Advanced] -> [Import]
|
|
||||||
connect(ui->actionImportOutputs, &QAction::triggered, this, &MainWindow::importOutputs);
|
|
||||||
connect(ui->actionImportKeyImages, &QAction::triggered, this, &MainWindow::importKeyImages);
|
|
||||||
|
|
||||||
// [Wallet] -> [History]
|
// [Wallet] -> [History]
|
||||||
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
|
connect(ui->actionExport_CSV, &QAction::triggered, this, &MainWindow::onExportHistoryCSV);
|
||||||
|
|
||||||
|
@ -344,8 +338,6 @@ 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->actionLoadSignedTxFromFile, &QAction::triggered, this, &MainWindow::loadSignedTx);
|
||||||
connect(ui->actionLoadSignedTxFromText, &QAction::triggered, this, &MainWindow::loadSignedTxFromText);
|
connect(ui->actionLoadSignedTxFromText, &QAction::triggered, this, &MainWindow::loadSignedTxFromText);
|
||||||
connect(ui->actionImport_transaction, &QAction::triggered, this, &MainWindow::importTransaction);
|
connect(ui->actionImport_transaction, &QAction::triggered, this, &MainWindow::importTransaction);
|
||||||
|
@ -853,8 +845,20 @@ void MainWindow::onTransactionCreated(PendingTransaction *tx, const QVector<QStr
|
||||||
|
|
||||||
m_wallet->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0));
|
m_wallet->addCacheTransaction(tx->txid()[0], tx->signedTxToHex(0));
|
||||||
|
|
||||||
|
// Offline transaction signing
|
||||||
|
if (m_wallet->viewOnly()) {
|
||||||
|
OfflineTxSigningWizard wizard(this, m_wallet, tx);
|
||||||
|
wizard.exec();
|
||||||
|
|
||||||
|
if (!wizard.readyToCommit()) {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
tx = wizard.signedTx();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Show advanced dialog on multi-destination transactions
|
// Show advanced dialog on multi-destination transactions
|
||||||
if (address.size() > 1 || m_wallet->viewOnly()) {
|
if (address.size() > 1) {
|
||||||
TxConfAdvDialog dialog_adv{m_wallet, m_wallet->tmpTxDescription, this};
|
TxConfAdvDialog dialog_adv{m_wallet, m_wallet->tmpTxDescription, this};
|
||||||
dialog_adv.setTransaction(tx, !m_wallet->viewOnly());
|
dialog_adv.setTransaction(tx, !m_wallet->viewOnly());
|
||||||
dialog_adv.exec();
|
dialog_adv.exec();
|
||||||
|
@ -964,6 +968,11 @@ void MainWindow::showViewOnlyDialog() {
|
||||||
dialog.exec();
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MainWindow::showKeyImageSyncWizard() {
|
||||||
|
OfflineTxSigningWizard wizard{this, m_wallet};
|
||||||
|
wizard.exec();
|
||||||
|
}
|
||||||
|
|
||||||
void MainWindow::menuHwDeviceClicked() {
|
void MainWindow::menuHwDeviceClicked() {
|
||||||
Utils::showInfo(this, "Hardware device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice()));
|
Utils::showInfo(this, "Hardware device", QString("This wallet is backed by a %1 hardware device.").arg(this->getHardwareDevice()));
|
||||||
}
|
}
|
||||||
|
@ -1204,83 +1213,6 @@ void MainWindow::showAddressChecker() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::exportKeyImages() {
|
|
||||||
QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Key Images (*_keyImages)");
|
|
||||||
if (fn.isEmpty()) return;
|
|
||||||
if (!fn.endsWith("_keyImages")) fn += "_keyImages";
|
|
||||||
bool r = m_wallet->exportKeyImages(fn, true);
|
|
||||||
if (!r) {
|
|
||||||
Utils::showError(this, "Failed to export key images", m_wallet->errorString());
|
|
||||||
} else {
|
|
||||||
Utils::showInfo(this, "Successfully exported key images");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::importKeyImages() {
|
|
||||||
QString fn = QFileDialog::getOpenFileName(this, "Import key image file", QDir::homePath(), "Key Images (*_keyImages);;All Files (*)");
|
|
||||||
if (fn.isEmpty()) return;
|
|
||||||
bool r = m_wallet->importKeyImages(fn);
|
|
||||||
if (!r) {
|
|
||||||
Utils::showError(this, "Failed to import key images", m_wallet->errorString());
|
|
||||||
} else {
|
|
||||||
Utils::showInfo(this, "Successfully imported key images");
|
|
||||||
m_wallet->refreshModels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::exportOutputs() {
|
|
||||||
QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QString("%1/%2_%3").arg(QDir::homePath(), this->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Outputs (*_outputs)");
|
|
||||||
if (fn.isEmpty()) return;
|
|
||||||
if (!fn.endsWith("_outputs")) fn += "_outputs";
|
|
||||||
bool r = m_wallet->exportOutputs(fn, true);
|
|
||||||
if (!r) {
|
|
||||||
Utils::showError(this, "Failed to export outputs", m_wallet->errorString());
|
|
||||||
} else {
|
|
||||||
Utils::showInfo(this, "Successfully exported outputs.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::importOutputs() {
|
|
||||||
QString fn = QFileDialog::getOpenFileName(this, "Import outputs file", QDir::homePath(), "Outputs (*_outputs);;All Files (*)");
|
|
||||||
if (fn.isEmpty()) return;
|
|
||||||
bool r = m_wallet->importOutputs(fn);
|
|
||||||
if (!r) {
|
|
||||||
Utils::showError(this, "Failed to import outputs", m_wallet->errorString());
|
|
||||||
} else {
|
|
||||||
Utils::showInfo(this, "Successfully imported outputs");
|
|
||||||
m_wallet->refreshModels();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::loadUnsignedTx() {
|
|
||||||
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*unsigned_monero_tx);;All Files (*)");
|
|
||||||
if (fn.isEmpty()) return;
|
|
||||||
UnsignedTransaction *tx = m_wallet->loadTxFile(fn);
|
|
||||||
auto err = m_wallet->errorString();
|
|
||||||
if (!err.isEmpty()) {
|
|
||||||
Utils::showError(this, "Failed to load transaction", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->createUnsignedTxDialog(tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::loadUnsignedTxFromClipboard() {
|
|
||||||
QString unsigned_tx = Utils::copyFromClipboard();
|
|
||||||
if (unsigned_tx.isEmpty()) {
|
|
||||||
Utils::showError(this, "Unable to load unsigned transaction", "Clipboard is empty");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
UnsignedTransaction *tx = m_wallet->loadTxFromBase64Str(unsigned_tx);
|
|
||||||
auto err = m_wallet->errorString();
|
|
||||||
if (!err.isEmpty()) {
|
|
||||||
Utils::showError(this, "Unable to load unsigned transaction", err);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this->createUnsignedTxDialog(tx);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::loadSignedTx() {
|
void MainWindow::loadSignedTx() {
|
||||||
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*signed_monero_tx);;All Files (*)");
|
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*signed_monero_tx);;All Files (*)");
|
||||||
if (fn.isEmpty()) return;
|
if (fn.isEmpty()) return;
|
||||||
|
@ -1301,12 +1233,6 @@ void MainWindow::loadSignedTxFromText() {
|
||||||
dialog.exec();
|
dialog.exec();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::createUnsignedTxDialog(UnsignedTransaction *tx) {
|
|
||||||
TxConfAdvDialog dialog{m_wallet, "", this};
|
|
||||||
dialog.setUnsignedTransaction(tx);
|
|
||||||
dialog.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
void MainWindow::importTransaction() {
|
void MainWindow::importTransaction() {
|
||||||
if (conf()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptNode) {
|
if (conf()->get(Config::torPrivacyLevel).toInt() == Config::allTorExceptNode) {
|
||||||
// TODO: don't show if connected to local node
|
// TODO: don't show if connected to local node
|
||||||
|
|
|
@ -137,12 +137,6 @@ private slots:
|
||||||
void onShowSettingsPage(int page);
|
void onShowSettingsPage(int page);
|
||||||
|
|
||||||
// offline tx signing
|
// offline tx signing
|
||||||
void exportKeyImages();
|
|
||||||
void importKeyImages();
|
|
||||||
void exportOutputs();
|
|
||||||
void importOutputs();
|
|
||||||
void loadUnsignedTx();
|
|
||||||
void loadUnsignedTxFromClipboard();
|
|
||||||
void loadSignedTx();
|
void loadSignedTx();
|
||||||
void loadSignedTxFromText();
|
void loadSignedTxFromText();
|
||||||
|
|
||||||
|
@ -166,6 +160,7 @@ private slots:
|
||||||
void showPasswordDialog();
|
void showPasswordDialog();
|
||||||
void showKeysDialog();
|
void showKeysDialog();
|
||||||
void showViewOnlyDialog();
|
void showViewOnlyDialog();
|
||||||
|
void showKeyImageSyncWizard();
|
||||||
void showWalletCacheDebugDialog();
|
void showWalletCacheDebugDialog();
|
||||||
void showAccountSwitcherDialog();
|
void showAccountSwitcherDialog();
|
||||||
void showAddressChecker();
|
void showAddressChecker();
|
||||||
|
@ -209,7 +204,6 @@ private:
|
||||||
void saveGeo();
|
void saveGeo();
|
||||||
void restoreGeo();
|
void restoreGeo();
|
||||||
void showDebugInfo();
|
void showDebugInfo();
|
||||||
void createUnsignedTxDialog(UnsignedTransaction *tx);
|
|
||||||
void updatePasswordIcon();
|
void updatePasswordIcon();
|
||||||
void updateNetStats();
|
void updateNetStats();
|
||||||
void rescanSpent();
|
void rescanSpent();
|
||||||
|
|
|
@ -479,7 +479,7 @@
|
||||||
<x>0</x>
|
<x>0</x>
|
||||||
<y>0</y>
|
<y>0</y>
|
||||||
<width>977</width>
|
<width>977</width>
|
||||||
<height>27</height>
|
<height>24</height>
|
||||||
</rect>
|
</rect>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuFile">
|
<widget class="QMenu" name="menuFile">
|
||||||
|
@ -521,28 +521,11 @@
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Advanced</string>
|
<string>Advanced</string>
|
||||||
</property>
|
</property>
|
||||||
<widget class="QMenu" name="menuExport">
|
|
||||||
<property name="title">
|
|
||||||
<string>Export</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionExportOutputs"/>
|
|
||||||
<addaction name="actionExportKeyImages"/>
|
|
||||||
</widget>
|
|
||||||
<widget class="QMenu" name="menuImport">
|
|
||||||
<property name="title">
|
|
||||||
<string>Import</string>
|
|
||||||
</property>
|
|
||||||
<addaction name="actionImportOutputs"/>
|
|
||||||
<addaction name="actionImportKeyImages"/>
|
|
||||||
</widget>
|
|
||||||
<addaction name="actionStore_wallet"/>
|
<addaction name="actionStore_wallet"/>
|
||||||
<addaction name="actionUpdate_balance"/>
|
<addaction name="actionUpdate_balance"/>
|
||||||
<addaction name="actionRefresh_tabs"/>
|
<addaction name="actionRefresh_tabs"/>
|
||||||
<addaction name="actionRescan_spent"/>
|
<addaction name="actionRescan_spent"/>
|
||||||
<addaction name="actionWallet_cache_debug"/>
|
<addaction name="actionWallet_cache_debug"/>
|
||||||
<addaction name="separator"/>
|
|
||||||
<addaction name="menuExport"/>
|
|
||||||
<addaction name="menuImport"/>
|
|
||||||
</widget>
|
</widget>
|
||||||
<addaction name="actionInformation"/>
|
<addaction name="actionInformation"/>
|
||||||
<addaction name="menuAdvanced"/>
|
<addaction name="menuAdvanced"/>
|
||||||
|
@ -560,13 +543,6 @@
|
||||||
<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">
|
<widget class="QMenu" name="menuLoad_signed_transaction">
|
||||||
<property name="title">
|
<property name="title">
|
||||||
<string>Broadcast transaction</string>
|
<string>Broadcast transaction</string>
|
||||||
|
@ -577,7 +553,7 @@
|
||||||
<addaction name="actionSignVerify"/>
|
<addaction name="actionSignVerify"/>
|
||||||
<addaction name="actionVerifyTxProof"/>
|
<addaction name="actionVerifyTxProof"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
<addaction name="menuLoad_transaction"/>
|
<addaction name="actionKeyImageSync"/>
|
||||||
<addaction name="menuLoad_signed_transaction"/>
|
<addaction name="menuLoad_signed_transaction"/>
|
||||||
<addaction name="actionImport_transaction"/>
|
<addaction name="actionImport_transaction"/>
|
||||||
<addaction name="separator"/>
|
<addaction name="separator"/>
|
||||||
|
@ -948,6 +924,11 @@
|
||||||
<string>Check for updates</string>
|
<string>Check for updates</string>
|
||||||
</property>
|
</property>
|
||||||
</action>
|
</action>
|
||||||
|
<action name="actionKeyImageSync">
|
||||||
|
<property name="text">
|
||||||
|
<string>Offline transaction signing</string>
|
||||||
|
</property>
|
||||||
|
</action>
|
||||||
</widget>
|
</widget>
|
||||||
<layoutdefault spacing="6" margin="11"/>
|
<layoutdefault spacing="6" margin="11"/>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
|
|
|
@ -13,6 +13,8 @@
|
||||||
#include "Icons.h"
|
#include "Icons.h"
|
||||||
#include "libwalletqt/WalletManager.h"
|
#include "libwalletqt/WalletManager.h"
|
||||||
|
|
||||||
|
#include "wizard/offline_tx_signing/OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
#if defined(WITH_SCANNER)
|
#if defined(WITH_SCANNER)
|
||||||
#include "qrcode/scanner/QrCodeScanDialog.h"
|
#include "qrcode/scanner/QrCodeScanDialog.h"
|
||||||
#include <QMediaDevices>
|
#include <QMediaDevices>
|
||||||
|
@ -132,9 +134,9 @@ void SendWidget::scanClicked() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto dialog = new QrCodeScanDialog(this);
|
auto dialog = new QrCodeScanDialog(this, false);
|
||||||
dialog->exec();
|
dialog->exec();
|
||||||
ui->lineAddress->setText(dialog->decodedString);
|
ui->lineAddress->setText(dialog->decodedString());
|
||||||
dialog->deleteLater();
|
dialog->deleteLater();
|
||||||
#else
|
#else
|
||||||
Utils::showError(this, "Can't open QR scanner", "Feather was built without webcam QR scanner support");
|
Utils::showError(this, "Can't open QR scanner", "Feather was built without webcam QR scanner support");
|
||||||
|
@ -222,9 +224,32 @@ void SendWidget::sendClicked() {
|
||||||
"Spendable balance: %1").arg(WalletManager::displayAmount(unlocked_balance)));
|
"Spendable balance: %1").arg(WalletManager::displayAmount(unlocked_balance)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (keyImageSync(sendAll, amount)) {
|
||||||
|
OfflineTxSigningWizard wizard(this, m_wallet);
|
||||||
|
auto r = wizard.exec();
|
||||||
|
|
||||||
|
if (r == QDialog::Rejected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
m_wallet->createTransaction(recipient, amount, description, sendAll);
|
m_wallet->createTransaction(recipient, amount, description, sendAll);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool SendWidget::keyImageSync(bool sendAll, quint64 amount) {
|
||||||
|
if (!m_wallet->viewOnly()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sendAll) {
|
||||||
|
return m_wallet->hasUnknownKeyImages();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 0.001 XMR to account for tx fee
|
||||||
|
return ((amount + WalletManager::amountFromDouble(0.001)) > m_wallet->viewOnlyBalance(m_wallet->currentSubaddressAccount()));
|
||||||
|
}
|
||||||
|
|
||||||
void SendWidget::aliasClicked() {
|
void SendWidget::aliasClicked() {
|
||||||
ui->btn_openAlias->setEnabled(false);
|
ui->btn_openAlias->setEnabled(false);
|
||||||
auto alias = ui->lineAddress->text();
|
auto alias = ui->lineAddress->text();
|
||||||
|
|
|
@ -50,6 +50,7 @@ private slots:
|
||||||
private:
|
private:
|
||||||
void setupComboBox();
|
void setupComboBox();
|
||||||
double amountDouble();
|
double amountDouble();
|
||||||
|
bool keyImageSync(bool sendAll, quint64 amount);
|
||||||
|
|
||||||
quint64 amount();
|
quint64 amount();
|
||||||
double conversionAmount();
|
double conversionAmount();
|
||||||
|
|
|
@ -21,17 +21,11 @@ TxConfAdvDialog::TxConfAdvDialog(Wallet *wallet, const QString &description, QWi
|
||||||
: WindowModalDialog(parent)
|
: WindowModalDialog(parent)
|
||||||
, ui(new Ui::TxConfAdvDialog)
|
, ui(new Ui::TxConfAdvDialog)
|
||||||
, m_wallet(wallet)
|
, m_wallet(wallet)
|
||||||
, m_exportUnsignedMenu(new QMenu(this))
|
|
||||||
, m_exportSignedMenu(new QMenu(this))
|
, m_exportSignedMenu(new QMenu(this))
|
||||||
, m_exportTxKeyMenu(new QMenu(this))
|
, m_exportTxKeyMenu(new QMenu(this))
|
||||||
{
|
{
|
||||||
ui->setupUi(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("Copy to clipboard", this, &TxConfAdvDialog::signedCopy);
|
||||||
m_exportSignedMenu->addAction("Save to file", this, &TxConfAdvDialog::signedSaveFile);
|
m_exportSignedMenu->addAction("Save to file", this, &TxConfAdvDialog::signedSaveFile);
|
||||||
ui->btn_exportSigned->setMenu(m_exportSignedMenu);
|
ui->btn_exportSigned->setMenu(m_exportSignedMenu);
|
||||||
|
@ -77,17 +71,6 @@ void TxConfAdvDialog::setTransaction(PendingTransaction *tx, bool isSigned) {
|
||||||
|
|
||||||
this->setAmounts(tx->amount(), tx->fee());
|
this->setAmounts(tx->amount(), tx->fee());
|
||||||
|
|
||||||
auto size_str = [this, isSigned]{
|
|
||||||
if (isSigned) {
|
|
||||||
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()));
|
|
||||||
} else {
|
|
||||||
|
|
||||||
return QString("Size: %1 bytes (unsigned)").arg(QString::number(m_tx->unsignedTxToBin().size()));
|
|
||||||
}
|
|
||||||
}();
|
|
||||||
ui->label_size->setText(size_str);
|
|
||||||
|
|
||||||
this->setupConstructionData(ptx);
|
this->setupConstructionData(ptx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,14 +78,12 @@ void TxConfAdvDialog::setUnsignedTransaction(UnsignedTransaction *utx) {
|
||||||
m_utx = utx;
|
m_utx = utx;
|
||||||
m_utx->refresh();
|
m_utx->refresh();
|
||||||
|
|
||||||
ui->btn_exportUnsigned->hide();
|
|
||||||
ui->btn_exportSigned->hide();
|
ui->btn_exportSigned->hide();
|
||||||
ui->btn_exportTxKey->hide();
|
ui->btn_exportTxKey->hide();
|
||||||
ui->btn_sign->show();
|
ui->btn_sign->show();
|
||||||
ui->btn_send->hide();
|
ui->btn_send->hide();
|
||||||
|
|
||||||
ui->txid->setText("n/a");
|
ui->txid->setText("n/a");
|
||||||
ui->label_size->setText("Size: n/a");
|
|
||||||
|
|
||||||
this->setAmounts(utx->amount(0), utx->fee(0));
|
this->setAmounts(utx->amount(0), utx->fee(0));
|
||||||
|
|
||||||
|
@ -157,40 +138,10 @@ void TxConfAdvDialog::setupConstructionData(ConstructionInfo *ci) {
|
||||||
cursor.insertBlock();
|
cursor.insertBlock();
|
||||||
}
|
}
|
||||||
ui->label_outputs->setText(QString("Outputs (%1)").arg(QString::number(outputs.size())));
|
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)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxConfAdvDialog::signTransaction() {
|
void TxConfAdvDialog::signTransaction() {
|
||||||
QString defaultName = QString("%1_signed_monero_tx").arg(QString::number(QDateTime::currentSecsSinceEpoch()));
|
this->accept();
|
||||||
QString fn = QFileDialog::getSaveFileName(this, "Save signed transaction to file", QDir::home().filePath(defaultName), "Transaction (*signed_monero_tx)");
|
|
||||||
if (fn.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = m_utx->sign(fn);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
Utils::showInfo(this, "Transaction saved successfully");
|
|
||||||
} else {
|
|
||||||
Utils::showError(this, "Failed 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool success = m_tx->saveToFile(fn);
|
|
||||||
|
|
||||||
if (success) {
|
|
||||||
Utils::showInfo(this, "Transaction saved successfully");
|
|
||||||
} else {
|
|
||||||
Utils::showError(this, "Failed to save transaction to file");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxConfAdvDialog::signedSaveFile() {
|
void TxConfAdvDialog::signedSaveFile() {
|
||||||
|
@ -209,21 +160,6 @@ void TxConfAdvDialog::signedSaveFile() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxConfAdvDialog::unsignedQrCode() {
|
|
||||||
if (m_tx->unsignedTxToBin().size() > 2953) {
|
|
||||||
Utils::showError(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);
|
|
||||||
QrCodeDialog dialog{this, &qr, "Unsigned Transaction"};
|
|
||||||
dialog.exec();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TxConfAdvDialog::unsignedCopy() {
|
|
||||||
Utils::copyToClipboard(m_tx->unsignedTxToBase64());
|
|
||||||
}
|
|
||||||
|
|
||||||
void TxConfAdvDialog::signedCopy() {
|
void TxConfAdvDialog::signedCopy() {
|
||||||
Utils::copyToClipboard(m_tx->signedTxToHex(0));
|
Utils::copyToClipboard(m_tx->signedTxToHex(0));
|
||||||
}
|
}
|
||||||
|
@ -237,9 +173,6 @@ void TxConfAdvDialog::txKeyCopy() {
|
||||||
Utils::copyToClipboard(m_tx->transaction(0)->txKey());
|
Utils::copyToClipboard(m_tx->transaction(0)->txKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
void TxConfAdvDialog::signedQrCode() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void TxConfAdvDialog::broadcastTransaction() {
|
void TxConfAdvDialog::broadcastTransaction() {
|
||||||
if (m_tx == nullptr) return;
|
if (m_tx == nullptr) return;
|
||||||
m_wallet->commitTransaction(m_tx, ui->line_description->text());
|
m_wallet->commitTransaction(m_tx, ui->line_description->text());
|
||||||
|
|
|
@ -35,12 +35,7 @@ private:
|
||||||
void closeDialog();
|
void closeDialog();
|
||||||
void setAmounts(quint64 amount, quint64 fee);
|
void setAmounts(quint64 amount, quint64 fee);
|
||||||
|
|
||||||
void unsignedCopy();
|
|
||||||
void unsignedQrCode();
|
|
||||||
void unsignedSaveFile();
|
|
||||||
|
|
||||||
void signedCopy();
|
void signedCopy();
|
||||||
void signedQrCode();
|
|
||||||
void signedSaveFile();
|
void signedSaveFile();
|
||||||
|
|
||||||
void txKeyCopy();
|
void txKeyCopy();
|
||||||
|
@ -49,7 +44,6 @@ private:
|
||||||
Wallet *m_wallet;
|
Wallet *m_wallet;
|
||||||
PendingTransaction *m_tx = nullptr;
|
PendingTransaction *m_tx = nullptr;
|
||||||
UnsignedTransaction *m_utx = nullptr;
|
UnsignedTransaction *m_utx = nullptr;
|
||||||
QMenu *m_exportUnsignedMenu;
|
|
||||||
QMenu *m_exportSignedMenu;
|
QMenu *m_exportSignedMenu;
|
||||||
QMenu *m_exportTxKeyMenu;
|
QMenu *m_exportTxKeyMenu;
|
||||||
QString m_txid;
|
QString m_txid;
|
||||||
|
|
|
@ -39,119 +39,76 @@
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
<item row="1" column="0">
|
||||||
</item>
|
<widget class="QLabel" name="label_description">
|
||||||
<item>
|
<property name="text">
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
<string>Description:</string>
|
||||||
<item>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout_3">
|
<property name="textInteractionFlags">
|
||||||
<item>
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_description">
|
|
||||||
<property name="text">
|
|
||||||
<string>Description:</string>
|
|
||||||
</property>
|
|
||||||
<property name="textInteractionFlags">
|
|
||||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLineEdit" name="line_description"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_size">
|
|
||||||
<property name="text">
|
|
||||||
<string>Size: </string>
|
|
||||||
</property>
|
|
||||||
<property name="textInteractionFlags">
|
|
||||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_ringSize">
|
|
||||||
<property name="text">
|
|
||||||
<string>Ringsize:</string>
|
|
||||||
</property>
|
|
||||||
<property name="textInteractionFlags">
|
|
||||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="Line" name="line">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Vertical</enum>
|
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item row="1" column="1">
|
||||||
<layout class="QFormLayout" name="formLayout">
|
<widget class="QLineEdit" name="line_description"/>
|
||||||
<item row="0" column="0">
|
</item>
|
||||||
<widget class="QLabel" name="label_amount">
|
<item row="2" column="0">
|
||||||
<property name="text">
|
<widget class="QLabel" name="label_amount">
|
||||||
<string>Amount: </string>
|
<property name="text">
|
||||||
</property>
|
<string>Amount: </string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item row="0" column="1">
|
</item>
|
||||||
<widget class="QLabel" name="amount">
|
<item row="2" column="1">
|
||||||
<property name="text">
|
<widget class="QLabel" name="amount">
|
||||||
<string>TextLabel</string>
|
<property name="text">
|
||||||
</property>
|
<string>TextLabel</string>
|
||||||
<property name="textInteractionFlags">
|
</property>
|
||||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
<property name="textInteractionFlags">
|
||||||
</property>
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item row="1" column="0">
|
</item>
|
||||||
<widget class="QLabel" name="label_fee">
|
<item row="3" column="0">
|
||||||
<property name="text">
|
<widget class="QLabel" name="label_fee">
|
||||||
<string>Fee: </string>
|
<property name="text">
|
||||||
</property>
|
<string>Fee: </string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item row="1" column="1">
|
</item>
|
||||||
<widget class="QLabel" name="fee">
|
<item row="3" column="1">
|
||||||
<property name="text">
|
<widget class="QLabel" name="fee">
|
||||||
<string>TextLabel</string>
|
<property name="text">
|
||||||
</property>
|
<string>TextLabel</string>
|
||||||
<property name="textInteractionFlags">
|
</property>
|
||||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
<property name="textInteractionFlags">
|
||||||
</property>
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item row="2" column="1">
|
</item>
|
||||||
<widget class="Line" name="line_2">
|
<item row="4" column="1">
|
||||||
<property name="orientation">
|
<widget class="Line" name="line_2">
|
||||||
<enum>Qt::Horizontal</enum>
|
<property name="orientation">
|
||||||
</property>
|
<enum>Qt::Horizontal</enum>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
<item row="3" column="0">
|
</item>
|
||||||
<widget class="QLabel" name="label_4">
|
<item row="5" column="1">
|
||||||
<property name="text">
|
<widget class="QLabel" name="total">
|
||||||
<string>Total:</string>
|
<property name="text">
|
||||||
</property>
|
<string>TextLabel</string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
<property name="textInteractionFlags">
|
||||||
<item row="3" column="1">
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
<widget class="QLabel" name="total">
|
</property>
|
||||||
<property name="text">
|
</widget>
|
||||||
<string>TextLabel</string>
|
</item>
|
||||||
</property>
|
<item row="5" column="0">
|
||||||
<property name="textInteractionFlags">
|
<widget class="QLabel" name="label_4">
|
||||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
<property name="text">
|
||||||
</property>
|
<string>Total:</string>
|
||||||
</widget>
|
</property>
|
||||||
</item>
|
</widget>
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</item>
|
</item>
|
||||||
|
@ -232,16 +189,6 @@
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
<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>
|
<item>
|
||||||
<widget class="QToolButton" name="btn_exportSigned">
|
<widget class="QToolButton" name="btn_exportSigned">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
|
@ -276,16 +223,16 @@
|
||||||
</spacer>
|
</spacer>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="btn_sign">
|
<widget class="QPushButton" name="btn_close">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Sign</string>
|
<string>Cancel</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
<item>
|
<item>
|
||||||
<widget class="QPushButton" name="btn_close">
|
<widget class="QPushButton" name="btn_sign">
|
||||||
<property name="text">
|
<property name="text">
|
||||||
<string>Cancel</string>
|
<string>Sign</string>
|
||||||
</property>
|
</property>
|
||||||
</widget>
|
</widget>
|
||||||
</item>
|
</item>
|
||||||
|
|
|
@ -69,8 +69,8 @@ QList<QVariant> PendingTransaction::subaddrIndices() const
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray PendingTransaction::unsignedTxToBin() const {
|
std::string PendingTransaction::unsignedTxToBin() const {
|
||||||
return QByteArray::fromStdString(m_pimpl->unsignedTxToBin());
|
return m_pimpl->unsignedTxToBin();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString PendingTransaction::unsignedTxToBase64() const
|
QString PendingTransaction::unsignedTxToBase64() const
|
||||||
|
|
|
@ -40,7 +40,7 @@ public:
|
||||||
QStringList txid() const;
|
QStringList txid() const;
|
||||||
quint64 txCount() const;
|
quint64 txCount() const;
|
||||||
QList<QVariant> subaddrIndices() const;
|
QList<QVariant> subaddrIndices() const;
|
||||||
QByteArray unsignedTxToBin() const;
|
std::string unsignedTxToBin() const;
|
||||||
QString unsignedTxToBase64() const;
|
QString unsignedTxToBase64() const;
|
||||||
QString signedTxToHex(int index) const;
|
QString signedTxToHex(int index) const;
|
||||||
void refresh();
|
void refresh();
|
||||||
|
|
|
@ -74,7 +74,12 @@ bool UnsignedTransaction::sign(const QString &fileName) const
|
||||||
if(!m_pimpl->sign(fileName.toStdString()))
|
if(!m_pimpl->sign(fileName.toStdString()))
|
||||||
return false;
|
return false;
|
||||||
// export key images
|
// export key images
|
||||||
return m_walletImpl->exportKeyImages(fileName.toStdString() + "_keyImages");
|
return true;
|
||||||
|
// return m_walletImpl->exportKeyImages(fileName.toStdString() + "_keyImages");
|
||||||
|
}
|
||||||
|
|
||||||
|
bool UnsignedTransaction::signToStr(std::string &data) const {
|
||||||
|
return m_pimpl->signToStr(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UnsignedTransaction::setFilename(const QString &fileName)
|
void UnsignedTransaction::setFilename(const QString &fileName)
|
||||||
|
|
|
@ -12,13 +12,6 @@
|
||||||
class UnsignedTransaction : public QObject
|
class UnsignedTransaction : public QObject
|
||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
Q_PROPERTY(Status status READ status)
|
|
||||||
Q_PROPERTY(QString errorString READ errorString)
|
|
||||||
Q_PROPERTY(quint64 txCount READ txCount)
|
|
||||||
Q_PROPERTY(QString confirmationMessage READ confirmationMessage)
|
|
||||||
Q_PROPERTY(QStringList recipientAddress READ recipientAddress)
|
|
||||||
Q_PROPERTY(QStringList paymentId READ paymentId)
|
|
||||||
Q_PROPERTY(quint64 minMixinCount READ minMixinCount)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum Status {
|
enum Status {
|
||||||
|
@ -30,16 +23,18 @@ public:
|
||||||
|
|
||||||
Status status() const;
|
Status status() const;
|
||||||
QString errorString() const;
|
QString errorString() const;
|
||||||
Q_INVOKABLE quint64 amount(size_t index) const;
|
quint64 amount(size_t index) const;
|
||||||
Q_INVOKABLE quint64 fee(size_t index) const;
|
quint64 fee(size_t index) const;
|
||||||
Q_INVOKABLE quint64 mixin(size_t index) const;
|
quint64 mixin(size_t index) const;
|
||||||
QStringList recipientAddress() const;
|
QStringList recipientAddress() const;
|
||||||
QStringList paymentId() const;
|
QStringList paymentId() const;
|
||||||
quint64 txCount() const;
|
quint64 txCount() const;
|
||||||
QString confirmationMessage() const;
|
QString confirmationMessage() const;
|
||||||
quint64 minMixinCount() const;
|
quint64 minMixinCount() const;
|
||||||
Q_INVOKABLE bool sign(const QString &fileName) const;
|
bool sign(const QString &fileName) const;
|
||||||
Q_INVOKABLE void setFilename(const QString &fileName);
|
bool signToStr(std::string &data) const;
|
||||||
|
|
||||||
|
void setFilename(const QString &fileName);
|
||||||
void refresh();
|
void refresh();
|
||||||
|
|
||||||
ConstructionInfo * constructionInfo(int index) const;
|
ConstructionInfo * constructionInfo(int index) const;
|
||||||
|
|
|
@ -125,6 +125,10 @@ bool Wallet::isDeterministic() const {
|
||||||
return m_walletImpl->isDeterministic();
|
return m_walletImpl->isDeterministic();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString Wallet::walletName() const {
|
||||||
|
return QFileInfo(this->cachePath()).fileName();
|
||||||
|
}
|
||||||
|
|
||||||
// #################### Balance ####################
|
// #################### Balance ####################
|
||||||
|
|
||||||
quint64 Wallet::balance() const {
|
quint64 Wallet::balance() const {
|
||||||
|
@ -151,6 +155,14 @@ quint64 Wallet::unlockedBalanceAll() const {
|
||||||
return m_walletImpl->unlockedBalanceAll();
|
return m_walletImpl->unlockedBalanceAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
quint64 Wallet::viewOnlyBalance(quint32 accountIndex) const {
|
||||||
|
std::vector<std::string> kis;
|
||||||
|
for (const auto & ki : m_selectedInputs) {
|
||||||
|
kis.push_back(ki);
|
||||||
|
}
|
||||||
|
return m_walletImpl->viewOnlyBalance(accountIndex, kis);
|
||||||
|
}
|
||||||
|
|
||||||
void Wallet::updateBalance() {
|
void Wallet::updateBalance() {
|
||||||
quint64 balance = this->balance();
|
quint64 balance = this->balance();
|
||||||
quint64 spendable = this->unlockedBalance();
|
quint64 spendable = this->unlockedBalance();
|
||||||
|
@ -499,22 +511,42 @@ void Wallet::onWalletPassphraseNeeded(bool on_device) {
|
||||||
|
|
||||||
// #################### Import / Export ####################
|
// #################### Import / Export ####################
|
||||||
|
|
||||||
|
bool Wallet::hasUnknownKeyImages() const {
|
||||||
|
return m_walletImpl->hasUnknownKeyImages();
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::exportKeyImages(const QString& path, bool all) {
|
bool Wallet::exportKeyImages(const QString& path, bool all) {
|
||||||
return m_walletImpl->exportKeyImages(path.toStdString(), all);
|
return m_walletImpl->exportKeyImages(path.toStdString(), all);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Wallet::exportKeyImagesToStr(std::string &keyImages, bool all) {
|
||||||
|
return m_walletImpl->exportKeyImagesToStr(keyImages, all);
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::importKeyImages(const QString& path) {
|
bool Wallet::importKeyImages(const QString& path) {
|
||||||
return m_walletImpl->importKeyImages(path.toStdString());
|
return m_walletImpl->importKeyImages(path.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Wallet::importKeyImagesFromStr(const std::string &keyImages) {
|
||||||
|
return m_walletImpl->importKeyImagesFromStr(keyImages);
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::exportOutputs(const QString& path, bool all) {
|
bool Wallet::exportOutputs(const QString& path, bool all) {
|
||||||
return m_walletImpl->exportOutputs(path.toStdString(), all);
|
return m_walletImpl->exportOutputs(path.toStdString(), all);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Wallet::exportOutputsToStr(std::string& outputs, bool all) {
|
||||||
|
return m_walletImpl->exportOutputsToStr(outputs, all);
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::importOutputs(const QString& path) {
|
bool Wallet::importOutputs(const QString& path) {
|
||||||
return m_walletImpl->importOutputs(path.toStdString());
|
return m_walletImpl->importOutputs(path.toStdString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Wallet::importOutputsFromStr(const std::string &outputs) {
|
||||||
|
return m_walletImpl->importOutputsFromStr(outputs);
|
||||||
|
}
|
||||||
|
|
||||||
bool Wallet::importTransaction(const QString& txid) {
|
bool Wallet::importTransaction(const QString& txid) {
|
||||||
std::vector<std::string> txids = {txid.toStdString()};
|
std::vector<std::string> txids = {txid.toStdString()};
|
||||||
return m_walletImpl->scanTransactions(txids);
|
return m_walletImpl->scanTransactions(txids);
|
||||||
|
@ -822,6 +854,12 @@ UnsignedTransaction * Wallet::loadTxFile(const QString &fileName)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UnsignedTransaction * Wallet::loadUnsignedTransactionFromStr(const std::string &data) {
|
||||||
|
Monero::UnsignedTransaction *ptImpl = m_walletImpl->loadUnsignedTxFromStr(data);
|
||||||
|
UnsignedTransaction *result = new UnsignedTransaction(ptImpl, m_walletImpl, this);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
UnsignedTransaction * Wallet::loadTxFromBase64Str(const QString &unsigned_tx)
|
UnsignedTransaction * Wallet::loadTxFromBase64Str(const QString &unsigned_tx)
|
||||||
{
|
{
|
||||||
Monero::UnsignedTransaction *ptImpl = m_walletImpl->loadUnsignedTxFromBase64Str(unsigned_tx.toStdString());
|
Monero::UnsignedTransaction *ptImpl = m_walletImpl->loadUnsignedTxFromBase64Str(unsigned_tx.toStdString());
|
||||||
|
@ -837,6 +875,13 @@ PendingTransaction * Wallet::loadSignedTxFile(const QString &fileName)
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PendingTransaction * Wallet::loadSignedTxFromStr(const std::string &data)
|
||||||
|
{
|
||||||
|
Monero::PendingTransaction *ptImpl = m_walletImpl->loadSignedTxFromStr(data);
|
||||||
|
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;
|
||||||
|
|
|
@ -133,6 +133,8 @@ public:
|
||||||
//! return true if deterministic keys
|
//! return true if deterministic keys
|
||||||
bool isDeterministic() const;
|
bool isDeterministic() const;
|
||||||
|
|
||||||
|
QString walletName() const;
|
||||||
|
|
||||||
// ##### Balance #####
|
// ##### Balance #####
|
||||||
//! returns balance
|
//! returns balance
|
||||||
quint64 balance() const;
|
quint64 balance() const;
|
||||||
|
@ -144,6 +146,8 @@ public:
|
||||||
quint64 unlockedBalance(quint32 accountIndex) const;
|
quint64 unlockedBalance(quint32 accountIndex) const;
|
||||||
quint64 unlockedBalanceAll() const;
|
quint64 unlockedBalanceAll() const;
|
||||||
|
|
||||||
|
quint64 viewOnlyBalance(quint32 accountIndex) const;
|
||||||
|
|
||||||
void updateBalance();
|
void updateBalance();
|
||||||
|
|
||||||
// ##### Subaddresses and Accounts #####
|
// ##### Subaddresses and Accounts #####
|
||||||
|
@ -235,13 +239,21 @@ public:
|
||||||
void onWalletPassphraseNeeded(bool on_device) override;
|
void onWalletPassphraseNeeded(bool on_device) override;
|
||||||
|
|
||||||
// ##### Import / Export #####
|
// ##### Import / Export #####
|
||||||
|
bool hasUnknownKeyImages() const;
|
||||||
|
|
||||||
//! export/import key images
|
//! export/import key images
|
||||||
bool exportKeyImages(const QString& path, bool all = false);
|
bool exportKeyImages(const QString& path, bool all = false);
|
||||||
|
bool exportKeyImagesToStr(std::string &keyImages, bool all = false);
|
||||||
|
|
||||||
bool importKeyImages(const QString& path);
|
bool importKeyImages(const QString& path);
|
||||||
|
bool importKeyImagesFromStr(const std::string &keyImages);
|
||||||
|
|
||||||
//! export/import outputs
|
//! export/import outputs
|
||||||
bool exportOutputs(const QString& path, bool all = false);
|
bool exportOutputs(const QString& path, bool all = false);
|
||||||
|
bool exportOutputsToStr(std::string& outputs, bool all);
|
||||||
|
|
||||||
bool importOutputs(const QString& path);
|
bool importOutputs(const QString& path);
|
||||||
|
bool importOutputsFromStr(const std::string &outputs);
|
||||||
|
|
||||||
//! import a transaction
|
//! import a transaction
|
||||||
bool importTransaction(const QString& txid);
|
bool importTransaction(const QString& txid);
|
||||||
|
@ -315,12 +327,14 @@ public:
|
||||||
|
|
||||||
//! Sign a transfer from file
|
//! Sign a transfer from file
|
||||||
UnsignedTransaction * loadTxFile(const QString &fileName);
|
UnsignedTransaction * loadTxFile(const QString &fileName);
|
||||||
|
UnsignedTransaction * loadUnsignedTransactionFromStr(const std::string &data);
|
||||||
|
|
||||||
//! Load an unsigned transaction from a base64 encoded string
|
//! Load an unsigned transaction from a base64 encoded string
|
||||||
UnsignedTransaction * loadTxFromBase64Str(const QString &unsigned_tx);
|
UnsignedTransaction * loadTxFromBase64Str(const QString &unsigned_tx);
|
||||||
|
|
||||||
//! Load a signed transaction from file
|
//! Load a signed transaction from file
|
||||||
PendingTransaction * loadSignedTxFile(const QString &fileName);
|
PendingTransaction * loadSignedTxFile(const QString &fileName);
|
||||||
|
PendingTransaction * loadSignedTxFromStr(const std::string &data);
|
||||||
|
|
||||||
//! Submit a transfer from file
|
//! Submit a transfer from file
|
||||||
bool submitTxFile(const QString &fileName) const;
|
bool submitTxFile(const QString &fileName) const;
|
||||||
|
|
|
@ -13,92 +13,21 @@
|
||||||
|
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
|
|
||||||
QrCodeScanDialog::QrCodeScanDialog(QWidget *parent)
|
QrCodeScanDialog::QrCodeScanDialog(QWidget *parent, bool scan_ur)
|
||||||
: QDialog(parent)
|
: QDialog(parent)
|
||||||
, ui(new Ui::QrCodeScanDialog)
|
, ui(new Ui::QrCodeScanDialog)
|
||||||
, m_sink(new QVideoSink(this))
|
|
||||||
{
|
{
|
||||||
ui->setupUi(this);
|
ui->setupUi(this);
|
||||||
this->setWindowTitle("Scan QR code");
|
this->setWindowTitle("Scan QR code");
|
||||||
|
|
||||||
QPixmap pixmap = QPixmap(":/assets/images/warning.png");
|
ui->widget_scanner->startCapture(scan_ur);
|
||||||
ui->icon_warning->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
|
|
||||||
|
|
||||||
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
|
||||||
for (const auto &camera : cameras) {
|
|
||||||
ui->combo_camera->addItem(camera.description());
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &QrCodeScanDialog::onCameraSwitched);
|
|
||||||
|
|
||||||
connect(ui->viewfinder->videoSink(), &QVideoSink::videoFrameChanged, this, &QrCodeScanDialog::handleFrameCaptured);
|
|
||||||
|
|
||||||
this->onCameraSwitched(0);
|
|
||||||
|
|
||||||
m_thread = new QrScanThread(this);
|
|
||||||
m_thread->start();
|
|
||||||
|
|
||||||
connect(m_thread, &QrScanThread::decoded, this, &QrCodeScanDialog::onDecoded);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void QrCodeScanDialog::handleFrameCaptured(const QVideoFrame &frame) {
|
QString QrCodeScanDialog::decodedString() {
|
||||||
QImage img = this->videoFrameToImage(frame);
|
return ui->widget_scanner->decodedString;
|
||||||
m_thread->addImage(img);
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage QrCodeScanDialog::videoFrameToImage(const QVideoFrame &videoFrame)
|
|
||||||
{
|
|
||||||
auto handleType = videoFrame.handleType();
|
|
||||||
|
|
||||||
if (handleType == QVideoFrame::NoHandle) {
|
|
||||||
|
|
||||||
QImage image = videoFrame.toImage();
|
|
||||||
|
|
||||||
if (image.isNull()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.format() != QImage::Format_ARGB32) {
|
|
||||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
|
||||||
}
|
|
||||||
|
|
||||||
return image.copy();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
void QrCodeScanDialog::onCameraSwitched(int index) {
|
|
||||||
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
|
||||||
|
|
||||||
if (index >= cameras.size()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
m_camera.reset(new QCamera(cameras.at(index)));
|
|
||||||
m_captureSession.setCamera(m_camera.data());
|
|
||||||
m_captureSession.setVideoOutput(ui->viewfinder);
|
|
||||||
|
|
||||||
connect(m_camera.data(), &QCamera::activeChanged, [this](bool active){
|
|
||||||
ui->frame_unavailable->setVisible(!active);
|
|
||||||
});
|
|
||||||
|
|
||||||
m_camera->start();
|
|
||||||
}
|
|
||||||
|
|
||||||
void QrCodeScanDialog::onDecoded(const QString &data) {
|
|
||||||
decodedString = data;
|
|
||||||
this->accept();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QrCodeScanDialog::~QrCodeScanDialog()
|
QrCodeScanDialog::~QrCodeScanDialog()
|
||||||
{
|
{
|
||||||
m_thread->stop();
|
|
||||||
m_thread->quit();
|
|
||||||
if (!m_thread->wait(5000))
|
|
||||||
{
|
|
||||||
m_thread->terminate();
|
|
||||||
m_thread->wait();
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -13,6 +13,8 @@
|
||||||
|
|
||||||
#include "QrScanThread.h"
|
#include "QrScanThread.h"
|
||||||
|
|
||||||
|
#include <bcur/ur-decoder.hpp>
|
||||||
|
|
||||||
namespace Ui {
|
namespace Ui {
|
||||||
class QrCodeScanDialog;
|
class QrCodeScanDialog;
|
||||||
}
|
}
|
||||||
|
@ -22,25 +24,13 @@ class QrCodeScanDialog : public QDialog
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit QrCodeScanDialog(QWidget *parent);
|
explicit QrCodeScanDialog(QWidget *parent, bool scan_ur = false);
|
||||||
~QrCodeScanDialog() override;
|
~QrCodeScanDialog() override;
|
||||||
|
|
||||||
QString decodedString = "";
|
QString decodedString();
|
||||||
|
|
||||||
private slots:
|
|
||||||
void onCameraSwitched(int index);
|
|
||||||
void onDecoded(const QString &data);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QImage videoFrameToImage(const QVideoFrame &videoFrame);
|
|
||||||
void handleFrameCaptured(const QVideoFrame &videoFrame);
|
|
||||||
|
|
||||||
QScopedPointer<Ui::QrCodeScanDialog> ui;
|
QScopedPointer<Ui::QrCodeScanDialog> ui;
|
||||||
|
|
||||||
QrScanThread *m_thread;
|
|
||||||
QScopedPointer<QCamera> m_camera;
|
|
||||||
QMediaCaptureSession m_captureSession;
|
|
||||||
QVideoSink m_sink;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -15,93 +15,15 @@
|
||||||
</property>
|
</property>
|
||||||
<layout class="QVBoxLayout" name="verticalLayout">
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
<item>
|
<item>
|
||||||
<widget class="QVideoWidget" name="viewfinder" native="true">
|
<widget class="QrCodeScanWidget" name="widget_scanner" native="true"/>
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QFrame" name="frame_unavailable">
|
|
||||||
<property name="frameShape">
|
|
||||||
<enum>QFrame::StyledPanel</enum>
|
|
||||||
</property>
|
|
||||||
<property name="frameShadow">
|
|
||||||
<enum>QFrame::Raised</enum>
|
|
||||||
</property>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_3">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="icon_warning">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>icon</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<spacer name="horizontalSpacer">
|
|
||||||
<property name="orientation">
|
|
||||||
<enum>Qt::Horizontal</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeType">
|
|
||||||
<enum>QSizePolicy::Preferred</enum>
|
|
||||||
</property>
|
|
||||||
<property name="sizeHint" stdset="0">
|
|
||||||
<size>
|
|
||||||
<width>55</width>
|
|
||||||
<height>0</height>
|
|
||||||
</size>
|
|
||||||
</property>
|
|
||||||
</spacer>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_2">
|
|
||||||
<property name="text">
|
|
||||||
<string>Lost connection to camera. Please restart scan dialog.</string>
|
|
||||||
</property>
|
|
||||||
<property name="wordWrap">
|
|
||||||
<bool>true</bool>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
|
||||||
<item>
|
|
||||||
<widget class="QLabel" name="label_3">
|
|
||||||
<property name="sizePolicy">
|
|
||||||
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
|
||||||
<horstretch>0</horstretch>
|
|
||||||
<verstretch>0</verstretch>
|
|
||||||
</sizepolicy>
|
|
||||||
</property>
|
|
||||||
<property name="text">
|
|
||||||
<string>Camera:</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<widget class="QComboBox" name="combo_camera"/>
|
|
||||||
</item>
|
|
||||||
</layout>
|
|
||||||
</item>
|
</item>
|
||||||
</layout>
|
</layout>
|
||||||
</widget>
|
</widget>
|
||||||
<customwidgets>
|
<customwidgets>
|
||||||
<customwidget>
|
<customwidget>
|
||||||
<class>QVideoWidget</class>
|
<class>QrCodeScanWidget</class>
|
||||||
<extends>QWidget</extends>
|
<extends>QWidget</extends>
|
||||||
<header>qvideowidget.h</header>
|
<header>qrcode/scanner/QrCodeScanWidget.h</header>
|
||||||
<container>1</container>
|
<container>1</container>
|
||||||
</customwidget>
|
</customwidget>
|
||||||
</customwidgets>
|
</customwidgets>
|
||||||
|
|
190
src/qrcode/scanner/QrCodeScanWidget.cpp
Normal file
190
src/qrcode/scanner/QrCodeScanWidget.cpp
Normal file
|
@ -0,0 +1,190 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "QrCodeScanWidget.h"
|
||||||
|
#include "ui_QrCodeScanWidget.h"
|
||||||
|
|
||||||
|
#include <QMediaDevices>
|
||||||
|
#include <QComboBox>
|
||||||
|
|
||||||
|
#include "utils/Icons.h"
|
||||||
|
#include <bcur/bc-ur.hpp>
|
||||||
|
|
||||||
|
QrCodeScanWidget::QrCodeScanWidget(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, ui(new Ui::QrCodeScanWidget)
|
||||||
|
, m_sink(new QVideoSink(this))
|
||||||
|
, m_thread(new QrScanThread(this))
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
this->setWindowTitle("Scan QR code");
|
||||||
|
|
||||||
|
ui->frame_error->hide();
|
||||||
|
ui->frame_error->setInfo(icons()->icon("warning.png"), "Lost connection to camera");
|
||||||
|
|
||||||
|
this->refreshCameraList();
|
||||||
|
|
||||||
|
connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &QrCodeScanWidget::onCameraSwitched);
|
||||||
|
connect(ui->viewfinder->videoSink(), &QVideoSink::videoFrameChanged, this, &QrCodeScanWidget::handleFrameCaptured);
|
||||||
|
connect(ui->btn_refresh, &QPushButton::clicked, [this]{
|
||||||
|
this->refreshCameraList();
|
||||||
|
this->onCameraSwitched(0);
|
||||||
|
});
|
||||||
|
connect(m_thread, &QrScanThread::decoded, this, &QrCodeScanWidget::onDecoded);
|
||||||
|
}
|
||||||
|
|
||||||
|
void QrCodeScanWidget::startCapture(bool scan_ur) {
|
||||||
|
m_scan_ur = scan_ur;
|
||||||
|
ui->progressBar_UR->setVisible(m_scan_ur);
|
||||||
|
ui->progressBar_UR->setFormat("%v / ?");
|
||||||
|
|
||||||
|
if (ui->combo_camera->count() < 1) {
|
||||||
|
ui->frame_error->setText("No cameras found.");
|
||||||
|
ui->frame_error->show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this->onCameraSwitched(0);
|
||||||
|
|
||||||
|
if (!m_thread->isRunning()) {
|
||||||
|
m_thread->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QrCodeScanWidget::reset() {
|
||||||
|
this->decodedString = "";
|
||||||
|
m_done = false;
|
||||||
|
ui->progressBar_UR->setValue(0);
|
||||||
|
ui->progressBar_UR->setFormat("%v / ?");
|
||||||
|
m_decoder = ur::URDecoder();
|
||||||
|
m_thread->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QrCodeScanWidget::stop() {
|
||||||
|
m_camera->stop();
|
||||||
|
m_thread->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QrCodeScanWidget::refreshCameraList() {
|
||||||
|
ui->combo_camera->clear();
|
||||||
|
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
||||||
|
for (const auto &camera : cameras) {
|
||||||
|
ui->combo_camera->addItem(camera.description());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void QrCodeScanWidget::handleFrameCaptured(const QVideoFrame &frame) {
|
||||||
|
QImage img = this->videoFrameToImage(frame);
|
||||||
|
if (img.format() == QImage::Format_ARGB32) {
|
||||||
|
m_thread->addImage(img);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage QrCodeScanWidget::videoFrameToImage(const QVideoFrame &videoFrame)
|
||||||
|
{
|
||||||
|
QImage image = videoFrame.toImage();
|
||||||
|
|
||||||
|
if (image.isNull()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (image.format() != QImage::Format_ARGB32) {
|
||||||
|
image = image.convertToFormat(QImage::Format_ARGB32);
|
||||||
|
}
|
||||||
|
|
||||||
|
return image.copy();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void QrCodeScanWidget::onCameraSwitched(int index) {
|
||||||
|
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
|
||||||
|
|
||||||
|
if (index < 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index >= cameras.size()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_camera) {
|
||||||
|
m_camera->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
m_camera.reset(new QCamera(cameras.at(index), this));
|
||||||
|
m_captureSession.setCamera(m_camera.data());
|
||||||
|
m_captureSession.setVideoOutput(ui->viewfinder);
|
||||||
|
|
||||||
|
connect(m_camera.data(), &QCamera::activeChanged, [this](bool active){
|
||||||
|
ui->frame_error->setText("Lost connection to camera");
|
||||||
|
ui->frame_error->setVisible(!active);
|
||||||
|
});
|
||||||
|
|
||||||
|
m_camera->start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void QrCodeScanWidget::onDecoded(const QString &data) {
|
||||||
|
if (m_done) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_scan_ur) {
|
||||||
|
m_decoder.receive_part(data.toStdString());
|
||||||
|
ui->progressBar_UR->setFormat("%v / %m");
|
||||||
|
|
||||||
|
size_t processed = m_decoder.processed_parts_count();
|
||||||
|
size_t expected = m_decoder.expected_part_count();
|
||||||
|
|
||||||
|
if (processed > expected && !m_decoder.is_complete()) {
|
||||||
|
processed = expected - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->progressBar_UR->setValue(processed);
|
||||||
|
ui->progressBar_UR->setMaximum(expected);
|
||||||
|
|
||||||
|
if (m_decoder.is_complete()) {
|
||||||
|
m_done = true;
|
||||||
|
m_thread->stop();
|
||||||
|
emit finished(m_decoder.is_success());
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
decodedString = data;
|
||||||
|
m_done = true;
|
||||||
|
m_thread->stop();
|
||||||
|
emit finished(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string QrCodeScanWidget::getURData() {
|
||||||
|
if (!m_decoder.is_success()) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
ur::ByteVector cbor = m_decoder.result_ur().cbor();
|
||||||
|
std::string data;
|
||||||
|
auto i = cbor.begin();
|
||||||
|
auto end = cbor.end();
|
||||||
|
ur::CborLite::decodeBytes(i, end, data);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString QrCodeScanWidget::getURError() {
|
||||||
|
if (!m_decoder.is_failure()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return QString::fromStdString(m_decoder.result_error().what());
|
||||||
|
}
|
||||||
|
|
||||||
|
QrCodeScanWidget::~QrCodeScanWidget()
|
||||||
|
{
|
||||||
|
m_thread->stop();
|
||||||
|
m_thread->quit();
|
||||||
|
if (!m_thread->wait(5000))
|
||||||
|
{
|
||||||
|
m_thread->terminate();
|
||||||
|
m_thread->wait();
|
||||||
|
}
|
||||||
|
}
|
61
src/qrcode/scanner/QrCodeScanWidget.h
Normal file
61
src/qrcode/scanner/QrCodeScanWidget.h
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_QRCODESCANWIDGET_H
|
||||||
|
#define FEATHER_QRCODESCANWIDGET_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QCamera>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QMediaCaptureSession>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QVideoSink>
|
||||||
|
|
||||||
|
#include "QrScanThread.h"
|
||||||
|
|
||||||
|
#include <bcur/ur-decoder.hpp>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class QrCodeScanWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class QrCodeScanWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit QrCodeScanWidget(QWidget *parent);
|
||||||
|
~QrCodeScanWidget() override;
|
||||||
|
|
||||||
|
QString decodedString = "";
|
||||||
|
std::string getURData();
|
||||||
|
QString getURError();
|
||||||
|
|
||||||
|
void startCapture(bool scan_ur = false);
|
||||||
|
void reset();
|
||||||
|
void stop();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished(bool success);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onCameraSwitched(int index);
|
||||||
|
void onDecoded(const QString &data);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void refreshCameraList();
|
||||||
|
QImage videoFrameToImage(const QVideoFrame &videoFrame);
|
||||||
|
void handleFrameCaptured(const QVideoFrame &videoFrame);
|
||||||
|
|
||||||
|
QScopedPointer<Ui::QrCodeScanWidget> ui;
|
||||||
|
|
||||||
|
bool m_scan_ur = false;
|
||||||
|
QrScanThread *m_thread;
|
||||||
|
QScopedPointer<QCamera> m_camera;
|
||||||
|
QMediaCaptureSession m_captureSession;
|
||||||
|
QVideoSink m_sink;
|
||||||
|
ur::URDecoder m_decoder;
|
||||||
|
bool m_done = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_QRCODESCANWIDGET_H
|
108
src/qrcode/scanner/QrCodeScanWidget.ui
Normal file
108
src/qrcode/scanner/QrCodeScanWidget.ui
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>QrCodeScanWidget</class>
|
||||||
|
<widget class="QWidget" name="QrCodeScanWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>526</width>
|
||||||
|
<height>429</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QVideoWidget" name="viewfinder" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="InfoFrame" name="frame_error">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QProgressBar" name="progressBar_UR">
|
||||||
|
<property name="maximum">
|
||||||
|
<number>1</number>
|
||||||
|
</property>
|
||||||
|
<property name="value">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<property name="format">
|
||||||
|
<string>%v / %m</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>Camera:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QComboBox" name="combo_camera">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btn_refresh">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Maximum" vsizetype="Fixed">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>⟳</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>QVideoWidget</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>qvideowidget.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
<customwidget>
|
||||||
|
<class>InfoFrame</class>
|
||||||
|
<extends>QFrame</extends>
|
||||||
|
<header>components.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -32,6 +32,14 @@ void QrScanThread::stop()
|
||||||
m_waitCondition.wakeOne();
|
m_waitCondition.wakeOne();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void QrScanThread::start()
|
||||||
|
{
|
||||||
|
m_queue.clear();
|
||||||
|
m_running = true;
|
||||||
|
m_waitCondition.wakeOne();
|
||||||
|
QThread::start();
|
||||||
|
}
|
||||||
|
|
||||||
void QrScanThread::addImage(const QImage &img)
|
void QrScanThread::addImage(const QImage &img)
|
||||||
{
|
{
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
|
|
|
@ -21,7 +21,9 @@ class QrScanThread : public QThread
|
||||||
public:
|
public:
|
||||||
explicit QrScanThread(QObject *parent = nullptr);
|
explicit QrScanThread(QObject *parent = nullptr);
|
||||||
void addImage(const QImage &img);
|
void addImage(const QImage &img);
|
||||||
|
|
||||||
virtual void stop();
|
virtual void stop();
|
||||||
|
virtual void start();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void decoded(const QString &data);
|
void decoded(const QString &data);
|
||||||
|
|
|
@ -29,6 +29,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||||
{Config::geometry, {QS("geometry"), {}}},
|
{Config::geometry, {QS("geometry"), {}}},
|
||||||
{Config::windowState, {QS("windowState"), {}}},
|
{Config::windowState, {QS("windowState"), {}}},
|
||||||
{Config::GUI_HistoryViewState, {QS("GUI_HistoryViewState"), {}}},
|
{Config::GUI_HistoryViewState, {QS("GUI_HistoryViewState"), {}}},
|
||||||
|
{Config::geometryOTSWizard, {QS("geometryOTSWizard"), {}}},
|
||||||
|
|
||||||
// Wallets
|
// Wallets
|
||||||
{Config::walletDirectory,{QS("walletDirectory"), ""}},
|
{Config::walletDirectory,{QS("walletDirectory"), ""}},
|
||||||
|
@ -74,6 +75,7 @@ static const QHash<Config::ConfigKey, ConfigDirective> configStrings = {
|
||||||
{Config::offlineMode, {QS("offlineMode"), false}},
|
{Config::offlineMode, {QS("offlineMode"), false}},
|
||||||
|
|
||||||
{Config::multiBroadcast, {QS("multiBroadcast"), true}},
|
{Config::multiBroadcast, {QS("multiBroadcast"), true}},
|
||||||
|
{Config::offlineTxSigningMethod, {QS("offlineTxSigningMethod"), Config::OTSMethod::UnifiedResources}},
|
||||||
{Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}},
|
{Config::warnOnExternalLink,{QS("warnOnExternalLink"), true}},
|
||||||
{Config::hideBalance, {QS("hideBalance"), false}},
|
{Config::hideBalance, {QS("hideBalance"), false}},
|
||||||
{Config::hideNotifications, {QS("hideNotifications"), false}},
|
{Config::hideNotifications, {QS("hideNotifications"), false}},
|
||||||
|
|
|
@ -32,6 +32,7 @@ public:
|
||||||
geometry,
|
geometry,
|
||||||
windowState,
|
windowState,
|
||||||
GUI_HistoryViewState,
|
GUI_HistoryViewState,
|
||||||
|
geometryOTSWizard,
|
||||||
|
|
||||||
// Wallets
|
// Wallets
|
||||||
walletDirectory, // Directory where wallet files are stored
|
walletDirectory, // Directory where wallet files are stored
|
||||||
|
@ -110,6 +111,7 @@ public:
|
||||||
|
|
||||||
// Transactions
|
// Transactions
|
||||||
multiBroadcast,
|
multiBroadcast,
|
||||||
|
offlineTxSigningMethod,
|
||||||
|
|
||||||
// Misc
|
// Misc
|
||||||
blockExplorer,
|
blockExplorer,
|
||||||
|
@ -145,6 +147,11 @@ public:
|
||||||
socks5
|
socks5
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum OTSMethod {
|
||||||
|
UnifiedResources = 0,
|
||||||
|
FileTransfer
|
||||||
|
};
|
||||||
|
|
||||||
~Config() override;
|
~Config() override;
|
||||||
QVariant get(ConfigKey key);
|
QVariant get(ConfigKey key);
|
||||||
QString getFileName();
|
QString getFileName();
|
||||||
|
|
|
@ -12,6 +12,10 @@ QrCodeWidget::QrCodeWidget(QWidget *parent) : QWidget(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
void QrCodeWidget::setQrCode(QrCode *qrCode) {
|
void QrCodeWidget::setQrCode(QrCode *qrCode) {
|
||||||
|
if (m_qrcode) {
|
||||||
|
delete m_qrcode;
|
||||||
|
}
|
||||||
|
|
||||||
m_qrcode = qrCode;
|
m_qrcode = qrCode;
|
||||||
|
|
||||||
int k = m_qrcode->width();
|
int k = m_qrcode->width();
|
||||||
|
|
77
src/widgets/TxDetailsSimple.cpp
Normal file
77
src/widgets/TxDetailsSimple.cpp
Normal file
|
@ -0,0 +1,77 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "TxDetailsSimple.h"
|
||||||
|
#include "ui_TxDetailsSimple.h"
|
||||||
|
|
||||||
|
#include "constants.h"
|
||||||
|
#include "libwalletqt/WalletManager.h"
|
||||||
|
#include "utils/AppData.h"
|
||||||
|
#include "utils/ColorScheme.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
TxDetailsSimple::TxDetailsSimple(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, ui(new Ui::TxDetailsSimple)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
ui->label_amount->setFont(Utils::getMonospaceFont());
|
||||||
|
ui->label_fee->setFont(Utils::getMonospaceFont());
|
||||||
|
ui->label_total->setFont(Utils::getMonospaceFont());
|
||||||
|
}
|
||||||
|
|
||||||
|
void TxDetailsSimple::setDetails(Wallet *wallet, PendingTransaction *tx, const QString &address) {
|
||||||
|
ui->label_note->hide();
|
||||||
|
|
||||||
|
QString preferredCur = conf()->get(Config::preferredFiatCurrency).toString();
|
||||||
|
|
||||||
|
auto convert = [preferredCur](double amount){
|
||||||
|
return QString::number(appData()->prices.convert("XMR", preferredCur, amount), 'f', 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
QString amount = WalletManager::displayAmount(tx->amount());
|
||||||
|
QString fee = WalletManager::displayAmount(tx->fee());
|
||||||
|
QString total = WalletManager::displayAmount(tx->amount() + tx->fee());
|
||||||
|
QVector<QString> amounts = {amount, fee, total};
|
||||||
|
int maxLength = Utils::maxLength(amounts);
|
||||||
|
std::for_each(amounts.begin(), amounts.end(), [maxLength](QString& amount){amount = amount.rightJustified(maxLength, ' ');});
|
||||||
|
|
||||||
|
QString amount_fiat = convert(tx->amount() / constants::cdiv);
|
||||||
|
QString fee_fiat = convert(tx->fee() / constants::cdiv);
|
||||||
|
QString total_fiat = convert((tx->amount() + tx->fee()) / constants::cdiv);
|
||||||
|
QVector<QString> amounts_fiat = {amount_fiat, fee_fiat, total_fiat};
|
||||||
|
int maxLengthFiat = Utils::maxLength(amounts_fiat);
|
||||||
|
std::for_each(amounts_fiat.begin(), amounts_fiat.end(), [maxLengthFiat](QString& amount){amount = amount.rightJustified(maxLengthFiat, ' ');});
|
||||||
|
|
||||||
|
ui->label_amount->setText(QString("%1 (%2 %3)").arg(amounts[0], amounts_fiat[0], preferredCur));
|
||||||
|
ui->label_fee->setText(QString("%1 (%2 %3)").arg(amounts[1], amounts_fiat[1], preferredCur));
|
||||||
|
ui->label_total->setText(QString("%1 (%2 %3)").arg(amounts[2], amounts_fiat[2], preferredCur));
|
||||||
|
|
||||||
|
auto subaddressIndex = wallet->subaddressIndex(address);
|
||||||
|
QString addressExtra;
|
||||||
|
|
||||||
|
ui->label_address->setText(Utils::displayAddress(address, 2));
|
||||||
|
ui->label_address->setFont(Utils::getMonospaceFont());
|
||||||
|
ui->label_address->setToolTip(address);
|
||||||
|
|
||||||
|
if (subaddressIndex.isValid()) {
|
||||||
|
ui->label_note->setText("Note: this is a churn transaction.");
|
||||||
|
ui->label_note->show();
|
||||||
|
ui->label_address->setStyleSheet(ColorScheme::GREEN.asStylesheet(true));
|
||||||
|
ui->label_address->setToolTip("Wallet receive address");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subaddressIndex.isPrimary()) {
|
||||||
|
ui->label_address->setStyleSheet(ColorScheme::YELLOW.asStylesheet(true));
|
||||||
|
ui->label_address->setToolTip("Wallet change/primary address");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx->fee() > WalletManager::amountFromDouble(0.01)) {
|
||||||
|
ui->label_fee->setStyleSheet(ColorScheme::RED.asStylesheet(true));
|
||||||
|
ui->label_fee->setToolTip("Unrealistic fee. You may be connected to a malicious node.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TxDetailsSimple::~TxDetailsSimple() = default;
|
32
src/widgets/TxDetailsSimple.h
Normal file
32
src/widgets/TxDetailsSimple.h
Normal file
|
@ -0,0 +1,32 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_TXDETAILSSIMPLE_H
|
||||||
|
#define FEATHER_TXDETAILSSIMPLE_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "components.h"
|
||||||
|
#include "libwalletqt/PendingTransaction.h"
|
||||||
|
#include "libwalletqt/WalletManager.h"
|
||||||
|
#include "libwalletqt/Wallet.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class TxDetailsSimple;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TxDetailsSimple : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit TxDetailsSimple(QWidget *parent);
|
||||||
|
~TxDetailsSimple() override;
|
||||||
|
|
||||||
|
void setDetails(Wallet *wallet, PendingTransaction *tx, const QString &address);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<Ui::TxDetailsSimple> ui;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_TXDETAILSSIMPLE_H
|
116
src/widgets/TxDetailsSimple.ui
Normal file
116
src/widgets/TxDetailsSimple.ui
Normal file
|
@ -0,0 +1,116 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>TxDetailsSimple</class>
|
||||||
|
<widget class="QWidget" name="TxDetailsSimple">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>386</width>
|
||||||
|
<height>152</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_note">
|
||||||
|
<property name="text">
|
||||||
|
<string>note</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<property name="horizontalSpacing">
|
||||||
|
<number>15</number>
|
||||||
|
</property>
|
||||||
|
<property name="verticalSpacing">
|
||||||
|
<number>7</number>
|
||||||
|
</property>
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label_3">
|
||||||
|
<property name="text">
|
||||||
|
<string>Address:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QLabel" name="label_address">
|
||||||
|
<property name="text">
|
||||||
|
<string>address</string>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Amount:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="1" column="1">
|
||||||
|
<widget class="QLabel" name="label_amount">
|
||||||
|
<property name="text">
|
||||||
|
<string>amount</string>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QLabel" name="label_2">
|
||||||
|
<property name="text">
|
||||||
|
<string>Fee:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="1">
|
||||||
|
<widget class="QLabel" name="label_fee">
|
||||||
|
<property name="text">
|
||||||
|
<string>fee</string>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="3" column="1">
|
||||||
|
<widget class="Line" name="line">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="0">
|
||||||
|
<widget class="QLabel" name="label_6">
|
||||||
|
<property name="text">
|
||||||
|
<string>Total:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="4" column="1">
|
||||||
|
<widget class="QLabel" name="label_total">
|
||||||
|
<property name="text">
|
||||||
|
<string>total</string>
|
||||||
|
</property>
|
||||||
|
<property name="textInteractionFlags">
|
||||||
|
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
54
src/widgets/URWidget.cpp
Normal file
54
src/widgets/URWidget.cpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "URWidget.h"
|
||||||
|
|
||||||
|
#include "URWidget.h"
|
||||||
|
#include "ui_URWidget.h"
|
||||||
|
|
||||||
|
#include <QDesktopServices>
|
||||||
|
#include <QInputDialog>
|
||||||
|
#include <QMenu>
|
||||||
|
#include <QTableWidget>
|
||||||
|
|
||||||
|
URWidget::URWidget(QWidget *parent)
|
||||||
|
: QWidget(parent)
|
||||||
|
, ui(new Ui::URWidget)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
m_timer.setInterval(100);
|
||||||
|
connect(&m_timer, &QTimer::timeout, this, &URWidget::nextQR);
|
||||||
|
}
|
||||||
|
|
||||||
|
void URWidget::setData(const QString &type, const std::string &data) {
|
||||||
|
std::string type_std = type.toStdString();
|
||||||
|
|
||||||
|
ur::ByteVector a = ur::string_to_bytes(data);
|
||||||
|
ur::ByteVector cbor;
|
||||||
|
ur::CborLite::encodeBytes(cbor, a);
|
||||||
|
ur::UR h = ur::UR(type_std, cbor);
|
||||||
|
|
||||||
|
delete m_urencoder;
|
||||||
|
m_urencoder = new ur::UREncoder(h, 100);
|
||||||
|
|
||||||
|
allParts.clear();
|
||||||
|
for (int i=0; i < m_urencoder->seq_len(); i++) {
|
||||||
|
allParts.append(m_urencoder->next_part());
|
||||||
|
}
|
||||||
|
|
||||||
|
m_timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
void URWidget::nextQR() {
|
||||||
|
ui->label_seq->setText(QString("%1/%2").arg(QString::number(currentIndex % m_urencoder->seq_len() + 1), QString::number(m_urencoder->seq_len())));
|
||||||
|
|
||||||
|
m_code = new QrCode{QString::fromStdString(allParts[currentIndex]), QrCode::Version::AUTO, QrCode::ErrorCorrectionLevel::MEDIUM};
|
||||||
|
ui->qrWidget->setQrCode(m_code);
|
||||||
|
|
||||||
|
currentIndex = (currentIndex + 1) % m_urencoder->seq_len();
|
||||||
|
}
|
||||||
|
|
||||||
|
URWidget::~URWidget() {
|
||||||
|
delete m_urencoder;
|
||||||
|
}
|
40
src/widgets/URWidget.h
Normal file
40
src/widgets/URWidget.h
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_URWIDGET_H
|
||||||
|
#define FEATHER_URWIDGET_H
|
||||||
|
|
||||||
|
#include <QWidget>
|
||||||
|
#include <QTimer>
|
||||||
|
|
||||||
|
#include "qrcode/QrCode.h"
|
||||||
|
#include "widgets/QrCodeWidget.h"
|
||||||
|
#include <bcur/bc-ur.hpp>
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class URWidget;
|
||||||
|
}
|
||||||
|
|
||||||
|
class URWidget : public QWidget
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit URWidget(QWidget *parent = nullptr);
|
||||||
|
~URWidget();
|
||||||
|
|
||||||
|
void setData(const QString &type, const std::string &data);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void nextQR();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QScopedPointer<Ui::URWidget> ui;
|
||||||
|
QTimer m_timer;
|
||||||
|
ur::UREncoder *m_urencoder = nullptr;
|
||||||
|
QrCode *m_code = nullptr;
|
||||||
|
QList<std::string> allParts;
|
||||||
|
qsizetype currentIndex = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_URWIDGET_H
|
69
src/widgets/URWidget.ui
Normal file
69
src/widgets/URWidget.ui
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>URWidget</class>
|
||||||
|
<widget class="QWidget" name="URWidget">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>400</width>
|
||||||
|
<height>300</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>Form</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<widget class="QrCodeWidget" name="qrWidget" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</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="QLabel" name="label_seq">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
<property name="text">
|
||||||
|
<string>seq:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>QrCodeWidget</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>widgets/QrCodeWidget.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
|
@ -68,7 +68,6 @@ WalletWizard::WalletWizard(QWidget *parent)
|
||||||
setOption(QWizard::HaveHelpButton, true);
|
setOption(QWizard::HaveHelpButton, true);
|
||||||
setOption(QWizard::HaveCustomButton1, true);
|
setOption(QWizard::HaveCustomButton1, true);
|
||||||
|
|
||||||
// Set up a custom button layout
|
|
||||||
QList<QWizard::WizardButton> layout;
|
QList<QWizard::WizardButton> layout;
|
||||||
layout << QWizard::HelpButton;
|
layout << QWizard::HelpButton;
|
||||||
layout << QWizard::CustomButton1;
|
layout << QWizard::CustomButton1;
|
||||||
|
|
72
src/wizard/offline_tx_signing/OfflineTxSigningWizard.cpp
Normal file
72
src/wizard/offline_tx_signing/OfflineTxSigningWizard.cpp
Normal file
|
@ -0,0 +1,72 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include "PageOTS_ExportOutputs.h"
|
||||||
|
#include "PageOTS_ImportKeyImages.h"
|
||||||
|
#include "PageOTS_ExportUnsignedTx.h"
|
||||||
|
#include "PageOTS_ExportSignedTx.h"
|
||||||
|
|
||||||
|
#include "PageOTS_ImportOffline.h"
|
||||||
|
#include "PageOTS_ExportKeyImages.h"
|
||||||
|
#include "PageOTS_ImportUnsignedTx.h"
|
||||||
|
#include "PageOTS_ImportSignedTx.h"
|
||||||
|
|
||||||
|
#include "utils/config.h"
|
||||||
|
|
||||||
|
OfflineTxSigningWizard::OfflineTxSigningWizard(QWidget *parent, Wallet *wallet, PendingTransaction *tx)
|
||||||
|
: QWizard(parent)
|
||||||
|
, m_wallet(wallet)
|
||||||
|
{
|
||||||
|
m_wizardFields.scanWidget = new QrCodeScanWidget(nullptr);
|
||||||
|
|
||||||
|
// View-only
|
||||||
|
setPage(Page_ExportOutputs, new PageOTS_ExportOutputs(this, m_wallet));
|
||||||
|
setPage(Page_ImportKeyImages, new PageOTS_ImportKeyImages(this, m_wallet, &m_wizardFields));
|
||||||
|
setPage(Page_ExportUnsignedTx, new PageOTS_ExportUnsignedTx(this, m_wallet, tx));
|
||||||
|
setPage(Page_ImportSignedTx, new PageOTS_ImportSignedTx(this, m_wallet, &m_wizardFields));
|
||||||
|
|
||||||
|
// Offline
|
||||||
|
setPage(Page_ImportOffline, new PageOTS_ImportOffline(this, m_wallet, &m_wizardFields));
|
||||||
|
setPage(Page_ExportKeyImages, new PageOTS_ExportKeyImages(this, m_wallet));
|
||||||
|
setPage(Page_ImportUnsignedTx, new PageOTS_ImportUnsignedTx(this, m_wallet, &m_wizardFields));
|
||||||
|
setPage(Page_ExportSignedTx, new PageOTS_ExportSignedTx(this, m_wallet, &m_wizardFields));
|
||||||
|
|
||||||
|
if (tx) {
|
||||||
|
setStartId(Page_ExportUnsignedTx);
|
||||||
|
} else {
|
||||||
|
setStartId(m_wallet->viewOnly() ? Page_ExportOutputs : Page_ImportOffline);
|
||||||
|
}
|
||||||
|
|
||||||
|
this->setWindowTitle("Offline transaction signing");
|
||||||
|
|
||||||
|
QList<QWizard::WizardButton> layout;
|
||||||
|
layout << QWizard::CancelButton;
|
||||||
|
layout << QWizard::Stretch;
|
||||||
|
layout << QWizard::BackButton;
|
||||||
|
layout << QWizard::NextButton;
|
||||||
|
layout << QWizard::FinishButton;
|
||||||
|
layout << QWizard::CommitButton;
|
||||||
|
this->setButtonLayout(layout);
|
||||||
|
|
||||||
|
setOption(QWizard::NoBackButtonOnStartPage);
|
||||||
|
setWizardStyle(WizardStyle::ModernStyle);
|
||||||
|
|
||||||
|
bool geo = this->restoreGeometry(QByteArray::fromBase64(conf()->get(Config::geometryOTSWizard).toByteArray()));
|
||||||
|
if (!geo) {
|
||||||
|
this->resize(600, 875);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool OfflineTxSigningWizard::readyToCommit() {
|
||||||
|
return m_wizardFields.readyToCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTransaction* OfflineTxSigningWizard::signedTx() {
|
||||||
|
return m_wizardFields.tx;
|
||||||
|
}
|
||||||
|
|
||||||
|
OfflineTxSigningWizard::~OfflineTxSigningWizard() {
|
||||||
|
conf()->set(Config::geometryOTSWizard, QString(saveGeometry().toBase64()));
|
||||||
|
}
|
50
src/wizard/offline_tx_signing/OfflineTxSigningWizard.h
Normal file
50
src/wizard/offline_tx_signing/OfflineTxSigningWizard.h
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_OFFLINETXSIGNINGWIZARD_H
|
||||||
|
#define FEATHER_OFFLINETXSIGNINGWIZARD_H
|
||||||
|
|
||||||
|
#include <QWizard>
|
||||||
|
#include "Wallet.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include "qrcode/scanner/QrCodeScanWidget.h"
|
||||||
|
|
||||||
|
struct TxWizardFields {
|
||||||
|
UnsignedTransaction *utx = nullptr;
|
||||||
|
PendingTransaction *tx = nullptr;
|
||||||
|
std::string signedTx;
|
||||||
|
QrCodeScanWidget *scanWidget = nullptr;
|
||||||
|
bool readyToCommit = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
class OfflineTxSigningWizard : public QWizard
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum Page {
|
||||||
|
Page_ExportOutputs = 0,
|
||||||
|
Page_ExportKeyImages,
|
||||||
|
Page_ImportKeyImages,
|
||||||
|
Page_ExportUnsignedTx,
|
||||||
|
Page_ImportUnsignedTx,
|
||||||
|
Page_SignTx,
|
||||||
|
Page_ExportSignedTx,
|
||||||
|
Page_ImportSignedTx,
|
||||||
|
Page_ImportOffline
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit OfflineTxSigningWizard(QWidget *parent, Wallet *wallet, PendingTransaction *tx = nullptr);
|
||||||
|
~OfflineTxSigningWizard() override;
|
||||||
|
|
||||||
|
bool readyToCommit();
|
||||||
|
PendingTransaction* signedTx();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Wallet *m_wallet;
|
||||||
|
TxWizardFields m_wizardFields;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FEATHER_OFFLINETXSIGNINGWIZARD_H
|
123
src/wizard/offline_tx_signing/PageOTS_Export.ui
Normal file
123
src/wizard/offline_tx_signing/PageOTS_Export.ui
Normal file
|
@ -0,0 +1,123 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>PageOTS_Export</class>
|
||||||
|
<widget class="QWizardPage" name="PageOTS_Export">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>758</width>
|
||||||
|
<height>734</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>WizardPage</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Method:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="combo_method">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Animated QR Codes</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Files</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_step">
|
||||||
|
<property name="text">
|
||||||
|
<string>details</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="page_UR">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="URWidget" name="widget_UR" native="true">
|
||||||
|
<property name="sizePolicy">
|
||||||
|
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||||
|
<horstretch>0</horstretch>
|
||||||
|
<verstretch>0</verstretch>
|
||||||
|
</sizepolicy>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_instructions">
|
||||||
|
<property name="text">
|
||||||
|
<string>Scan this animated QR code with your view-only wallet.</string>
|
||||||
|
</property>
|
||||||
|
<property name="wordWrap">
|
||||||
|
<bool>true</bool>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_files">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btn_export">
|
||||||
|
<property name="text">
|
||||||
|
<string>Export to file</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<layout class="QVBoxLayout" name="layout_extra"/>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>URWidget</class>
|
||||||
|
<extends>QWidget</extends>
|
||||||
|
<header>widgets/URWidget.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
68
src/wizard/offline_tx_signing/PageOTS_ExportKeyImages.cpp
Normal file
68
src/wizard/offline_tx_signing/PageOTS_ExportKeyImages.cpp
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ExportKeyImages.h"
|
||||||
|
#include "ui_PageOTS_Export.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include <QCheckBox>
|
||||||
|
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
PageOTS_ExportKeyImages::PageOTS_ExportKeyImages(QWidget *parent, Wallet *wallet)
|
||||||
|
: QWizardPage(parent)
|
||||||
|
, ui(new Ui::PageOTS_Export)
|
||||||
|
, m_wallet(wallet)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
this->setTitle("Export key images");
|
||||||
|
|
||||||
|
ui->label_step->hide();
|
||||||
|
ui->label_instructions->setText("Scan this animated QR code with the view-only wallet.");
|
||||||
|
|
||||||
|
auto check_exportAll = new QCheckBox(this);
|
||||||
|
check_exportAll->setText("Export all key images");
|
||||||
|
ui->layout_extra->addWidget(check_exportAll);
|
||||||
|
connect(check_exportAll, &QCheckBox::toggled, this, &PageOTS_ExportKeyImages::setupUR);
|
||||||
|
|
||||||
|
connect(ui->btn_export, &QPushButton::clicked, this, &PageOTS_ExportKeyImages::exportKeyImages);
|
||||||
|
connect(ui->combo_method, &QComboBox::currentIndexChanged, [this](int index){
|
||||||
|
conf()->set(Config::offlineTxSigningMethod, index);
|
||||||
|
ui->stackedWidget->setCurrentIndex(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->combo_method->setCurrentIndex(conf()->get(Config::offlineTxSigningMethod).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportKeyImages::exportKeyImages() {
|
||||||
|
QString fn = QFileDialog::getSaveFileName(this, "Save key images to file", QString("%1/%2_%3").arg(QDir::homePath(), m_wallet->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Key Images (*_keyImages)");
|
||||||
|
if (fn.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fn.endsWith("_keyImages")) {
|
||||||
|
fn += "_keyImages";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r = m_wallet->exportKeyImages(fn, true);
|
||||||
|
if (!r) {
|
||||||
|
Utils::showError(this, "Failed to export key images", m_wallet->errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::showInfo(this, "Successfully exported key images");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportKeyImages::setupUR(bool all) {
|
||||||
|
std::string ki_export;
|
||||||
|
m_wallet->exportKeyImagesToStr(ki_export, all);
|
||||||
|
ui->widget_UR->setData("xmr-keyimage", ki_export);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportKeyImages::initializePage() {
|
||||||
|
this->setupUR(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PageOTS_ExportKeyImages::nextId() const {
|
||||||
|
return OfflineTxSigningWizard::Page_ImportUnsignedTx;
|
||||||
|
}
|
33
src/wizard/offline_tx_signing/PageOTS_ExportKeyImages.h
Normal file
33
src/wizard/offline_tx_signing/PageOTS_ExportKeyImages.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_EXPORTKEYIMAGES_H
|
||||||
|
#define FEATHER_PAGEOTS_EXPORTKEYIMAGES_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Export;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ExportKeyImages : public QWizardPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ExportKeyImages(QWidget *parent, Wallet *wallet);
|
||||||
|
void initializePage() override;
|
||||||
|
[[nodiscard]] int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void exportKeyImages();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUR(bool all);
|
||||||
|
|
||||||
|
Ui::PageOTS_Export *ui;
|
||||||
|
Wallet *m_wallet;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_EXPORTKEYIMAGES_H
|
69
src/wizard/offline_tx_signing/PageOTS_ExportOutputs.cpp
Normal file
69
src/wizard/offline_tx_signing/PageOTS_ExportOutputs.cpp
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ExportOutputs.h"
|
||||||
|
#include "ui_PageOTS_Export.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
#include <QCheckBox>
|
||||||
|
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
|
||||||
|
PageOTS_ExportOutputs::PageOTS_ExportOutputs(QWidget *parent, Wallet *wallet)
|
||||||
|
: QWizardPage(parent)
|
||||||
|
, ui(new Ui::PageOTS_Export)
|
||||||
|
, m_wallet(wallet)
|
||||||
|
, m_check_exportAll(new QCheckBox(this))
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
this->setTitle("Export outputs");
|
||||||
|
|
||||||
|
ui->label_step->hide();
|
||||||
|
ui->label_instructions->setText("Scan this animated QR code with your offline wallet (Tools → Offline Transaction Signing).");
|
||||||
|
|
||||||
|
m_check_exportAll->setText("Export all outputs");
|
||||||
|
ui->layout_extra->addWidget(m_check_exportAll);
|
||||||
|
connect(m_check_exportAll, &QCheckBox::toggled, this, &PageOTS_ExportOutputs::setupUR);
|
||||||
|
|
||||||
|
connect(ui->btn_export, &QPushButton::clicked, this, &PageOTS_ExportOutputs::exportOutputs);
|
||||||
|
connect(ui->combo_method, &QComboBox::currentIndexChanged, [this](int index){
|
||||||
|
conf()->set(Config::offlineTxSigningMethod, index);
|
||||||
|
ui->stackedWidget->setCurrentIndex(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->combo_method->setCurrentIndex(conf()->get(Config::offlineTxSigningMethod).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportOutputs::exportOutputs() {
|
||||||
|
QString fn = QFileDialog::getSaveFileName(this, "Save outputs to file", QString("%1/%2_%3").arg(QDir::homePath(), m_wallet->walletName(), QString::number(QDateTime::currentSecsSinceEpoch())), "Outputs (*_outputs)");
|
||||||
|
if (fn.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!fn.endsWith("_outputs")) {
|
||||||
|
fn += "_outputs";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r = m_wallet->exportOutputs(fn, m_check_exportAll->isChecked());
|
||||||
|
if (!r) {
|
||||||
|
Utils::showError(this, "Failed to export outputs", m_wallet->errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::showInfo(this, "Successfully exported outputs.");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportOutputs::setupUR(bool all) {
|
||||||
|
std::string output_export;
|
||||||
|
m_wallet->exportOutputsToStr(output_export, all);
|
||||||
|
ui->widget_UR->setData("xmr-output", output_export);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportOutputs::initializePage() {
|
||||||
|
this->setupUR(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PageOTS_ExportOutputs::nextId() const {
|
||||||
|
return OfflineTxSigningWizard::Page_ImportKeyImages;
|
||||||
|
}
|
36
src/wizard/offline_tx_signing/PageOTS_ExportOutputs.h
Normal file
36
src/wizard/offline_tx_signing/PageOTS_ExportOutputs.h
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_EXPORTOUTPUTS_H
|
||||||
|
#define FEATHER_PAGEOTS_EXPORTOUTPUTS_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include <QCheckBox>
|
||||||
|
#include "Wallet.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Export;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ExportOutputs : public QWizardPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ExportOutputs(QWidget *parent, Wallet *wallet);
|
||||||
|
void initializePage() override;
|
||||||
|
[[nodiscard]] int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void exportOutputs();
|
||||||
|
|
||||||
|
private:
|
||||||
|
void setupUR(bool all);
|
||||||
|
|
||||||
|
Ui::PageOTS_Export *ui;
|
||||||
|
QCheckBox *m_check_exportAll;
|
||||||
|
Wallet *m_wallet;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_EXPORTOUTPUTS_H
|
64
src/wizard/offline_tx_signing/PageOTS_ExportSignedTx.cpp
Normal file
64
src/wizard/offline_tx_signing/PageOTS_ExportSignedTx.cpp
Normal file
|
@ -0,0 +1,64 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ExportSignedTx.h"
|
||||||
|
#include "ui_PageOTS_Export.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
PageOTS_ExportSignedTx::PageOTS_ExportSignedTx(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields)
|
||||||
|
: QWizardPage(parent)
|
||||||
|
, ui(new Ui::PageOTS_Export)
|
||||||
|
, m_wallet(wallet)
|
||||||
|
, m_wizardFields(wizardFields)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
this->setTitle("Export signed transaction");
|
||||||
|
|
||||||
|
ui->label_step->hide();
|
||||||
|
ui->label_instructions->setText("Scan this animated QR code with your view-only wallet.");
|
||||||
|
|
||||||
|
connect(ui->btn_export, &QPushButton::clicked, this, &PageOTS_ExportSignedTx::exportSignedTx);
|
||||||
|
connect(ui->combo_method, &QComboBox::currentIndexChanged, [this](int index){
|
||||||
|
conf()->set(Config::offlineTxSigningMethod, index);
|
||||||
|
ui->stackedWidget->setCurrentIndex(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->combo_method->setCurrentIndex(conf()->get(Config::offlineTxSigningMethod).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportSignedTx::exportSignedTx() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r = m_wizardFields->utx->sign(fn);
|
||||||
|
|
||||||
|
if (!r) {
|
||||||
|
Utils::showError(this, "Failed to save transaction to file");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::showInfo(this, "Transaction saved successfully");
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportSignedTx::initializePage() {
|
||||||
|
if (m_wizardFields->utx) {
|
||||||
|
m_wizardFields->utx->signToStr(m_wizardFields->signedTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: check that signedTx is not empty
|
||||||
|
|
||||||
|
ui->widget_UR->setData("xmr-txsigned", m_wizardFields->signedTx);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PageOTS_ExportSignedTx::nextId() const {
|
||||||
|
return -1;
|
||||||
|
}
|
33
src/wizard/offline_tx_signing/PageOTS_ExportSignedTx.h
Normal file
33
src/wizard/offline_tx_signing/PageOTS_ExportSignedTx.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_EXPORTSIGNEDTX_H
|
||||||
|
#define FEATHER_PAGEOTS_EXPORTSIGNEDTX_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Export;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ExportSignedTx : public QWizardPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ExportSignedTx(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields);
|
||||||
|
void initializePage() override;
|
||||||
|
[[nodiscard]] int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void exportSignedTx();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::PageOTS_Export *ui;
|
||||||
|
Wallet *m_wallet;
|
||||||
|
TxWizardFields *m_wizardFields;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_EXPORTSIGNEDTX_H
|
54
src/wizard/offline_tx_signing/PageOTS_ExportUnsignedTx.cpp
Normal file
54
src/wizard/offline_tx_signing/PageOTS_ExportUnsignedTx.cpp
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ExportUnsignedTx.h"
|
||||||
|
#include "ui_PageOTS_Export.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
|
||||||
|
PageOTS_ExportUnsignedTx::PageOTS_ExportUnsignedTx(QWidget *parent, Wallet *wallet, PendingTransaction *tx)
|
||||||
|
: QWizardPage(parent)
|
||||||
|
, ui(new Ui::PageOTS_Export)
|
||||||
|
, m_wallet(wallet)
|
||||||
|
, m_tx(tx)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
this->setTitle("Export unsigned transaction");
|
||||||
|
|
||||||
|
ui->label_step->hide();
|
||||||
|
ui->label_instructions->setText("Scan this animated QR code with the offline wallet.");
|
||||||
|
|
||||||
|
connect(ui->btn_export, &QPushButton::clicked, this, &PageOTS_ExportUnsignedTx::exportUnsignedTx);
|
||||||
|
connect(ui->combo_method, &QComboBox::currentIndexChanged, [this](int index){
|
||||||
|
conf()->set(Config::offlineTxSigningMethod, index);
|
||||||
|
ui->stackedWidget->setCurrentIndex(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->combo_method->setCurrentIndex(conf()->get(Config::offlineTxSigningMethod).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportUnsignedTx::initializePage() {
|
||||||
|
ui->widget_UR->setData("xmr-txunsigned", m_tx->unsignedTxToBin());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ExportUnsignedTx::exportUnsignedTx() {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r = m_tx->saveToFile(fn);
|
||||||
|
if (!r) {
|
||||||
|
Utils::showError(this, "Failed to export unsigned transaction", m_wallet->errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils::showInfo(this, "Successfully exported unsigned transaction");
|
||||||
|
}
|
||||||
|
|
||||||
|
int PageOTS_ExportUnsignedTx::nextId() const {
|
||||||
|
return OfflineTxSigningWizard::Page_ImportSignedTx;
|
||||||
|
}
|
33
src/wizard/offline_tx_signing/PageOTS_ExportUnsignedTx.h
Normal file
33
src/wizard/offline_tx_signing/PageOTS_ExportUnsignedTx.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_EXPORTUNSIGNEDTX_H
|
||||||
|
#define FEATHER_PAGEOTS_EXPORTUNSIGNEDTX_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
#include "PendingTransaction.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Export;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ExportUnsignedTx : public QWizardPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ExportUnsignedTx(QWidget *parent, Wallet *wallet, PendingTransaction *tx = nullptr);
|
||||||
|
void initializePage() override;
|
||||||
|
int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void exportUnsignedTx();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Ui::PageOTS_Export *ui;
|
||||||
|
Wallet *m_wallet;
|
||||||
|
PendingTransaction *m_tx;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_EXPORTUNSIGNEDTX_H
|
79
src/wizard/offline_tx_signing/PageOTS_Import.cpp
Normal file
79
src/wizard/offline_tx_signing/PageOTS_Import.cpp
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Projecteated by user on 11/5/23.
|
||||||
|
|
||||||
|
#include "PageOTS_Import.h"
|
||||||
|
#include "ui_PageOTS_Import.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Icons.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
PageOTS_Import::PageOTS_Import(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields, const QString &type, const QString &successButtonText)
|
||||||
|
: QWizardPage(parent)
|
||||||
|
, m_wallet(wallet)
|
||||||
|
, m_wizardFields(wizardFields)
|
||||||
|
, m_scanWidget(wizardFields->scanWidget)
|
||||||
|
, m_type(type)
|
||||||
|
, m_successButtonText(successButtonText)
|
||||||
|
, ui(new Ui::PageOTS_Import)
|
||||||
|
{
|
||||||
|
ui->setupUi(this);
|
||||||
|
|
||||||
|
this->setTitle(QString("Import %1").arg(m_type));
|
||||||
|
this->setCommitPage(true);
|
||||||
|
this->setButtonText(QWizard::CommitButton, "Next");
|
||||||
|
this->setButtonText(QWizard::FinishButton, "Next");
|
||||||
|
|
||||||
|
ui->label_step->hide();
|
||||||
|
ui->frame_status->hide();
|
||||||
|
|
||||||
|
connect(ui->btn_import, &QPushButton::clicked, this, &PageOTS_Import::importFromFile);
|
||||||
|
connect(ui->combo_method, &QComboBox::currentIndexChanged, [this](int index){
|
||||||
|
conf()->set(Config::offlineTxSigningMethod, index);
|
||||||
|
ui->stackedWidget->setCurrentIndex(index);
|
||||||
|
});
|
||||||
|
|
||||||
|
ui->combo_method->setCurrentIndex(conf()->get(Config::offlineTxSigningMethod).toInt());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_Import::onScanFinished(bool success) {
|
||||||
|
if (!success) {
|
||||||
|
Utils::showError(this, "Failed to scan QR code", m_scanWidget->getURError());
|
||||||
|
m_scanWidget->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string data = m_scanWidget->getURData();
|
||||||
|
importFromStr(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_Import::onSuccess() {
|
||||||
|
m_success = true;
|
||||||
|
emit completeChanged();
|
||||||
|
|
||||||
|
this->wizard()->button(QWizard::CommitButton)->click();
|
||||||
|
this->wizard()->button(QWizard::FinishButton)->click();
|
||||||
|
|
||||||
|
ui->frame_status->show();
|
||||||
|
ui->frame_status->setInfo(icons()->icon("confirmed.svg"), QString("%1 imported successfully").arg(m_type));
|
||||||
|
this->setButtonText(QWizard::FinishButton, m_successButtonText);
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_Import::initializePage() {
|
||||||
|
m_scanWidget->reset();
|
||||||
|
connect(m_scanWidget, &QrCodeScanWidget::finished, this, &PageOTS_Import::onScanFinished);
|
||||||
|
ui->layout_scanner->addWidget(m_scanWidget);
|
||||||
|
m_scanWidget->startCapture(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PageOTS_Import::isComplete() const {
|
||||||
|
return m_success;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PageOTS_Import::validatePage() {
|
||||||
|
m_scanWidget->disconnect();
|
||||||
|
return true;
|
||||||
|
}
|
45
src/wizard/offline_tx_signing/PageOTS_Import.h
Normal file
45
src/wizard/offline_tx_signing/PageOTS_Import.h
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_IMPORT_H
|
||||||
|
#define FEATHER_PAGEOTS_IMPORT_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
#include "qrcode/scanner/QrCodeScanWidget.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Import;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_Import : public QWizardPage
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_Import(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields, const QString &type, const QString &successButtonText = "Next");
|
||||||
|
void initializePage() override;
|
||||||
|
bool validatePage() override;
|
||||||
|
bool isComplete() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void onScanFinished(bool success);
|
||||||
|
|
||||||
|
private:
|
||||||
|
virtual void importFromStr(const std::string &data) = 0;
|
||||||
|
virtual void importFromFile() = 0;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void onSuccess();
|
||||||
|
|
||||||
|
Ui::PageOTS_Import *ui;
|
||||||
|
TxWizardFields *m_wizardFields;
|
||||||
|
QrCodeScanWidget *m_scanWidget;
|
||||||
|
bool m_success = false;
|
||||||
|
Wallet *m_wallet;
|
||||||
|
QString m_type;
|
||||||
|
QString m_successButtonText;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_IMPORT_H
|
134
src/wizard/offline_tx_signing/PageOTS_Import.ui
Normal file
134
src/wizard/offline_tx_signing/PageOTS_Import.ui
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<ui version="4.0">
|
||||||
|
<class>PageOTS_Import</class>
|
||||||
|
<widget class="QWizardPage" name="PageOTS_Import">
|
||||||
|
<property name="geometry">
|
||||||
|
<rect>
|
||||||
|
<x>0</x>
|
||||||
|
<y>0</y>
|
||||||
|
<width>741</width>
|
||||||
|
<height>723</height>
|
||||||
|
</rect>
|
||||||
|
</property>
|
||||||
|
<property name="windowTitle">
|
||||||
|
<string>WizardPage</string>
|
||||||
|
</property>
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout">
|
||||||
|
<item>
|
||||||
|
<layout class="QFormLayout" name="formLayout">
|
||||||
|
<item row="0" column="0">
|
||||||
|
<widget class="QLabel" name="label">
|
||||||
|
<property name="text">
|
||||||
|
<string>Method:</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="0" column="1">
|
||||||
|
<widget class="QComboBox" name="combo_method">
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Animated QR Codes</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<property name="text">
|
||||||
|
<string>Files</string>
|
||||||
|
</property>
|
||||||
|
</item>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_step">
|
||||||
|
<property name="text">
|
||||||
|
<string>details</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QStackedWidget" name="stackedWidget">
|
||||||
|
<property name="currentIndex">
|
||||||
|
<number>0</number>
|
||||||
|
</property>
|
||||||
|
<widget class="QWidget" name="page">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_2">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="layout_scanner">
|
||||||
|
<item>
|
||||||
|
<spacer name="verticalSpacer">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Vertical</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>0</width>
|
||||||
|
<height>500</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="QLabel" name="label_instructions">
|
||||||
|
<property name="text">
|
||||||
|
<string>Scan the animated QR code shown on the view-only wallet.</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<widget class="QWidget" name="page_2">
|
||||||
|
<layout class="QVBoxLayout" name="verticalLayout_3">
|
||||||
|
<item>
|
||||||
|
<layout class="QHBoxLayout" name="horizontalLayout_2">
|
||||||
|
<item>
|
||||||
|
<widget class="QPushButton" name="btn_import">
|
||||||
|
<property name="text">
|
||||||
|
<string>Import from file</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<spacer name="horizontalSpacer_2">
|
||||||
|
<property name="orientation">
|
||||||
|
<enum>Qt::Horizontal</enum>
|
||||||
|
</property>
|
||||||
|
<property name="sizeHint" stdset="0">
|
||||||
|
<size>
|
||||||
|
<width>40</width>
|
||||||
|
<height>20</height>
|
||||||
|
</size>
|
||||||
|
</property>
|
||||||
|
</spacer>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item>
|
||||||
|
<widget class="InfoFrame" name="frame_status">
|
||||||
|
<property name="frameShape">
|
||||||
|
<enum>QFrame::StyledPanel</enum>
|
||||||
|
</property>
|
||||||
|
<property name="frameShadow">
|
||||||
|
<enum>QFrame::Raised</enum>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
</layout>
|
||||||
|
</widget>
|
||||||
|
<customwidgets>
|
||||||
|
<customwidget>
|
||||||
|
<class>InfoFrame</class>
|
||||||
|
<extends>QFrame</extends>
|
||||||
|
<header>components.h</header>
|
||||||
|
<container>1</container>
|
||||||
|
</customwidget>
|
||||||
|
</customwidgets>
|
||||||
|
<resources/>
|
||||||
|
<connections/>
|
||||||
|
</ui>
|
47
src/wizard/offline_tx_signing/PageOTS_ImportKeyImages.cpp
Normal file
47
src/wizard/offline_tx_signing/PageOTS_ImportKeyImages.cpp
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ImportKeyImages.h"
|
||||||
|
#include "ui_PageOTS_Import.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Icons.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
PageOTS_ImportKeyImages::PageOTS_ImportKeyImages(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields)
|
||||||
|
: PageOTS_Import(parent, wallet, wizardFields, "key images", "Create transaction")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportKeyImages::importFromStr(const std::string &data) {
|
||||||
|
bool r = m_wallet->importKeyImagesFromStr(data);
|
||||||
|
if (!r) {
|
||||||
|
Utils::showError(this, "Failed to import key images", m_wallet->errorString());
|
||||||
|
m_scanWidget->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PageOTS_Import::onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportKeyImages::importFromFile() {
|
||||||
|
QString fn = QFileDialog::getOpenFileName(this, "Import key image file", QDir::homePath(), "Key Images (*_keyImages);;All Files (*)");
|
||||||
|
if (fn.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool r = m_wallet->importKeyImages(fn);
|
||||||
|
if (!r) {
|
||||||
|
Utils::showError(this, "Failed to import key images", m_wallet->errorString());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PageOTS_Import::onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
int PageOTS_ImportKeyImages::nextId() const {
|
||||||
|
return -1;
|
||||||
|
}
|
33
src/wizard/offline_tx_signing/PageOTS_ImportKeyImages.h
Normal file
33
src/wizard/offline_tx_signing/PageOTS_ImportKeyImages.h
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_IMPORTKEYIMAGES_H
|
||||||
|
#define FEATHER_PAGEOTS_IMPORTKEYIMAGES_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
#include "qrcode/scanner/QrCodeScanWidget.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
#include "PageOTS_Import.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Import;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ImportKeyImages : public PageOTS_Import
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ImportKeyImages(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields);
|
||||||
|
int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void importFromStr(const std::string &data) override;
|
||||||
|
void importFromFile() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void onSuccess();
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_IMPORTKEYIMAGES_H
|
114
src/wizard/offline_tx_signing/PageOTS_ImportOffline.cpp
Normal file
114
src/wizard/offline_tx_signing/PageOTS_ImportOffline.cpp
Normal file
|
@ -0,0 +1,114 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ImportOffline.h"
|
||||||
|
#include "ui_PageOTS_Import.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include "dialog/TxConfAdvDialog.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Icons.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
PageOTS_ImportOffline::PageOTS_ImportOffline(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields)
|
||||||
|
: PageOTS_Import(parent, wallet, wizardFields, "outputs or unsigned transactions", "Next")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportOffline::importFromStr(const std::string &data) {
|
||||||
|
if (this->isOutputs(data)) {
|
||||||
|
bool r = m_wallet->importOutputsFromStr(data);
|
||||||
|
if (!r) {
|
||||||
|
Utils::showError(this, "Failed to import outputs", m_wallet->errorString());
|
||||||
|
m_scanWidget->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->frame_status->show();
|
||||||
|
ui->frame_status->setInfo(icons()->icon("confirmed.svg"), "Outputs imported successfully");
|
||||||
|
}
|
||||||
|
else if (this->isUnsignedTransaction(data)) {
|
||||||
|
UnsignedTransaction *utx = m_wallet->loadUnsignedTransactionFromStr(data);
|
||||||
|
|
||||||
|
if (utx->status() != UnsignedTransaction::Status_Ok) {
|
||||||
|
Utils::showError(this, "Failed to import unsigned transaction", m_wallet->errorString());
|
||||||
|
m_scanWidget->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ui->frame_status->show();
|
||||||
|
ui->frame_status->setInfo(icons()->icon("confirmed.svg"), "Unsigned transaction imported successfully");
|
||||||
|
m_wizardFields->utx = utx;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Utils::showError(this, "Failed to import outputs or unsigned transaction", "Unrecognized data");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PageOTS_Import::onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportOffline::importFromFile() {
|
||||||
|
QString fn = QFileDialog::getOpenFileName(this, "Import outputs or unsigned tx file", QDir::homePath(), "All Files (*)");
|
||||||
|
if (fn.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(fn);
|
||||||
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QByteArray qdata = file.readAll();
|
||||||
|
std::string data = qdata.toStdString();
|
||||||
|
|
||||||
|
file.close();
|
||||||
|
|
||||||
|
importFromStr(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
bool PageOTS_ImportOffline::isOutputs(const std::string &data) {
|
||||||
|
std::string outputMagic = "Monero output export";
|
||||||
|
const size_t magiclen = outputMagic.length();
|
||||||
|
if (data.size() < magiclen || memcmp(data.data(), outputMagic.data(), magiclen) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_importType = ImportType::OUTPUTS;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PageOTS_ImportOffline::isUnsignedTransaction(const std::string &data) {
|
||||||
|
std::string utxMagic = "Monero unsigned tx set";
|
||||||
|
const size_t magiclen = utxMagic.length();
|
||||||
|
if (data.size() < magiclen || memcmp(data.data(), utxMagic.data(), magiclen) != 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
m_importType = ImportType::UNSIGNED_TX;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int PageOTS_ImportOffline::nextId() const {
|
||||||
|
return m_importType == ImportType::OUTPUTS ? OfflineTxSigningWizard::Page_ExportKeyImages : OfflineTxSigningWizard::Page_ExportSignedTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
//bool PageOTS_ImportOffline::validatePage() {
|
||||||
|
// m_scanWidget->disconnect();
|
||||||
|
//
|
||||||
|
// if (m_wizardFields->utx) {
|
||||||
|
//// TxConfAdvDialog dialog{m_wallet, "", this};
|
||||||
|
//// dialog.setUnsignedTransaction(m_wizardFields->utx);
|
||||||
|
//// auto r = dialog.exec();
|
||||||
|
////
|
||||||
|
//// if (r != QDialog::Accepted) {
|
||||||
|
//// return false;
|
||||||
|
//// }
|
||||||
|
//
|
||||||
|
// m_wizardFields->utx->signToStr(m_wizardFields->signedTx);
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// return true;
|
||||||
|
//}
|
41
src/wizard/offline_tx_signing/PageOTS_ImportOffline.h
Normal file
41
src/wizard/offline_tx_signing/PageOTS_ImportOffline.h
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_IMPORTOFFLINE_H
|
||||||
|
#define FEATHER_PAGEOTS_IMPORTOFFLINE_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
#include "qrcode/scanner/QrCodeScanWidget.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
#include "PageOTS_Import.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Import;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ImportOffline : public PageOTS_Import
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
enum ImportType {
|
||||||
|
OUTPUTS = 0,
|
||||||
|
UNSIGNED_TX
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ImportOffline(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields);
|
||||||
|
int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void importFromStr(const std::string &data) override;
|
||||||
|
void importFromFile() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool isOutputs(const std::string &data);
|
||||||
|
bool isUnsignedTransaction(const std::string &data);
|
||||||
|
|
||||||
|
ImportType m_importType;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_IMPORT_H
|
58
src/wizard/offline_tx_signing/PageOTS_ImportSignedTx.cpp
Normal file
58
src/wizard/offline_tx_signing/PageOTS_ImportSignedTx.cpp
Normal file
|
@ -0,0 +1,58 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ImportSignedTx.h"
|
||||||
|
#include "ui_PageOTS_Import.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include "dialog/TxConfDialog.h"
|
||||||
|
#include "dialog/TxConfAdvDialog.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Icons.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
PageOTS_ImportSignedTx::PageOTS_ImportSignedTx(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields)
|
||||||
|
: PageOTS_Import(parent, wallet, wizardFields, "signed transaction", "Send..")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportSignedTx::importFromStr(const std::string &data) {
|
||||||
|
PendingTransaction *tx = m_wallet->loadSignedTxFromStr(data);
|
||||||
|
if (tx->status() != PendingTransaction::Status_Ok) {
|
||||||
|
Utils::showError(this, "Failed to import signed transaction", m_wallet->errorString());
|
||||||
|
m_scanWidget->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wizardFields->tx = tx;
|
||||||
|
PageOTS_Import::onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportSignedTx::importFromFile() {
|
||||||
|
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*signed_monero_tx);;All Files (*)");
|
||||||
|
if (fn.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTransaction *tx = m_wallet->loadSignedTxFile(fn);
|
||||||
|
auto err = m_wallet->errorString();
|
||||||
|
if (!err.isEmpty()) {
|
||||||
|
Utils::showError(this, "Failed to load signed transaction", err);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wizardFields->tx = tx;
|
||||||
|
PageOTS_Import::onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
int PageOTS_ImportSignedTx::nextId() const {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PageOTS_ImportSignedTx::validatePage() {
|
||||||
|
m_scanWidget->disconnect();
|
||||||
|
m_wizardFields->readyToCommit = true;
|
||||||
|
return true;
|
||||||
|
}
|
34
src/wizard/offline_tx_signing/PageOTS_ImportSignedTx.h
Normal file
34
src/wizard/offline_tx_signing/PageOTS_ImportSignedTx.h
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_IMPORTSIGNEDTX_H
|
||||||
|
#define FEATHER_PAGEOTS_IMPORTSIGNEDTX_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
#include "qrcode/scanner/QrCodeScanWidget.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
#include "PageOTS_Import.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Import;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ImportSignedTx : public PageOTS_Import
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ImportSignedTx(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields);
|
||||||
|
// void initializePage() override;
|
||||||
|
int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void importFromStr(const std::string &data) override;
|
||||||
|
void importFromFile() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool validatePage() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_IMPORTSIGNEDTX_H
|
69
src/wizard/offline_tx_signing/PageOTS_ImportUnsignedTx.cpp
Normal file
69
src/wizard/offline_tx_signing/PageOTS_ImportUnsignedTx.cpp
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#include "PageOTS_ImportUnsignedTx.h"
|
||||||
|
#include "ui_PageOTS_Import.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
|
||||||
|
#include <QFileDialog>
|
||||||
|
|
||||||
|
#include "dialog/TxConfAdvDialog.h"
|
||||||
|
#include "utils/config.h"
|
||||||
|
#include "utils/Icons.h"
|
||||||
|
#include "utils/Utils.h"
|
||||||
|
|
||||||
|
PageOTS_ImportUnsignedTx::PageOTS_ImportUnsignedTx(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields)
|
||||||
|
: PageOTS_Import(parent, wallet, wizardFields, "unsigned transaction", "Review transaction")
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportUnsignedTx::importFromStr(const std::string &data) {
|
||||||
|
UnsignedTransaction *utx = m_wallet->loadUnsignedTransactionFromStr(data);
|
||||||
|
|
||||||
|
if (utx->status() != UnsignedTransaction::Status_Ok) {
|
||||||
|
Utils::showError(this, "Failed to import unsigned transaction", m_wallet->errorString());
|
||||||
|
m_scanWidget->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wizardFields->utx = utx;
|
||||||
|
PageOTS_Import::onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PageOTS_ImportUnsignedTx::importFromFile() {
|
||||||
|
QString fn = QFileDialog::getOpenFileName(this, "Select transaction to load", QDir::homePath(), "Transaction (*unsigned_monero_tx);;All Files (*)");
|
||||||
|
if (fn.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
UnsignedTransaction *utx = m_wallet->loadTxFile(fn);
|
||||||
|
if (utx->status() != UnsignedTransaction::Status_Ok) {
|
||||||
|
Utils::showError(this, "Failed to import unsigned transaction", m_wallet->errorString());
|
||||||
|
m_scanWidget->reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_wizardFields->utx = utx;
|
||||||
|
PageOTS_Import::onSuccess();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
int PageOTS_ImportUnsignedTx::nextId() const {
|
||||||
|
return OfflineTxSigningWizard::Page_ExportSignedTx;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//bool PageOTS_ImportUnsignedTx::validatePage() {
|
||||||
|
// m_scanWidget->disconnect();
|
||||||
|
// TxConfAdvDialog dialog{m_wallet, "", this};
|
||||||
|
// dialog.setUnsignedTransaction(m_wizardFields->utx);
|
||||||
|
// auto r = dialog.exec();
|
||||||
|
//
|
||||||
|
// if (r != QDialog::Accepted) {
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// // TODO: error handling
|
||||||
|
// m_wizardFields->utx->signToStr(m_wizardFields->signedTx);
|
||||||
|
// return true;
|
||||||
|
//}
|
29
src/wizard/offline_tx_signing/PageOTS_ImportUnsignedTx.h
Normal file
29
src/wizard/offline_tx_signing/PageOTS_ImportUnsignedTx.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
|
||||||
|
|
||||||
|
#ifndef FEATHER_PAGEOTS_IMPORTUNSIGNEDTX_H
|
||||||
|
#define FEATHER_PAGEOTS_IMPORTUNSIGNEDTX_H
|
||||||
|
|
||||||
|
#include <QWizardPage>
|
||||||
|
#include "Wallet.h"
|
||||||
|
#include "OfflineTxSigningWizard.h"
|
||||||
|
#include "PageOTS_Import.h"
|
||||||
|
|
||||||
|
namespace Ui {
|
||||||
|
class PageOTS_Import;
|
||||||
|
}
|
||||||
|
|
||||||
|
class PageOTS_ImportUnsignedTx : public PageOTS_Import
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit PageOTS_ImportUnsignedTx(QWidget *parent, Wallet *wallet, TxWizardFields *wizardFields);
|
||||||
|
[[nodiscard]] int nextId() const override;
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void importFromStr(const std::string &data) override;
|
||||||
|
void importFromFile() override;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif //FEATHER_PAGEOTS_IMPORTUNSIGNEDTX_H
|
Loading…
Reference in a new issue