Multi destination transactions

This commit is contained in:
tobtoht 2021-01-27 00:55:27 +01:00
parent 9fc77f3bc9
commit 045d9ec2d2
No known key found for this signature in database
GPG key ID: 1CADD27F41F45C3C
18 changed files with 411 additions and 45 deletions

View file

@ -178,7 +178,7 @@ void AppContext::initWS() {
this->ws->start(); this->ws->start();
} }
void AppContext::onCancelTransaction(PendingTransaction *tx, const QString &address) { void AppContext::onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address) {
// tx cancelled by user // tx cancelled by user
double amount = tx->amount() / globals::cdiv; double amount = tx->amount() / globals::cdiv;
emit createTransactionCancelled(address, amount); emit createTransactionCancelled(address, amount);
@ -239,6 +239,30 @@ void AppContext::onCreateTransaction(const QString &address, quint64 amount, con
emit initiateTransaction(); emit initiateTransaction();
} }
void AppContext::onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &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) { void AppContext::onCreateTransactionError(const QString &msg) {
this->tmpTxDescription = ""; this->tmpTxDescription = "";
emit endTransaction(); 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) { void AppContext::onTransactionCreated(PendingTransaction *tx, const QVector<QString> &address) {
if(address == this->donationAddress) for (auto &addr : address) {
this->donationSending = true; if (addr == this->donationAddress) {
this->donationSending = true;
}
}
// Let UI know that the transaction was constructed // Let UI know that the transaction was constructed
emit endTransaction(); emit endTransaction();
// tx created, but not sent yet. ask user to verify first. // tx created, but not sent yet. ask user to verify first.
emit createTransactionSuccess(tx, address, mixin); emit createTransactionSuccess(tx, address);
} }
void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){ void AppContext::onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid){

View file

@ -115,7 +115,8 @@ public slots:
void onOpenWallet(const QString& path, const QString &password); void onOpenWallet(const QString& path, const QString &password);
void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all); void onCreateTransaction(const QString &address, quint64 amount, const QString &description, bool all);
void onCreateTransaction(XmrToOrder *order); void onCreateTransaction(XmrToOrder *order);
void onCancelTransaction(PendingTransaction *tx, const QString &address); void onCreateTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
void onCancelTransaction(PendingTransaction *tx, const QVector<QString> &address);
void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const; void onSweepOutput(const QString &keyImage, QString address, bool churn, int outputs) const;
void onCreateTransactionError(const QString &msg); void onCreateTransactionError(const QString &msg);
void onOpenAliasResolve(const QString &openAlias); void onOpenAliasResolve(const QString &openAlias);
@ -136,7 +137,7 @@ private slots:
void onWalletOpened(Wallet *wallet); void onWalletOpened(Wallet *wallet);
void onWalletNewBlock(quint64 blockheight, quint64 targetHeight); void onWalletNewBlock(quint64 blockheight, quint64 targetHeight);
void onHeightRefreshed(quint64 walletHeight, quint64 daemonHeight, 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<QString> &address);
void onTransactionCommitted(bool status, PendingTransaction *t, const QStringList& txid); void onTransactionCommitted(bool status, PendingTransaction *t, const QStringList& txid);
signals: signals:
@ -159,8 +160,8 @@ signals:
void walletOpenPasswordNeeded(bool invalidPassword, QString path); void walletOpenPasswordNeeded(bool invalidPassword, QString path);
void transactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); void transactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid);
void createTransactionError(QString message); void createTransactionError(QString message);
void createTransactionCancelled(QString address, double amount); void createTransactionCancelled(const QVector<QString> &address, double amount);
void createTransactionSuccess(PendingTransaction *tx, const QString &address, const quint32 &mixin); void createTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void redditUpdated(QList<QSharedPointer<RedditPost>> &posts); void redditUpdated(QList<QSharedPointer<RedditPost>> &posts);
void nodesUpdated(QList<QSharedPointer<FeatherNode>> &nodes); void nodesUpdated(QList<QSharedPointer<FeatherNode>> &nodes);
void ccsUpdated(QList<QSharedPointer<CCSEntry>> &entries); void ccsUpdated(QList<QSharedPointer<CCSEntry>> &entries);

View file

@ -185,17 +185,11 @@
<item> <item>
<widget class="QTextEdit" name="outputs"> <widget class="QTextEdit" name="outputs">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Maximum"> <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
</property> </property>
<property name="maximumSize">
<size>
<width>16777215</width>
<height>100</height>
</size>
</property>
<property name="readOnly"> <property name="readOnly">
<bool>true</bool> <bool>true</bool>
</property> </property>

View file

@ -9,14 +9,13 @@
#include <QMessageBox> #include <QMessageBox>
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) : QDialog(parent)
, ui(new Ui::TxConfDialog) , ui(new Ui::TxConfDialog)
, m_ctx(ctx) , m_ctx(ctx)
, m_tx(tx) , m_tx(tx)
, m_address(address) , m_address(address)
, m_description(description) , m_description(description)
, m_mixin(mixin)
{ {
ui->setupUi(this); ui->setupUi(this);

View file

@ -18,7 +18,7 @@ class TxConfDialog : public QDialog
Q_OBJECT Q_OBJECT
public: 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; ~TxConfDialog() override;
bool showAdvanced = false; bool showAdvanced = false;
@ -31,7 +31,6 @@ private:
PendingTransaction *m_tx; PendingTransaction *m_tx;
QString m_address; QString m_address;
QString m_description; QString m_description;
int m_mixin;
}; };
#endif //FEATHER_TXCONFDIALOG_H #endif //FEATHER_TXCONFDIALOG_H

View file

@ -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] { m_scheduler.run([this, dst_addr, payment_id, amount, mixin_count, priority] {
PendingTransaction *tx = createTransaction(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<QString> address {dst_addr};
emit transactionCreated(tx, address);
});
}
PendingTransaction* Wallet::createTransactionMultiDest(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority)
{
std::vector<std::string> dests;
for (auto &addr : dst_addr) {
dests.push_back(addr.toStdString());
}
std::vector<uint64_t> amounts;
for (auto &a : amount) {
amounts.push_back(a);
}
// TODO: remove mixin count
Monero::PendingTransaction * ptImpl = m_walletImpl->createTransactionMultDest(dests, "", amounts, 11, static_cast<Monero::PendingTransaction::Priority>(priority));
PendingTransaction * result = new PendingTransaction(ptImpl);
return result;
}
void Wallet::createTransactionMultiDestAsync(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority)
{
m_scheduler.run([this, dst_addr, amount, priority] {
PendingTransaction *tx = createTransactionMultiDest(dst_addr, amount, priority);
QVector<QString> 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] { m_scheduler.run([this, dst_addr, payment_id, mixin_count, priority] {
PendingTransaction *tx = createTransactionAll(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<QString> 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] { m_scheduler.run([this, key_image, dst_addr, outputs, priority] {
PendingTransaction *tx = createTransactionSingle(key_image, dst_addr, outputs, priority); PendingTransaction *tx = createTransactionSingle(key_image, dst_addr, outputs, priority);
emit transactionCreated(tx, dst_addr, "", 10); // todo: return true mixincount QVector<QString> address {dst_addr};
emit transactionCreated(tx, address);
}); });
} }
@ -645,7 +680,8 @@ void Wallet::createSweepUnmixableTransactionAsync()
{ {
m_scheduler.run([this] { m_scheduler.run([this] {
PendingTransaction *tx = createSweepUnmixableTransaction(); PendingTransaction *tx = createSweepUnmixableTransaction();
emit transactionCreated(tx, "", "", 0); QVector<QString> address {""};
emit transactionCreated(tx, address);
}); });
} }

View file

@ -266,6 +266,15 @@ public:
quint64 amount, quint32 mixin_count, quint64 amount, quint32 mixin_count,
PendingTransaction::Priority priority); PendingTransaction::Priority priority);
//! creates multi-destination transaction
Q_INVOKABLE PendingTransaction * createTransactionMultiDest(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority);
//! creates async multi-destination transaction
Q_INVOKABLE void createTransactionMultiDestAsync(const QVector<QString> &dst_addr, const QVector<quint64> &amount,
PendingTransaction::Priority priority);
//! creates transaction with all outputs //! creates transaction with all outputs
Q_INVOKABLE PendingTransaction * createTransactionAll(const QString &dst_addr, const QString &payment_id, Q_INVOKABLE PendingTransaction * createTransactionAll(const QString &dst_addr, const QString &payment_id,
quint32 mixin_count, PendingTransaction::Priority priority); quint32 mixin_count, PendingTransaction::Priority priority);
@ -449,7 +458,7 @@ signals:
void deviceShowAddressShowed(); void deviceShowAddressShowed();
// emitted when transaction is created async // emitted when transaction is created async
void transactionCreated(PendingTransaction * transaction, QString address, QString paymentId, quint32 mixinCount); void transactionCreated(PendingTransaction * transaction, QVector<QString> address);
void connectionStatusChanged(int status) const; void connectionStatusChanged(int status) const;
void currentSubaddressAccountChanged() const; void currentSubaddressAccountChanged() const;

View file

@ -161,6 +161,7 @@ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
#endif #endif
qInstallMessageHandler(Utils::applicationLogHandler); qInstallMessageHandler(Utils::applicationLogHandler);
qRegisterMetaType<QVector<QString>>();
auto *mainWindow = new MainWindow(ctx); auto *mainWindow = new MainWindow(ctx);
return QApplication::exec(); return QApplication::exec();

View file

@ -74,6 +74,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::menuQuitClicked); connect(ui->actionQuit, &QAction::triggered, this, &MainWindow::menuQuitClicked);
connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::menuSettingsClicked); connect(ui->actionSettings, &QAction::triggered, this, &MainWindow::menuSettingsClicked);
connect(ui->actionCalculator, &QAction::triggered, this, &MainWindow::showCalcWindow); 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); connect(ui->actionWallet_cache_debug, &QAction::triggered, this, &MainWindow::showWalletCacheDebugDialog);
#if defined(Q_OS_WIN) || defined(Q_OS_MACOS) #if defined(Q_OS_WIN) || defined(Q_OS_MACOS)
@ -153,6 +154,7 @@ MainWindow::MainWindow(AppContext *ctx, QWidget *parent) :
// Send widget // Send widget
connect(ui->sendWidget, &SendWidget::createTransaction, m_ctx, QOverload<const QString &, quint64, const QString &, bool>::of(&AppContext::onCreateTransaction)); connect(ui->sendWidget, &SendWidget::createTransaction, m_ctx, QOverload<const QString &, quint64, const QString &, bool>::of(&AppContext::onCreateTransaction));
connect(ui->sendWidget, &SendWidget::createTransactionMultiDest, m_ctx, &AppContext::onCreateTransactionMultiDest);
// Nodes // Nodes
connect(m_ctx->nodes, &Nodes::nodeExhausted, this, &MainWindow::showNodeExhaustedMessage); connect(m_ctx->nodes, &Nodes::nodeExhausted, this, &MainWindow::showNodeExhaustedMessage);
@ -737,7 +739,7 @@ void MainWindow::onConnectionStatusChanged(int status)
m_statusBtnConnectionStatusIndicator->setIcon(QIcon(statusIcon)); m_statusBtnConnectionStatusIndicator->setIcon(QIcon(statusIcon));
} }
void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QString &address, const quint32 &mixin) { void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address) {
auto tx_status = tx->status(); auto tx_status = tx->status();
auto err = QString("Can't create transaction: "); auto err = QString("Can't create transaction: ");
@ -761,7 +763,16 @@ void MainWindow::onCreateTransactionSuccess(PendingTransaction *tx, const QStrin
} else { } else {
const auto &description = m_ctx->tmpTxDescription; const auto &description = m_ctx->tmpTxDescription;
auto *dialog = new TxConfDialog(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()) { switch (dialog->exec()) {
case QDialog::Rejected: case QDialog::Rejected:
{ {
@ -1061,6 +1072,15 @@ void MainWindow::showCalcWindow() {
m_windowCalc->show(); 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) { void MainWindow::showSendScreen(const CCSEntry &entry) {
ui->sendWidget->fill(entry); ui->sendWidget->fill(entry);
ui->tabWidget->setCurrentIndex(Tabs::SEND); ui->tabWidget->setCurrentIndex(Tabs::SEND);

View file

@ -104,6 +104,7 @@ public slots:
void showViewOnlyDialog(); void showViewOnlyDialog();
void donateButtonClicked(); void donateButtonClicked();
void showCalcWindow(); void showCalcWindow();
void payToMany();
void showWalletCacheDebugDialog(); void showWalletCacheDebugDialog();
void showSendTab(); void showSendTab();
void showHistoryTab(); void showHistoryTab();
@ -141,7 +142,7 @@ public slots:
void onWalletClosed(WalletWizard::Page page = WalletWizard::Page_Menu); void onWalletClosed(WalletWizard::Page page = WalletWizard::Page_Menu);
void onConnectionStatusChanged(int status); void onConnectionStatusChanged(int status);
void onCreateTransactionError(const QString &message); void onCreateTransactionError(const QString &message);
void onCreateTransactionSuccess(PendingTransaction *tx, const QString &address, const quint32 &mixin); void onCreateTransactionSuccess(PendingTransaction *tx, const QVector<QString> &address);
void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid);
signals: signals:

View file

@ -190,7 +190,7 @@
<item> <item>
<widget class="SendWidget" name="sendWidget" native="true"> <widget class="SendWidget" name="sendWidget" native="true">
<property name="sizePolicy"> <property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred"> <sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch> <horstretch>0</horstretch>
<verstretch>0</verstretch> <verstretch>0</verstretch>
</sizepolicy> </sizepolicy>
@ -446,6 +446,7 @@
<addaction name="menuLoad_signed_transaction"/> <addaction name="menuLoad_signed_transaction"/>
<addaction name="actionImport_transaction"/> <addaction name="actionImport_transaction"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="actionPay_to_many"/>
<addaction name="actionCalculator"/> <addaction name="actionCalculator"/>
<addaction name="actionCreateDesktopEntry"/> <addaction name="actionCreateDesktopEntry"/>
</widget> </widget>
@ -726,6 +727,11 @@
<string>Wallet cache debug</string> <string>Wallet cache debug</string>
</property> </property>
</action> </action>
<action name="actionPay_to_many">
<property name="text">
<string>Pay to many</string>
</property>
</action>
</widget> </widget>
<layoutdefault spacing="6" margin="11"/> <layoutdefault spacing="6" margin="11"/>
<customwidgets> <customwidgets>

View file

@ -24,8 +24,8 @@ SendWidget::SendWidget(QWidget *parent) :
connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked); connect(ui->btnClear, &QPushButton::clicked, this, &SendWidget::clearClicked);
connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked); connect(ui->btnMax, &QPushButton::clicked, this, &SendWidget::btnMaxClicked);
connect(ui->comboCurrencySelection, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SendWidget::currencyComboChanged); connect(ui->comboCurrencySelection, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SendWidget::currencyComboChanged);
connect(ui->lineAmount, &QLineEdit::textEdited, this, &SendWidget::amountEdited); connect(ui->lineAmount, &QLineEdit::textChanged, this, &SendWidget::amountEdited);
connect(ui->lineAddress, &QLineEdit::textEdited, this, &SendWidget::addressEdited); connect(ui->lineAddress, &QPlainTextEdit::textChanged, this, &SendWidget::addressEdited);
connect(ui->btn_openAlias, &QPushButton::clicked, this, &SendWidget::aliasClicked); connect(ui->btn_openAlias, &QPushButton::clicked, this, &SendWidget::aliasClicked);
ui->label_conversionAmount->setText(""); ui->label_conversionAmount->setText("");
ui->label_conversionAmount->hide(); 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" "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."); "To send all your balance, click the Max button to the right.");
ui->lineAddress->setNetType(m_ctx->networkType);
this->setupComboBox(); this->setupComboBox();
} }
@ -50,8 +51,22 @@ void SendWidget::currencyComboChanged(int index) {
this->amountEdited(amount); this->amountEdited(amount);
} }
void SendWidget::addressEdited(const QString &text) { void SendWidget::addressEdited() {
text.contains(".") ? ui->btn_openAlias->show() : ui->btn_openAlias->hide(); QVector<PartialTxOutput> 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) { 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) { void SendWidget::fill(const QString &address, const QString &description, double amount) {
ui->lineDescription->setText(description); ui->lineDescription->setText(description);
ui->lineAddress->setText(address); ui->lineAddress->setText(address);
ui->lineAddress->setCursorPosition(0);
ui->lineAddress->moveCursor(QTextCursor::Start);
if (amount > 0) if (amount > 0)
ui->lineAmount->setText(QString::number(amount)); ui->lineAmount->setText(QString::number(amount));
this->updateConversionLabel(); this->updateConversionLabel();
@ -77,7 +94,7 @@ void SendWidget::fill(const QString &address, const QString &description, double
void SendWidget::fillAddress(const QString &address) { void SendWidget::fillAddress(const QString &address) {
ui->lineAddress->setText(address); ui->lineAddress->setText(address);
ui->lineAddress->setCursorPosition(0); ui->lineAddress->moveCursor(QTextCursor::Start);
} }
void SendWidget::sendClicked() { void SendWidget::sendClicked() {
@ -96,6 +113,35 @@ void SendWidget::sendClicked() {
return; return;
} }
QVector<PartialTxOutput> outputs = ui->lineAddress->getOutputs();
QVector<PayToLineError> 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<QString> addresses;
QVector<quint64> amounts;
for (auto &output : outputs) {
addresses.push_back(output.address);
amounts.push_back(output.amount);
}
emit createTransactionMultiDest(addresses, amounts, description);
return;
}
quint64 amount; quint64 amount;
if (currency == "XMR") { if (currency == "XMR") {
amount = this->amount(); amount = this->amount();
@ -193,6 +239,10 @@ void SendWidget::clearFields() {
ui->label_conversionAmount->clear(); ui->label_conversionAmount->clear();
} }
void SendWidget::payToMany() {
ui->lineAddress->payToMany();
}
void SendWidget::onWalletClosed() { void SendWidget::onWalletClosed() {
this->clearFields(); this->clearFields();
ui->btnSend->setEnabled(true); ui->btnSend->setEnabled(true);

View file

@ -22,6 +22,7 @@ public:
void fill(const QString &address, const QString& description, double amount = 0); void fill(const QString &address, const QString& description, double amount = 0);
void fill(double amount); void fill(double amount);
void clearFields(); void clearFields();
void payToMany();
~SendWidget() override; ~SendWidget() override;
public slots: public slots:
@ -30,7 +31,7 @@ public slots:
void aliasClicked(); void aliasClicked();
void btnMaxClicked(); void btnMaxClicked();
void amountEdited(const QString &text); void amountEdited(const QString &text);
void addressEdited(const QString &text); void addressEdited();
void currencyComboChanged(int index); void currencyComboChanged(int index);
void fillAddress(const QString &address); void fillAddress(const QString &address);
void updateConversionLabel(); void updateConversionLabel();
@ -45,6 +46,7 @@ public slots:
signals: signals:
void resolveOpenAlias(const QString &address); void resolveOpenAlias(const QString &address);
void createTransaction(const QString &address, quint64 amount, const QString &description, bool all); void createTransaction(const QString &address, quint64 amount, const QString &description, bool all);
void createTransactionMultiDest(const QVector<QString> &addresses, const QVector<quint64> &amounts, const QString &description);
private: private:
void setupComboBox(); void setupComboBox();

View file

@ -7,7 +7,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>647</width> <width>647</width>
<height>175</height> <height>231</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -46,11 +46,7 @@
</widget> </widget>
</item> </item>
<item row="0" column="1"> <item row="0" column="1">
<widget class="QLineEdit" name="lineAddress"> <widget class="PayToEdit" name="lineAddress"/>
<property name="text">
<string/>
</property>
</widget>
</item> </item>
<item row="1" column="0"> <item row="1" column="0">
<widget class="HelpLabel" name="label_Description"> <widget class="HelpLabel" name="label_Description">
@ -190,6 +186,11 @@
<extends>QLabel</extends> <extends>QLabel</extends>
<header>components.h</header> <header>components.h</header>
</customwidget> </customwidget>
<customwidget>
<class>PayToEdit</class>
<extends>QPlainTextEdit</extends>
<header>widgets/PayToEdit.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources/> <resources/>
<connections/> <connections/>

View file

@ -24,9 +24,9 @@ XmrToOrder::XmrToOrder(AppContext *ctx, UtilsNetworking *network, QString baseUr
connect(m_ctx, &AppContext::createTransactionCancelled, this, &XmrToOrder::onTransactionCancelled); connect(m_ctx, &AppContext::createTransactionCancelled, this, &XmrToOrder::onTransactionCancelled);
} }
void XmrToOrder::onTransactionCancelled(const QString &address, double amount) { void XmrToOrder::onTransactionCancelled(const QVector<QString> &address, double amount) {
// listener for all cancelled transactions - will try to match the exact amount to this order. // 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->errorMsg = "TX cancelled by user";
this->changeState(OrderState::Status_OrderFailed); this->changeState(OrderState::Status_OrderFailed);

View file

@ -66,7 +66,7 @@ public:
public slots: public slots:
void onCountdown(); void onCountdown();
void onTransactionCancelled(const QString &address, double amount); void onTransactionCancelled(const QVector<QString> &address, double amount);
void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid); void onTransactionCommitted(bool status, PendingTransaction *tx, const QStringList& txid);
void onCreatedError(); void onCreatedError();

150
src/widgets/PayToEdit.cpp Normal file
View file

@ -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 <QtGlobal>
#include <QScrollBar>
#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<PayToLineError> PayToEdit::getErrors() {
return m_errors;
}
QVector<PartialTxOutput> 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;
}
}

70
src/widgets/PayToEdit.h Normal file
View file

@ -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 <QObject>
#include <QPlainTextEdit>
#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<PayToLineError> getErrors();
QVector<PartialTxOutput> 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<PayToLineError> m_errors;
QVector<PartialTxOutput> m_outputs;
};
#endif //FEATHER_PAYTOEDIT_H