From 045d9ec2d2b7ead102ce3023122e62e89101afe6 Mon Sep 17 00:00:00 2001 From: tobtoht Date: Wed, 27 Jan 2021 00:55:27 +0100 Subject: [PATCH] Multi destination transactions --- src/appcontext.cpp | 37 +++++++-- src/appcontext.h | 9 +- src/dialog/txconfadvdialog.ui | 8 +- src/dialog/txconfdialog.cpp | 3 +- src/dialog/txconfdialog.h | 3 +- src/libwalletqt/Wallet.cpp | 44 +++++++++- src/libwalletqt/Wallet.h | 11 ++- src/main.cpp | 1 + src/mainwindow.cpp | 24 +++++- src/mainwindow.h | 3 +- src/mainwindow.ui | 8 +- src/sendwidget.cpp | 62 ++++++++++++-- src/sendwidget.h | 4 +- src/sendwidget.ui | 13 +-- src/utils/xmrtoorder.cpp | 4 +- src/utils/xmrtoorder.h | 2 +- src/widgets/PayToEdit.cpp | 150 ++++++++++++++++++++++++++++++++++ src/widgets/PayToEdit.h | 70 ++++++++++++++++ 18 files changed, 411 insertions(+), 45 deletions(-) create mode 100644 src/widgets/PayToEdit.cpp create mode 100644 src/widgets/PayToEdit.h diff --git a/src/appcontext.cpp b/src/appcontext.cpp index dd91c00..e0fa022 100644 --- a/src/appcontext.cpp +++ b/src/appcontext.cpp @@ -178,7 +178,7 @@ void AppContext::initWS() { this->ws->start(); } -void AppContext::onCancelTransaction(PendingTransaction *tx, const QString &address) { +void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector &address) { // tx cancelled by user double amount = tx->amount() / globals::cdiv; emit createTransactionCancelled(address, amount); @@ -239,6 +239,30 @@ void AppContext::onCreateTransaction(const QString &address, quint64 amount, con emit initiateTransaction(); } +void AppContext::onCreateTransactionMultiDest(const QVector &addresses, const QVector &amounts, const QString &description) { + this->tmpTxDescription = description; + + if (this->currentWallet == nullptr) { + emit createTransactionError("Cannot create transaction; no wallet loaded"); + return; + } + + quint64 total_amount = 0; + for (auto &amount : amounts) { + total_amount += amount; + } + + auto unlocked_balance = this->currentWallet->unlockedBalance(); + if (total_amount > unlocked_balance) { + emit createTransactionError("Not enough money to spend"); + } + + qDebug() << "Creating tx"; + this->currentWallet->createTransactionMultiDestAsync(addresses, amounts, this->tx_priority); + + emit initiateTransaction(); +} + void AppContext::onCreateTransactionError(const QString &msg) { this->tmpTxDescription = ""; emit endTransaction(); @@ -750,15 +774,18 @@ void AppContext::onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, q } } -void AppContext::onTransactionCreated(PendingTransaction *tx, const QString &address, const QString &paymentId, quint32 mixin) { - if(address == this->donationAddress) - this->donationSending = true; +void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector &address) { + for (auto &addr : address) { + if (addr == this->donationAddress) { + this->donationSending = true; + } + } // Let UI know that the transaction was constructed emit endTransaction(); // tx created, but not sent yet. ask user to verify first. - emit createTransactionSuccess(tx, address, mixin); + emit createTransactionSuccess(tx, address); } void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){ diff --git a/src/appcontext.h b/src/appcontext.h index 68d0d82..f971129 100644 --- a/src/appcontext.h +++ b/src/appcontext.h @@ -115,7 +115,8 @@ public slots: void onOpenWallet(const QString& path, const QString &password); void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all); void onCreateTransaction(XmrToOrder *order); - void onCancelTransaction(PendingTransaction *tx, const QString &address); + void onCreateTransactionMultiDest(const QVector &addresses, const QVector &amounts, const QString &description); + void onCancelTransaction(PendingTransaction *tx, const QVector &address); void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const; void onCreateTransactionError(const QString &msg); void onOpenAliasResolve(const QString &openAlias); @@ -136,7 +137,7 @@ private slots: void onWalletOpened(Wallet *wallet); void onWalletNewBlock(quint64 blockheight, quint64 targetHeight); void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, quint64 targetHeight); - void onTransactionCreated(PendingTransaction *tx, const QString &address, const QString &paymentId, quint32 mixin); + void onTransactionCreated(PendingTransaction *tx, const QVector &address); void onTransactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); signals: @@ -159,8 +160,8 @@ signals: void walletOpenPasswordNeeded(bool invalidPassword, QString path); void transactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); void createTransactionError(QString message); - void createTransactionCancelled(QString address, double amount); - void createTransactionSuccess(PendingTransaction *tx, const QString &address, const quint32 &mixin); + void createTransactionCancelled(const QVector &address, double amount); + void createTransactionSuccess(PendingTransaction *tx, const QVector &address); void redditUpdated(QList> &posts); void nodesUpdated(QList> &nodes); void ccsUpdated(QList> &entries); diff --git a/src/dialog/txconfadvdialog.ui b/src/dialog/txconfadvdialog.ui index 86df777..38f3cbc 100644 --- a/src/dialog/txconfadvdialog.ui +++ b/src/dialog/txconfadvdialog.ui @@ -185,17 +185,11 @@ - + 0 0 - - - 16777215 - 100 - - true diff --git a/src/dialog/txconfdialog.cpp b/src/dialog/txconfdialog.cpp index 4d8d8a6..b088c5d 100644 --- a/src/dialog/txconfdialog.cpp +++ b/src/dialog/txconfdialog.cpp @@ -9,14 +9,13 @@ #include -TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent) +TxConfDialog::TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent) : QDialog(parent) , ui(new Ui::TxConfDialog) , m_ctx(ctx) , m_tx(tx) , m_address(address) , m_description(description) - , m_mixin(mixin) { ui->setupUi(this); diff --git a/src/dialog/txconfdialog.h b/src/dialog/txconfdialog.h index b4bb7d1..0fdd95a 100644 --- a/src/dialog/txconfdialog.h +++ b/src/dialog/txconfdialog.h @@ -18,7 +18,7 @@ class TxConfDialog : public QDialog Q_OBJECT public: - explicit TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, int mixin, QWidget *parent = nullptr); + explicit TxConfDialog(AppContext *ctx, PendingTransaction *tx, const QString &address, const QString &description, QWidget *parent = nullptr); ~TxConfDialog() override; bool showAdvanced = false; @@ -31,7 +31,6 @@ private: PendingTransaction *m_tx; QString m_address; QString m_description; - int m_mixin; }; #endif //FEATHER_TXCONFDIALOG_H diff --git a/src/libwalletqt/Wallet.cpp b/src/libwalletqt/Wallet.cpp index 7e5816a..c1100ae 100644 --- a/src/libwalletqt/Wallet.cpp +++ b/src/libwalletqt/Wallet.cpp @@ -591,7 +591,40 @@ void Wallet::createTransactionAsync(const QString &dst_addr, const QString &paym { m_scheduler.run([this, dst_addr, payment_id, amount, mixin_count, priority] { PendingTransaction *tx = createTransaction(dst_addr, payment_id, amount, mixin_count, priority); - emit transactionCreated(tx, dst_addr, payment_id, mixin_count); + QVector address {dst_addr}; + emit transactionCreated(tx, address); + }); +} + +PendingTransaction* Wallet::createTransactionMultiDest(const QVector &dst_addr, const QVector &amount, + PendingTransaction::Priority priority) +{ + std::vector dests; + for (auto &addr : dst_addr) { + dests.push_back(addr.toStdString()); + } + + std::vector amounts; + for (auto &a : amount) { + amounts.push_back(a); + } + + // TODO: remove mixin count + Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionMultDest(dests, "", amounts, 11, static_cast(priority)); + PendingTransaction * result = new PendingTransaction(ptImpl); + return result; +} + +void Wallet::createTransactionMultiDestAsync(const QVector &dst_addr, const QVector &amount, + PendingTransaction::Priority priority) +{ + m_scheduler.run([this, dst_addr, amount, priority] { + PendingTransaction *tx = createTransactionMultiDest(dst_addr, amount, priority); + QVector addresses; + for (auto &addr : dst_addr) { + addresses.push_back(addr); + } + emit transactionCreated(tx, addresses); }); } @@ -612,7 +645,8 @@ void Wallet::createTransactionAllAsync(const QString &dst_addr, const QString &p { m_scheduler.run([this, dst_addr, payment_id, mixin_count, priority] { PendingTransaction *tx = createTransactionAll(dst_addr, payment_id, mixin_count, priority); - emit transactionCreated(tx, dst_addr, payment_id, mixin_count); + QVector address {dst_addr}; + emit transactionCreated(tx, address); }); } @@ -630,7 +664,8 @@ void Wallet::createTransactionSingleAsync(const QString &key_image, const QStrin { m_scheduler.run([this, key_image, dst_addr, outputs, priority] { PendingTransaction *tx = createTransactionSingle(key_image, dst_addr, outputs, priority); - emit transactionCreated(tx, dst_addr, "", 10); // todo: return true mixincount + QVector address {dst_addr}; + emit transactionCreated(tx, address); }); } @@ -645,7 +680,8 @@ void Wallet::createSweepUnmixableTransactionAsync() { m_scheduler.run([this] { PendingTransaction *tx = createSweepUnmixableTransaction(); - emit transactionCreated(tx, "", "", 0); + QVector address {""}; + emit transactionCreated(tx, address); }); } diff --git a/src/libwalletqt/Wallet.h b/src/libwalletqt/Wallet.h index b825929..e8f1e19 100644 --- a/src/libwalletqt/Wallet.h +++ b/src/libwalletqt/Wallet.h @@ -266,6 +266,15 @@ public: quint64 amount, quint32 mixin_count, PendingTransaction::Priority priority); + //! creates multi-destination transaction + Q_INVOKABLE PendingTransaction * createTransactionMultiDest(const QVector &dst_addr, const QVector &amount, + PendingTransaction::Priority priority); + + //! creates async multi-destination transaction + Q_INVOKABLE void createTransactionMultiDestAsync(const QVector &dst_addr, const QVector &amount, + PendingTransaction::Priority priority); + + //! creates transaction with all outputs Q_INVOKABLE PendingTransaction * createTransactionAll(const QString &dst_addr, const QString &payment_id, quint32 mixin_count, PendingTransaction::Priority priority); @@ -449,7 +458,7 @@ signals: void deviceShowAddressShowed(); // emitted when transaction is created async - void transactionCreated(PendingTransaction * transaction, QString address, QString paymentId, quint32 mixinCount); + void transactionCreated(PendingTransaction * transaction, QVector address); void connectionStatusChanged(int status) const; void currentSubaddressAccountChanged() const; diff --git a/src/main.cpp b/src/main.cpp index e3db53d..e3d56ba 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -161,6 +161,7 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) { #endif qInstallMessageHandler(Utils::applicationLogHandler); + qRegisterMetaType>(); auto *mainWindow = new MainWindow(ctx); return QApplication::exec(); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index c0b3044..fea615c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -74,6 +74,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) : connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::menuQuitClicked); connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::menuSettingsClicked); connect(ui->actionCalculator, &QAction::triggered, this, &MainWindow::showCalcWindow); + connect(ui->actionPay_to_many, &QAction::triggered, this, &MainWindow::payToMany); connect(ui->actionWallet_cache_debug, &QAction::triggered, this, &MainWindow::showWalletCacheDebugDialog); #if defined(Q_OS_WIN) || defined(Q_OS_MACOS) @@ -153,6 +154,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) : // Send widget connect(ui->sendWidget, &SendWidget::createTransaction, m_ctx, QOverload::of(&AppContext::onCreateTransaction)); + connect(ui->sendWidget, &SendWidget::createTransactionMultiDest, m_ctx, &AppContext::onCreateTransactionMultiDest); // Nodes connect(m_ctx->nodes, &Nodes::nodeExhausted, this, &MainWindow::showNodeExhaustedMessage); @@ -737,7 +739,7 @@ void MainWindow::onConnectionStatusChanged(int status) m_statusBtnConnectionStatusIndicator->setIcon(QIcon(statusIcon)); } -void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QString &address, const quint32 &mixin) { +void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector &address) { auto tx_status = tx->status(); auto err = QString("Can't create transaction: "); @@ -761,7 +763,16 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin } else { const auto &description = m_ctx->tmpTxDescription; - auto *dialog = new TxConfDialog(m_ctx, tx, address, description, mixin, this); + // Show advanced dialog on multi-destination transactions + if (address.size() > 1) { + auto *dialog_adv = new TxConfAdvDialog(m_ctx, description, this); + dialog_adv->setTransaction(tx); + dialog_adv->exec(); + dialog_adv->deleteLater(); + return; + } + + auto *dialog = new TxConfDialog(m_ctx, tx, address[0], description, this); switch (dialog->exec()) { case QDialog::Rejected: { @@ -1061,6 +1072,15 @@ void MainWindow::showCalcWindow() { m_windowCalc->show(); } +void MainWindow::payToMany() { + ui->tabWidget->setCurrentIndex(Tabs::SEND); + ui->sendWidget->payToMany(); + QMessageBox::information(this, "Pay to many", "Enter a list of outputs in the 'Pay to' field.\n" + "One output per line.\n" + "Format: address, amount\n" + "A maximum of 16 addresses may be specified."); +} + void MainWindow::showSendScreen(const CCSEntry &entry) { ui->sendWidget->fill(entry); ui->tabWidget->setCurrentIndex(Tabs::SEND); diff --git a/src/mainwindow.h b/src/mainwindow.h index 4016f22..fc6257c 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -104,6 +104,7 @@ public slots: void showViewOnlyDialog(); void donateButtonClicked(); void showCalcWindow(); + void payToMany(); void showWalletCacheDebugDialog(); void showSendTab(); void showHistoryTab(); @@ -141,7 +142,7 @@ public slots: void onWalletClosed(WalletWizard::Page page = WalletWizard::Page_Menu); void onConnectionStatusChanged(int status); void onCreateTransactionError(const QString &message); - void onCreateTransactionSuccess(PendingTransaction *tx, const QString &address, const quint32 &mixin); + void onCreateTransactionSuccess(PendingTransaction *tx, const QVector &address); void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); signals: diff --git a/src/mainwindow.ui b/src/mainwindow.ui index a1e2b4f..bb51edb 100644 --- a/src/mainwindow.ui +++ b/src/mainwindow.ui @@ -190,7 +190,7 @@ - + 0 0 @@ -446,6 +446,7 @@ + @@ -726,6 +727,11 @@ Wallet cache debug + + + Pay to many + + diff --git a/src/sendwidget.cpp b/src/sendwidget.cpp index 5e0b883..141ea84 100644 --- a/src/sendwidget.cpp +++ b/src/sendwidget.cpp @@ -24,8 +24,8 @@ SendWidget::SendWidget(QWidget *parent) : connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked); connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked); connect(ui->comboCurrencySelection, QOverload::of(&QComboBox::currentIndexChanged), this, &SendWidget::currencyComboChanged); - connect(ui->lineAmount, &QLineEdit::textEdited, this, &SendWidget::amountEdited); - connect(ui->lineAddress, &QLineEdit::textEdited, this, &SendWidget::addressEdited); + connect(ui->lineAmount, &QLineEdit::textChanged, this, &SendWidget::amountEdited); + connect(ui->lineAddress, &QPlainTextEdit::textChanged, this, &SendWidget::addressEdited); connect(ui->btn_openAlias, &QPushButton::clicked, this, &SendWidget::aliasClicked); ui->label_conversionAmount->setText(""); ui->label_conversionAmount->hide(); @@ -41,6 +41,7 @@ SendWidget::SendWidget(QWidget *parent) : "You will be able to review the transaction fee before the transaction is broadcast.\n\n" "To send all your balance, click the Max button to the right."); + ui->lineAddress->setNetType(m_ctx->networkType); this->setupComboBox(); } @@ -50,8 +51,22 @@ void SendWidget::currencyComboChanged(int index) { this->amountEdited(amount); } -void SendWidget::addressEdited(const QString &text) { - text.contains(".") ? ui->btn_openAlias->show() : ui->btn_openAlias->hide(); +void SendWidget::addressEdited() { + QVector outputs = ui->lineAddress->getOutputs(); + + bool freezeAmounts = outputs.size() > 0; + + ui->lineAmount->setReadOnly(freezeAmounts); + ui->lineAmount->setFrame(!freezeAmounts); + ui->btnMax->setDisabled(freezeAmounts); + + if (outputs.size() > 0) { + ui->lineAmount->setText(WalletManager::displayAmount(ui->lineAddress->getTotal())); + } else { + ui->lineAmount->setText(""); + } + + ui->btn_openAlias->setVisible(ui->lineAddress->isOpenAlias()); } void SendWidget::amountEdited(const QString &text) { @@ -69,7 +84,9 @@ void SendWidget::fill(double amount) { void SendWidget::fill(const QString &address, const QString &description, double amount) { ui->lineDescription->setText(description); ui->lineAddress->setText(address); - ui->lineAddress->setCursorPosition(0); + + ui->lineAddress->moveCursor(QTextCursor::Start); + if (amount > 0) ui->lineAmount->setText(QString::number(amount)); this->updateConversionLabel(); @@ -77,7 +94,7 @@ void SendWidget::fill(const QString &address, const QString &description, double void SendWidget::fillAddress(const QString &address) { ui->lineAddress->setText(address); - ui->lineAddress->setCursorPosition(0); + ui->lineAddress->moveCursor(QTextCursor::Start); } void SendWidget::sendClicked() { @@ -96,6 +113,35 @@ void SendWidget::sendClicked() { return; } + QVector outputs = ui->lineAddress->getOutputs(); + QVector errors = ui->lineAddress->getErrors(); + if (errors.size() > 0 && ui->lineAddress->isMultiline()) { + QString errorText; + for (auto &error: errors) { + errorText += QString("Line #%1:\n%2\n").arg(QString::number(error.idx + 1), error.error); + } + + QMessageBox::warning(this, "Warning", QString("Invalid lines found:\n\n%1").arg(errorText)); + return; + } + + if (outputs.size() > 0) { // multi destination transaction + if (outputs.size() > 16) { + QMessageBox::warning(this, "Warning", "Maximum number of outputs (16) exceeded."); + return; + } + + QVector addresses; + QVector amounts; + for (auto &output : outputs) { + addresses.push_back(output.address); + amounts.push_back(output.amount); + } + + emit createTransactionMultiDest(addresses, amounts, description); + return; + } + quint64 amount; if (currency == "XMR") { amount = this->amount(); @@ -193,6 +239,10 @@ void SendWidget::clearFields() { ui->label_conversionAmount->clear(); } +void SendWidget::payToMany() { + ui->lineAddress->payToMany(); +} + void SendWidget::onWalletClosed() { this->clearFields(); ui->btnSend->setEnabled(true); diff --git a/src/sendwidget.h b/src/sendwidget.h index 91bac4b..0821f01 100644 --- a/src/sendwidget.h +++ b/src/sendwidget.h @@ -22,6 +22,7 @@ public: void fill(const QString &address, const QString& description, double amount = 0); void fill(double amount); void clearFields(); + void payToMany(); ~SendWidget() override; public slots: @@ -30,7 +31,7 @@ public slots: void aliasClicked(); void btnMaxClicked(); void amountEdited(const QString &text); - void addressEdited(const QString &text); + void addressEdited(); void currencyComboChanged(int index); void fillAddress(const QString &address); void updateConversionLabel(); @@ -45,6 +46,7 @@ public slots: signals: void resolveOpenAlias(const QString &address); void createTransaction(const QString &address, quint64 amount, const QString &description, bool all); + void createTransactionMultiDest(const QVector &addresses, const QVector &amounts, const QString &description); private: void setupComboBox(); diff --git a/src/sendwidget.ui b/src/sendwidget.ui index 8e5464e..b5b3810 100644 --- a/src/sendwidget.ui +++ b/src/sendwidget.ui @@ -7,7 +7,7 @@ 0 0 647 - 175 + 231 @@ -46,11 +46,7 @@ - - - - - + @@ -190,6 +186,11 @@ QLabel
components.h
+ + PayToEdit + QPlainTextEdit +
widgets/PayToEdit.h
+
diff --git a/src/utils/xmrtoorder.cpp b/src/utils/xmrtoorder.cpp index 75f382a..36011dd 100644 --- a/src/utils/xmrtoorder.cpp +++ b/src/utils/xmrtoorder.cpp @@ -24,9 +24,9 @@ XmrToOrder::XmrToOrder(AppContext *ctx, UtilsNetworking *network, QString baseUr connect(m_ctx, &AppContext::createTransactionCancelled, this, &XmrToOrder::onTransactionCancelled); } -void XmrToOrder::onTransactionCancelled(const QString &address, double amount) { +void XmrToOrder::onTransactionCancelled(const QVector &address, double amount) { // listener for all cancelled transactions - will try to match the exact amount to this order. - if(this->incoming_amount_total != amount || this->receiving_subaddress != address) return; + if(this->incoming_amount_total != amount || this->receiving_subaddress != address[0]) return; this->errorMsg = "TX cancelled by user"; this->changeState(OrderState::Status_OrderFailed); diff --git a/src/utils/xmrtoorder.h b/src/utils/xmrtoorder.h index b3b8dce..ae585d3 100644 --- a/src/utils/xmrtoorder.h +++ b/src/utils/xmrtoorder.h @@ -66,7 +66,7 @@ public: public slots: void onCountdown(); - void onTransactionCancelled(const QString &address, double amount); + void onTransactionCancelled(const QVector &address, double amount); void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); void onCreatedError(); diff --git a/src/widgets/PayToEdit.cpp b/src/widgets/PayToEdit.cpp new file mode 100644 index 0000000..f19a6e3 --- /dev/null +++ b/src/widgets/PayToEdit.cpp @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. +// Copyright (c) 2012 thomasv@gitorious + +#include "PayToEdit.h" + +#include +#include + +#include "utils/utils.h" +#include "model/ModelUtils.h" + +#include "libwalletqt/WalletManager.h" + +PayToEdit::PayToEdit(QWidget *parent) : QPlainTextEdit(parent) +{ + this->setFont(ModelUtils::getMonospaceFont()); + + connect(this->document(), &QTextDocument::contentsChanged, this, &PayToEdit::updateSize); + connect(this, &QPlainTextEdit::textChanged, this, &PayToEdit::checkText); + + this->updateSize(); +} + +void PayToEdit::setNetType(NetworkType::Type netType) { + m_netType = netType; +} + +void PayToEdit::setText(const QString &text) { + this->setPlainText(text); +} + +QString PayToEdit::text() { + return this->toPlainText(); +} + +QVector PayToEdit::getErrors() { + return m_errors; +} + +QVector PayToEdit::getOutputs() { + return m_outputs; +} + +quint64 PayToEdit::getTotal() { + return m_total; +} + +QStringList PayToEdit::lines() { + return this->toPlainText().split("\n"); +} + +bool PayToEdit::isMultiline() { + return this->lines().size() > 1; +} + +void PayToEdit::payToMany() { + this->setPlainText("\n\n\n"); + this->updateSize(); +} + +bool PayToEdit::isOpenAlias() { + if (this->isMultiline()) { + return false; + } + auto text = this->toPlainText().trimmed(); + if (!(text.contains('.') and (!text.contains(' ')))) { + return false; + } + auto parts = text.split(','); + if (parts.size() > 0 and WalletManager::addressValid(parts[0], m_netType)) { + return false; + } + return true; +} + +void PayToEdit::checkText() { + m_errors.clear(); + m_outputs.clear(); + + // filter out empty lines + QStringList lines; + for (auto &l : this->lines()) { + if (!l.isEmpty()) { + lines.push_back(l); + } + } + + this->parseAsMultiline(lines); +} + +void PayToEdit::updateSize() { + qreal lineHeight = QFontMetrics(this->document()->defaultFont()).height(); + qreal docHeight = this->document()->size().height(); + int h = int(docHeight * lineHeight + 11); + h = qMin(qMax(h, m_heightMin), m_heightMax); + this->setMinimumHeight(h); + this->setMaximumHeight(h); + this->verticalScrollBar()->hide(); +} + +PartialTxOutput PayToEdit::parseAddressAndAmount(const QString &line) { + QStringList x = line.split(","); + if (x.size() != 2) { + return PartialTxOutput(); + } + + QString address = this->parseAddress(x[0]); + quint64 amount = this->parseAmount(x[1]); + + return PartialTxOutput(address, amount); +} + +quint64 PayToEdit::parseAmount(QString amount) { + amount.replace(',', '.'); + if (amount.isEmpty()) return 0; + + return WalletManager::amountFromString(amount.trimmed()); +} + +QString PayToEdit::parseAddress(QString address) { + if (!WalletManager::addressValid(address.trimmed(), m_netType)) { + return ""; + } + return address; +} + +void PayToEdit::parseAsMultiline(const QStringList &lines) { + m_outputs.clear(); + m_total = 0; + + int i = 0; + for (auto &line : lines) { + PartialTxOutput output = this->parseAddressAndAmount(line); + if (output.address.isEmpty() && output.amount == 0) { + m_errors.append(PayToLineError(line, "Expected two comma-separated values: (address, amount)", i, true)); + continue; + } else if (output.address.isEmpty()) { + m_errors.append(PayToLineError(line, "Invalid address", i, true)); + continue; + } else if (output.amount == 0) { + m_errors.append(PayToLineError(line, "Invalid amount", i, true)); + continue; + } + + m_outputs.append(output); + m_total += output.amount; + i += 1; + } +} \ No newline at end of file diff --git a/src/widgets/PayToEdit.h b/src/widgets/PayToEdit.h new file mode 100644 index 0000000..78ffd23 --- /dev/null +++ b/src/widgets/PayToEdit.h @@ -0,0 +1,70 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2020-2021, The Monero Project. +// Copyright (c) 2012 thomasv@gitorious + +#ifndef FEATHER_PAYTOEDIT_H +#define FEATHER_PAYTOEDIT_H + +#include +#include + +#include "utils/utils.h" + +struct PartialTxOutput { + explicit PartialTxOutput(QString address = "", quint64 amount = 0) + : address(address), amount(amount) {} + + QString address; + quint64 amount; +}; + +struct PayToLineError { + explicit PayToLineError(QString lineContent, QString error, int idx = 0, bool isMultiline = false) + : lineContent(lineContent), error(error), idx(idx), isMultiline(isMultiline) {} + + QString lineContent; + QString error; + int idx; + bool isMultiline; +}; + +class PayToEdit : public QPlainTextEdit +{ +Q_OBJECT + +public: + explicit PayToEdit(QWidget *parent = nullptr); + + void setNetType(NetworkType::Type netType); + void setText(const QString &text); + QString text(); + + QVector getErrors(); + QVector getOutputs(); + quint64 getTotal(); + + QStringList lines(); + bool isMultiline(); + void payToMany(); + bool isOpenAlias(); + +private: + void checkText(); + void updateSize(); + + PartialTxOutput parseAddressAndAmount(const QString &line); + quint64 parseAmount(QString amount); + QString parseAddress(QString address); + + void parseAsMultiline(const QStringList &lines); + + int m_heightMin = 0; + int m_heightMax = 150; + quint64 m_total = 0; + NetworkType::Type m_netType = NetworkType::Type::MAINNET; + + QVector m_errors; + QVector m_outputs; +}; + +#endif //FEATHER_PAYTOEDIT_H