Hush lite wallet https://faq.hush.is/sdl
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

995 lines
41 KiB

// Copyright 2019-2024 The Hush developers
3 years ago
// Released under the GPLv3
6 years ago
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "addressbook.h"
6 years ago
#include "ui_confirm.h"
6 years ago
#include "ui_memodialog.h"
#include "ui_newrecurring.h"
6 years ago
#include "settings.h"
#include "controller.h"
#include "recurring.h"
6 years ago
using json = nlohmann::json;
6 years ago
void MainWindow::setupSendTab() {
6 years ago
// Create the validator for send to/amount fields
amtValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}"));
6 years ago
ui->Amount1->setValidator(amtValidator);
6 years ago
6 years ago
// Send button
QObject::connect(ui->sendTransactionButton, &QPushButton::clicked, this, &MainWindow::sendButton);
6 years ago
6 years ago
// Cancel Button
QObject::connect(ui->cancelSendButton, &QPushButton::clicked, this, &MainWindow::cancelButton);
6 years ago
6 years ago
// Hook up add address button click
QObject::connect(ui->addAddressButton, &QPushButton::clicked, this, &MainWindow::addAddressSection);
6 years ago
6 years ago
// Max available Checkbox
QObject::connect(ui->Max1, &QCheckBox::stateChanged, this, &MainWindow::maxAmountChecked);
6 years ago
//Custom Fee Checkbox
QObject::connect(ui->customFee, &QCheckBox::stateChanged, this, &MainWindow::toggleMinerFeeEditable);
// The first Address button
QObject::connect(ui->Address1, &QLineEdit::textChanged, [=] (auto text) {
this->addressChanged(1, text);
});
// The first Memo button
QObject::connect(ui->MemoBtn1, &QPushButton::clicked, [=] () {
this->memoButtonClicked(1);
});
setMemoEnabled(1, false);
// This is the damnest thing ever. If we do AddressBook::readFromStorage() directly, the whole file
// doesn't get read. It needs to run in a timer after everything has finished to be able to read
// the file properly.
QTimer::singleShot(2000, [=]() { updateLabelsAutoComplete(); });
// The first address book button
QObject::connect(ui->AddressBook1, &QPushButton::clicked, [=] () {
AddressBook::open(this, ui->Address1);
});
// The first Amount button
QObject::connect(ui->Amount1, &QLineEdit::textChanged, [=] (auto text) {
this->amountChanged(1, text);
});
ui->minerFeeAmt->setReadOnly(true);
// Fee amount changed
6 years ago
QObject::connect(ui->minerFeeAmt, &QLineEdit::textChanged, [=](auto txt) {
5 years ago
CAmount fee = CAmount::fromDecimalString(txt);
if (Settings::getInstance()->get_currency_name() == "USD") {
5 years ago
ui->lblMinerFeeUSD->setText(fee.toDecimalUSDString());
} else if (Settings::getInstance()->get_currency_name() == "EUR") {
ui->lblMinerFeeUSD->setText(fee.toDecimalEURString());
} else if (Settings::getInstance()->get_currency_name() == "BTC") {
ui->lblMinerFeeUSD->setText(fee.toDecimalBTCString());
} else if (Settings::getInstance()->get_currency_name() == "CNY") {
ui->lblMinerFeeUSD->setText(fee.toDecimalCNYString());
} else if (Settings::getInstance()->get_currency_name() == "RUB") {
ui->lblMinerFeeUSD->setText(fee.toDecimalRUBString());
} else if (Settings::getInstance()->get_currency_name() == "CAD") {
ui->lblMinerFeeUSD->setText(fee.toDecimalCADString());
} else if (Settings::getInstance()->get_currency_name() == "SGD") {
ui->lblMinerFeeUSD->setText(fee.toDecimalSGDString());
} else if (Settings::getInstance()->get_currency_name() == "CHF") {
ui->lblMinerFeeUSD->setText(fee.toDecimalCHFString());
} else if (Settings::getInstance()->get_currency_name() == "INR") {
ui->lblMinerFeeUSD->setText(fee.toDecimalINRString());
} else if (Settings::getInstance()->get_currency_name() == "GBP") {
ui->lblMinerFeeUSD->setText(fee.toDecimalGBPString());
} else if (Settings::getInstance()->get_currency_name() == "AUD") {
ui->lblMinerFeeUSD->setText(fee.toDecimalAUDString());
}
6 years ago
});
ui->minerFeeAmt->setText(Settings::getMinerFee().toDecimalString());
6 years ago
// Set up focus enter to set fees
QObject::connect(ui->tabWidget, &QTabWidget::currentChanged, [=] (int pos) {
if (pos == 1) {
6 years ago
QString txt = ui->minerFeeAmt->text();
if (Settings::getInstance()->get_currency_name() == "USD") {
5 years ago
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalUSDString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "EUR") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalEURString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "BTC") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalEURString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "CNY") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalCNYString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "RUB") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalRUBString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "CAD") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalCADString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "SGD") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalSGDString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "CHF") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalCHFString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "INR") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalINRString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "GBP") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalGBPString();
ui->lblMinerFeeUSD->setText(feeUSD);
} else if (Settings::getInstance()->get_currency_name() == "AUD") {
QString feeUSD = CAmount::fromDecimalString(txt).toDecimalAUDString();
ui->lblMinerFeeUSD->setText(feeUSD);
}
}
});
//Fees validator
feesValidator = new QRegExpValidator(QRegExp("[0-9]{0,8}\\.?[0-9]{0,8}"));
ui->minerFeeAmt->setValidator(feesValidator);
6 years ago
// Font for the first Memo label
QFont f = ui->Address1->font();
f.setPointSize(f.pointSize() - 1);
ui->MemoTxt1->setFont(f);
// Recurring button
/*QObject::connect(ui->chkRecurring, &QCheckBox::stateChanged, [=] (int checked) {
if (checked) {
ui->btnRecurSchedule->setEnabled(true);
// If this is the first time the button is checked, open the edit schedule dialog
if (sendTxRecurringInfo == nullptr) {
ui->btnRecurSchedule->click();
}
} else {
ui->btnRecurSchedule->setEnabled(false);
ui->lblRecurDesc->setText("");
}
});*/
// Recurring schedule button
// QObject::connect(ui->btnRecurSchedule, &QPushButton::clicked, this, &MainWindow::editSchedule);
// Set the default state for the whole page
clearSendForm();
}
void MainWindow::disableRecurring() {
/* if (!Settings::getInstance()->isTestnet()) {
ui->chkRecurring->setVisible(false);
ui->chkRecurring->setEnabled(false);
ui->btnRecurSchedule->setVisible(false);
ui->btnRecurSchedule->setEnabled(false);
ui->action_Recurring_Payments->setVisible(false);*/
// }
}
void MainWindow::editSchedule() {
// Only on testnet for now
if (!Settings::getInstance()->isTestnet()) {
QMessageBox::critical(this, "Not Supported yet",
"Recurring payments are only supported on Testnet for now.", QMessageBox::Ok);
return;
}
// Check to see that recurring payments are not selected when there are 2 or more addresses
if (ui->sendToWidgets->children().size()-1 > 2) {
QMessageBox::critical(this, tr("Cannot support multiple addresses"),
tr("Recurring payments doesn't currently support multiple addresses"), QMessageBox::Ok);
return;
}
// Open the edit schedule dialog
// auto recurringInfo = Recurring::getInstance()->getNewRecurringFromTx(this, this,
// createTxFromSendPage(), this->sendTxRecurringInfo);
// if (recurringInfo == nullptr) {
// User pressed cancel.
// If there is no existing recurring info, uncheck the recurring box
// if (sendTxRecurringInfo == nullptr) {
// ui->chkRecurring->setCheckState(Qt::Unchecked);
// }
// }
// else {
// delete this->sendTxRecurringInfo;
// this->sendTxRecurringInfo = recurringInfo;
// ui->lblRecurDesc->setText(recurringInfo->getScheduleDescription());
//}
6 years ago
}
void MainWindow::updateLabelsAutoComplete() {
QList<QString> list;
auto labels = AddressBook::getInstance()->getAllAddressLabels();
std::transform(labels.begin(), labels.end(), std::back_inserter(list), [=] (auto la) -> QString {
return la.getName() % "/" % la.getPartnerAddress();
});
delete labelCompleter;
labelCompleter = new QCompleter(list, this);
labelCompleter->setCaseSensitivity(Qt::CaseInsensitive);
// Then, find all the address fields and update the completer.
QRegularExpression re("Address[0-9]+", QRegularExpression::CaseInsensitiveOption);
for (auto target: ui->sendToWidgets->findChildren<QLineEdit *>(re)) {
target->setCompleter(labelCompleter);
}
}
6 years ago
void MainWindow::addAddressSection() {
int itemNumber = ui->sendToWidgets->children().size() - 1;
auto verticalGroupBox = new QGroupBox(ui->sendToWidgets);
verticalGroupBox->setTitle(QString(tr("Recipient ")) % QString::number(itemNumber));
6 years ago
verticalGroupBox->setObjectName(QString("AddressGroupBox") % QString::number(itemNumber));
auto sendAddressLayout = new QVBoxLayout(verticalGroupBox);
sendAddressLayout->setSpacing(6);
sendAddressLayout->setContentsMargins(11, 11, 11, 11);
auto horizontalLayout_12 = new QHBoxLayout();
horizontalLayout_12->setSpacing(6);
auto label_4 = new QLabel(verticalGroupBox);
label_4->setText(tr("Address"));
6 years ago
horizontalLayout_12->addWidget(label_4);
auto Address1 = new QLineEdit(verticalGroupBox);
Address1->setObjectName(QString("Address") % QString::number(itemNumber));
Address1->setPlaceholderText(tr("Address"));
QObject::connect(Address1, &QLineEdit::textChanged, [=] (auto text) {
this->addressChanged(itemNumber, text);
});
Address1->setCompleter(labelCompleter);
6 years ago
horizontalLayout_12->addWidget(Address1);
auto addressBook1 = new QPushButton(verticalGroupBox);
addressBook1->setObjectName(QStringLiteral("AddressBook") % QString::number(itemNumber));
addressBook1->setText(tr("Address Book"));
QObject::connect(addressBook1, &QPushButton::clicked, [=] () {
AddressBook::open(this, Address1);
});
horizontalLayout_12->addWidget(addressBook1);
6 years ago
sendAddressLayout->addLayout(horizontalLayout_12);
auto horizontalLayout_13 = new QHBoxLayout();
horizontalLayout_13->setSpacing(6);
6 years ago
auto label_6 = new QLabel(verticalGroupBox);
label_6->setText(tr("Amount"));
6 years ago
horizontalLayout_13->addWidget(label_6);
auto Amount1 = new QLineEdit(verticalGroupBox);
Amount1->setPlaceholderText(tr("Amount"));
Amount1->setObjectName(QString("Amount") % QString::number(itemNumber));
6 years ago
Amount1->setBaseSize(QSize(200, 0));
Amount1->setAlignment(Qt::AlignRight);
6 years ago
// Create the validator for send to/amount fields
Amount1->setValidator(amtValidator);
QObject::connect(Amount1, &QLineEdit::textChanged, [=] (auto text) {
this->amountChanged(itemNumber, text);
});
6 years ago
horizontalLayout_13->addWidget(Amount1);
auto AmtUSD1 = new QLabel(verticalGroupBox);
AmtUSD1->setObjectName(QString("AmtUSD") % QString::number(itemNumber));
horizontalLayout_13->addWidget(AmtUSD1);
auto horizontalSpacer_4 = new QSpacerItem(40, 20, QSizePolicy::Expanding, QSizePolicy::Minimum);
6 years ago
horizontalLayout_13->addItem(horizontalSpacer_4);
auto MemoBtn1 = new QPushButton(verticalGroupBox);
MemoBtn1->setObjectName(QString("MemoBtn") % QString::number(itemNumber));
MemoBtn1->setText(tr("Memo"));
// Connect Memo Clicked button
QObject::connect(MemoBtn1, &QPushButton::clicked, [=] () {
this->memoButtonClicked(itemNumber);
});
horizontalLayout_13->addWidget(MemoBtn1);
setMemoEnabled(itemNumber, false);
6 years ago
sendAddressLayout->addLayout(horizontalLayout_13);
auto MemoTxt1 = new QLabel(verticalGroupBox);
MemoTxt1->setObjectName(QString("MemoTxt") % QString::number(itemNumber));
QFont font1 = Address1->font();
font1.setPointSize(font1.pointSize()-1);
MemoTxt1->setFont(font1);
MemoTxt1->setWordWrap(true);
sendAddressLayout->addWidget(MemoTxt1);
ui->sendToLayout->insertWidget(itemNumber-1, verticalGroupBox);
6 years ago
// Disable recurring payments if a address section is added, since recurring payments
// aren't supported for more than 1 address
// delete sendTxRecurringInfo;
// sendTxRecurringInfo = nullptr;
// ui->lblRecurDesc->setText("");
// ui->chkRecurring->setChecked(false);
// ui->chkRecurring->setEnabled(false);
// Set focus into the address
Address1->setFocus();
6 years ago
// Delay the call to scroll to allow the scroll window to adjust
QTimer::singleShot(10, [=] () {ui->sendToScrollArea->ensureWidgetVisible(ui->addAddressButton);});
6 years ago
}
void MainWindow::addressChanged(int itemNumber, const QString& text) {
auto addr = AddressBook::addressFromAddressLabel(text);
setMemoEnabled(itemNumber, Settings::isZAddress(addr));
}
void MainWindow::amountChanged(int item, const QString& text) {
auto usd = ui->sendToWidgets->findChild<QLabel*>(QString("AmtUSD") % QString::number(item));
5 years ago
CAmount amt = CAmount::fromDecimalString(text);
if (Settings::getInstance()->get_currency_name() == "USD") {
5 years ago
usd->setText(amt.toDecimalUSDString());
} else if (Settings::getInstance()->get_currency_name() == "EUR") {
usd->setText(amt.toDecimalEURString());
} else if (Settings::getInstance()->get_currency_name() == "BTC") {
usd->setText(amt.toDecimalBTCString());
} else if (Settings::getInstance()->get_currency_name() == "CNY") {
usd->setText(amt.toDecimalCNYString());
} else if (Settings::getInstance()->get_currency_name() == "RUB") {
usd->setText(amt.toDecimalRUBString());
} else if (Settings::getInstance()->get_currency_name() == "CAD") {
usd->setText(amt.toDecimalCADString());
} else if (Settings::getInstance()->get_currency_name() == "SGD") {
usd->setText(amt.toDecimalSGDString());
} else if (Settings::getInstance()->get_currency_name() == "CHF") {
usd->setText(amt.toDecimalCHFString());
} else if (Settings::getInstance()->get_currency_name() == "INR") {
usd->setText(amt.toDecimalINRString());
} else if (Settings::getInstance()->get_currency_name() == "GBP") {
usd->setText(amt.toDecimalGBPString());
} else if (Settings::getInstance()->get_currency_name() == "AUD") {
usd->setText(amt.toDecimalAUDString());
}
// If there is a recurring payment, update the info there as well
//if (sendTxRecurringInfo != nullptr) {
// Recurring::getInstance()->updateInfoWithTx(sendTxRecurringInfo, createTxFromSendPage());
// ui->lblRecurDesc->setText(sendTxRecurringInfo->getScheduleDescription());
// }
}
void MainWindow::setMemoEnabled(int number, bool enabled) {
auto memoBtn = ui->sendToWidgets->findChild<QPushButton*>(QString("MemoBtn") % QString::number(number));
if (enabled) {
memoBtn->setEnabled(true);
memoBtn->setToolTip("");
} else {
memoBtn->setEnabled(false);
memoBtn->setToolTip(tr("Only z-addresses can have memos"));
}
}
void MainWindow::memoButtonClicked(int number, bool includeReplyTo) {
// Memos can only be used with zAddrs. So check that first
// auto addr = ui->sendToWidgets->findChild<QLineEdit*>(QString("Address") + QString::number(number));
//if (! Settings::isZAddress(AddressBook::addressFromAddressLabel(addr->text()))) {
// QMessageBox msg(QMessageBox::Critical, tr("Memos can only be used with z-addresses"),
// tr("The memo field can only be used with a z-address.\n") + addr->text() + tr("\ndoesn't look like a z-address"),
// QMessageBox::Ok, this);
// msg.exec();
// return;
// }
// Get the current memo if it exists
auto memoTxt = ui->sendToWidgets->findChild<QLabel *>(QString("MemoTxt") + QString::number(number));
QString currentMemo = memoTxt->text();
6 years ago
Ui_MemoDialog memoDialog;
QDialog dialog(this);
memoDialog.setupUi(&dialog);
Settings::saveRestore(&dialog);
6 years ago
memoDialog.memoTxt->setLenDisplayLabel(memoDialog.memoSize);
memoDialog.memoTxt->setAcceptButton(memoDialog.buttonBox->button(QDialogButtonBox::Ok));
auto fnAddReplyTo = [=, &dialog]() {
auto replyTo = rpc->getDefaultSaplingAddress();
if (replyTo.isEmpty())
return;
memoDialog.memoTxt->includeReplyTo(replyTo);
// MacOS has a really annoying bug where the Plaintext doesn't refresh when the content is
// updated. So we do this ugly hack - resize the window slightly to force it to refresh
dialog.setGeometry(dialog.geometry().adjusted(0,0,0,1));
dialog.setGeometry(dialog.geometry().adjusted(0,0,0,-1));
};
// Insert From Address button
QObject::connect(memoDialog.btnInsertFrom, &QPushButton::clicked, fnAddReplyTo);
memoDialog.memoTxt->setPlainText(currentMemo);
memoDialog.memoTxt->setFocus();
6 years ago
if (includeReplyTo)
fnAddReplyTo();
6 years ago
if (dialog.exec() == QDialog::Accepted) {
memoTxt->setText(memoDialog.memoTxt->toPlainText());
}
}
void MainWindow::clearSendForm() {
6 years ago
// The last one is a spacer, so ignore that
int totalItems = ui->sendToWidgets->children().size() - 2;
6 years ago
// Clear the first recipient fields
6 years ago
auto addr = ui->sendToWidgets->findChild<QLineEdit*>(QString("Address1"));
addr->clear();
auto amt = ui->sendToWidgets->findChild<QLineEdit*>(QString("Amount1"));
amt->clear();
auto amtUSD = ui->sendToWidgets->findChild<QLabel*>(QString("AmtUSD1"));
amtUSD->clear();
6 years ago
auto max = ui->sendToWidgets->findChild<QCheckBox*>(QString("Max1"));
max->setChecked(false);
auto memo = ui->sendToWidgets->findChild<QLabel*>(QString("MemoTxt1"));
memo->clear();
6 years ago
// Disable first memo btn
setMemoEnabled(1, false);
6 years ago
// Reset the fee
5 years ago
ui->minerFeeAmt->setText(Settings::getMinerFee().toDecimalString());
6 years ago
6 years ago
// Start the deletion after the first item, since we want to keep 1 send field there all there
for (int i=1; i < totalItems; i++) {
auto addressGroupBox = ui->sendToWidgets->findChild<QGroupBox*>(QString("AddressGroupBox") % QString::number(i+1));
6 years ago
delete addressGroupBox;
}
// Reset the recurring button
if (Settings::getInstance()->isTestnet()) {
// ui->chkRecurring->setEnabled(true);
}
// ui->chkRecurring->setCheckState(Qt::Unchecked);
// ui->btnRecurSchedule->setEnabled(false);
// ui->lblRecurDesc->setText("");
// delete sendTxRecurringInfo;
// sendTxRecurringInfo = nullptr;
6 years ago
}
void MainWindow::maxAmountChecked(int checked) {
if (checked == Qt::Checked) {
ui->Amount1->setReadOnly(true);
if (rpc == nullptr) return;
6 years ago
// Calculate maximum amount
CAmount sumAllAmounts;
6 years ago
// Calculate all other amounts
int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that
6 years ago
// Start counting the sum skipping the first one, because the MAX button is on the first one, and we don't
// want to include it in the sum.
6 years ago
for (int i=1; i < totalItems; i++) {
auto amt = ui->sendToWidgets->findChild<QLineEdit*>(QString("Amount") % QString::number(i+1));
5 years ago
sumAllAmounts = sumAllAmounts + CAmount::fromDecimalString(amt->text());
}
6 years ago
5 years ago
sumAllAmounts = sumAllAmounts + Settings::getMinerFee();
auto maxamount = rpc->getModel()->getAvailableBalance() - sumAllAmounts;
maxamount = (maxamount < 0) ? CAmount::fromqint64(0): maxamount;
5 years ago
ui->Amount1->setText(maxamount.toDecimalString());
6 years ago
} else if (checked == Qt::Unchecked) {
// Just remove the readonly part, don't change the content
ui->Amount1->setReadOnly(false);
}
}
// Create a Tx from the current state of the send page.
Tx MainWindow::createTxFromSendPage() {
Tx tx;
// For each addr/amt in the sendTo tab
int totalItems = ui->sendToWidgets->children().size() - 2; // The last one is a spacer, so ignore that
CAmount totalAmt;
for (int i=0; i < totalItems; i++) {
QString addr = ui->sendToWidgets->findChild<QLineEdit*>(QString("Address") % QString::number(i+1))->text().trimmed();
// Remove label if it exists
addr = AddressBook::addressFromAddressLabel(addr);
// QString dustamt = "0";
QString amtStr = ui->sendToWidgets->findChild<QLineEdit*>(QString("Amount") % QString::number(i+1))->text().trimmed();
if (amtStr.isEmpty()) {
amtStr = "-1";; // The user didn't specify an amount
}
bool ok;
CAmount amt;
// Make sure it parses
amtStr.toDouble(&ok);
if (!ok) {
5 years ago
amt = CAmount::fromqint64(-1);
} else {
5 years ago
amt = CAmount::fromDecimalString(amtStr);
totalAmt = totalAmt + amt;
}
4 years ago
QString memo = ui->sendToWidgets->findChild<QLabel*>(QString("MemoTxt") % QString::number(i+1))->text().trimmed();
4 years ago
tx.toAddrs.push_back( ToFields{addr, amt, memo} );
}
// Allow Custom Fee in SendTab
bool customFee = ui->customFee->isChecked();
CAmount fee ;
if (customFee) {
QString feeStr = ui->minerFeeAmt->text();
tx.fee = CAmount::fromDecimalString(feeStr);
}else{
tx.fee = Settings::getMinerFee();
}
return tx;
}
bool MainWindow::confirmTx(Tx tx, RecurringPaymentInfo* rpi) {
// Function to split the address to make it easier to read.
// Split it into chunks of 4 chars.
auto fnSplitAddressForWrap = [=] (const QString& a) -> QString {
if (Settings::isTAddress(a))
return a;
QStringList ans;
static int splitSize = 8;
for (int i=0; i < a.length(); i+= splitSize) {
ans << a.mid(i, splitSize);
}
return ans.join(" ");
// if (! Settings::isZAddress(a)) return a;
// auto half = a.length() / 2;
// auto splitted = a.left(half) + "\n" + a.right(a.length() - half);
// return splitted;
6 years ago
};
// Update the recurring info with the latest Tx
if (rpi != nullptr) {
Recurring::getInstance()->updateInfoWithTx(rpi, tx);
}
6 years ago
6 years ago
// Show a confirmation dialog
QDialog d(this);
Ui_confirm confirm;
confirm.setupUi(&d);
Settings::saveRestore(&d);
6 years ago
6 years ago
// Remove all existing address/amt qlabels on the confirm dialog.
int totalConfirmAddrItems = confirm.sendToAddrs->children().size();
for (int i = 0; i < totalConfirmAddrItems / 3; i++) {
6 years ago
auto addr = confirm.sendToAddrs->findChild<QLabel*>(QString("Addr") % QString::number(i+1));
auto amt = confirm.sendToAddrs->findChild<QLabel*>(QString("Amt") % QString::number(i+1));
auto memo = confirm.sendToAddrs->findChild<QLabel*>(QString("Memo") % QString::number(i+1));
auto amtUSD = confirm.sendToAddrs->findChild<QLabel*>(QString("AmtUSD") % QString::number(i+1));
auto spacer = confirm.sendToAddrs->findChild<QLabel*>(QString("spacer") % QString::number(i+1));
6 years ago
delete memo;
6 years ago
delete addr;
delete amt;
delete amtUSD;
delete spacer;
6 years ago
}
6 years ago
// Remove the fee labels
delete confirm.sendToAddrs->findChild<QLabel*>("labelMinerFee");
delete confirm.sendToAddrs->findChild<QLabel*>("minerFee");
delete confirm.sendToAddrs->findChild<QLabel*>("minerFeeUSD");
// For each addr/amt/memo, construct the JSON and also build the confirm dialog box
int row = 0;
CAmount totalSpending;
for (int i=0; i < tx.toAddrs.size(); i++) {
auto toAddr = tx.toAddrs[i];
6 years ago
6 years ago
// Add new Address widgets instead of the same one.
{
// Address
6 years ago
auto Addr = new QLabel(confirm.sendToAddrs);
Addr->setObjectName(QString("Addr") % QString::number(i + 1));
Addr->setWordWrap(true);
Addr->setText(fnSplitAddressForWrap(toAddr.addr));
confirm.gridLayout->addWidget(Addr, row, 0, 1, 1);
6 years ago
5 years ago
// Amount (hush)
6 years ago
auto Amt = new QLabel(confirm.sendToAddrs);
Amt->setObjectName(QString("Amt") % QString::number(i + 1));
Amt->setText(toAddr.amount.toDecimalhushString());
6 years ago
Amt->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(Amt, row, 1, 1, 1);
5 years ago
totalSpending = totalSpending + toAddr.amount;
// Amount (USD)
if (Settings::getInstance()->get_currency_name() == "USD") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
5 years ago
AmtUSD->setText(toAddr.amount.toDecimalUSDString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (EUR)
} else if (Settings::getInstance()->get_currency_name() == "EUR") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalEURString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (BTC)
} else if (Settings::getInstance()->get_currency_name() == "BTC") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalBTCString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (CNY)
} else if (Settings::getInstance()->get_currency_name() == "CNY") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalCNYString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (RUB)
} else if (Settings::getInstance()->get_currency_name() == "RUB") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalRUBString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (CAD)
} else if (Settings::getInstance()->get_currency_name() == "CAD") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalCADString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (SGD)
} else if (Settings::getInstance()->get_currency_name() == "SGD") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalSGDString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (CHF)
} else if (Settings::getInstance()->get_currency_name() == "CHF") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalCHFString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (INR)
} else if (Settings::getInstance()->get_currency_name() == "INR") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalINRString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (GBP)
} else if (Settings::getInstance()->get_currency_name() == "GBP") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalGBPString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
// Amount (AUD)
} else if (Settings::getInstance()->get_currency_name() == "AUD") {
auto AmtUSD = new QLabel(confirm.sendToAddrs);
AmtUSD->setObjectName(QString("AmtUSD") % QString::number(i + 1));
AmtUSD->setText(toAddr.amount.toDecimalAUDString());
AmtUSD->setAlignment(Qt::AlignRight | Qt::AlignTrailing | Qt::AlignVCenter);
confirm.gridLayout->addWidget(AmtUSD, row, 2, 1, 1);
}
// Memo
5 years ago
if (Settings::isZAddress(toAddr.addr) && !toAddr.memo.isEmpty()) {
row++;
auto Memo = new QLabel(confirm.sendToAddrs);
Memo->setObjectName(QStringLiteral("Memo") % QString::number(i + 1));
5 years ago
Memo->setText(toAddr.memo);
QFont font1 = Addr->font();
font1.setPointSize(font1.pointSize() - 1);
Memo->setFont(font1);
Memo->setWordWrap(true);
confirm.gridLayout->addWidget(Memo, row, 0, 1, 3);
}
row ++;
// Add an empty spacer to create a blank space
auto spacer = new QLabel(confirm.sendToAddrs);
spacer->setObjectName(QString("spacer") % QString::number(i + 1));
confirm.gridLayout->addWidget(spacer, row, 0, 1, 1);
row++;
6 years ago
}
6 years ago
}
6 years ago
// Add fees
{
auto labelMinerFee = new QLabel(confirm.sendToAddrs);
labelMinerFee->setObjectName(QStringLiteral("labelMinerFee"));
confirm.gridLayout->addWidget(labelMinerFee, row, 0, 1, 1);
labelMinerFee->setText(tr("Miner Fee"));
auto minerFee = new QLabel(confirm.sendToAddrs);
6 years ago
QSizePolicy sizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred);
minerFee->setSizePolicy(sizePolicy);
minerFee->setObjectName(QStringLiteral("minerFee"));
minerFee->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
confirm.gridLayout->addWidget(minerFee, row, 1, 1, 1);
minerFee->setText(tx.fee.toDecimalhushString());
5 years ago
totalSpending = totalSpending + tx.fee;
auto minerFeeUSD = new QLabel(confirm.sendToAddrs);
6 years ago
QSizePolicy sizePolicy1(QSizePolicy::Minimum, QSizePolicy::Preferred);
minerFeeUSD->setSizePolicy(sizePolicy1);
minerFeeUSD->setObjectName(QStringLiteral("minerFeeUSD"));
minerFeeUSD->setAlignment(Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter);
confirm.gridLayout->addWidget(minerFeeUSD, row, 2, 1, 1);
if (Settings::getInstance()->get_currency_name() == "USD") {
minerFeeUSD->setText(tx.fee.toDecimalUSDString());
} else if (Settings::getInstance()->get_currency_name() == "EUR") {
minerFeeUSD->setText(tx.fee.toDecimalEURString());
} else if (Settings::getInstance()->get_currency_name() == "BTC") {
minerFeeUSD->setText(tx.fee.toDecimalBTCString());
} else if (Settings::getInstance()->get_currency_name() == "CNY") {
minerFeeUSD->setText(tx.fee.toDecimalCNYString());
} else if (Settings::getInstance()->get_currency_name() == "RUB") {
minerFeeUSD->setText(tx.fee.toDecimalRUBString());
} else if (Settings::getInstance()->get_currency_name() == "CAD") {
minerFeeUSD->setText(tx.fee.toDecimalCADString());
} else if (Settings::getInstance()->get_currency_name() == "SGD") {
minerFeeUSD->setText(tx.fee.toDecimalSGDString());
} else if (Settings::getInstance()->get_currency_name() == "CHF") {
minerFeeUSD->setText(tx.fee.toDecimalCHFString());
} else if (Settings::getInstance()->get_currency_name() == "INR") {
minerFeeUSD->setText(tx.fee.toDecimalINRString());
} else if (Settings::getInstance()->get_currency_name() == "GBP") {
minerFeeUSD->setText(tx.fee.toDecimalGBPString());
} else if (Settings::getInstance()->get_currency_name() == "AUD") {
minerFeeUSD->setText(tx.fee.toDecimalAUDString());
}
}
// Recurring payment info, show only if there is exactly one destination address
if (rpi == nullptr || tx.toAddrs.size() != 1) {
confirm.grpRecurring->setVisible(false);
}
else {
confirm.grpRecurring->setVisible(true);
confirm.lblRecurringDesc->setText(rpi->getScheduleDescription());
}
CAmount defaultFee = Settings::getMinerFee();
if (tx.fee.toDecimalString() != defaultFee.toDecimalString() ) {
auto customFeeWarning = new QLabel(confirm.sendToAddrs);
customFeeWarning->setObjectName(QStringLiteral("Custom Fee"));
customFeeWarning->setText(tr("You are using a custom Fee"));
customFeeWarning->setStyleSheet("color: red;");
confirm.gridLayout->addWidget(customFeeWarning);
confirm.gridLayout->rowStretch(1);
row++;
}
// Syncing warning
confirm.syncingWarning->setVisible(Settings::getInstance()->isSyncing());
6 years ago
// Show the dialog and submit it if the user confirms
return d.exec() == QDialog::Accepted;
}
// Send button clicked
void MainWindow::sendButton() {
// Create a Tx from the values on the send tab. Note that this Tx object
// might not be valid yet.
Tx tx = createTxFromSendPage();
6 years ago
QString error = doSendTxValidations(tx);
if (!error.isEmpty()) {
// Something went wrong, so show an error and exit
QMessageBox msg(QMessageBox::Critical, tr("Transaction Error"), error,
QMessageBox::Ok, this);
msg.exec();
// abort the Tx
return;
}
// Show a dialog to confirm the Tx
if (confirmTx(tx, sendTxRecurringInfo)) {
// If this is a recurring payment, save the hash so we can
// update the payment if it submits.
QString recurringPaymentHash;
// Recurring payments are enabled only if there is exactly 1 destination address.
if (sendTxRecurringInfo && tx.toAddrs.size() == 1) {
// Add it to the list
Recurring::getInstance()->addRecurringInfo(*sendTxRecurringInfo);
recurringPaymentHash = sendTxRecurringInfo->getHash();
}
// Then delete the additional fields from the sendTo tab
clearSendForm();
// Create a new Dialog to show that we are computing/sending the Tx
auto d = new QDialog(this);
auto connD = new Ui_ConnectionDialog();
connD->setupUi(d);
QMovie *movie1 = new QMovie(":/img/res/silentdragonlite-animated-startup-dark.gif");;
QMovie *movie2 = new QMovie(":/img/res/silentdragonlite-animated-startup-dark.gif");;
auto theme = Settings::getInstance()->get_theme_name();
if (theme == "Dark" || theme == "Midnight") {
movie2->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie2);
movie2->start();
} else {
movie1->setScaledSize(QSize(512,512));
connD->topIcon->setMovie(movie1);
movie1->start();
}
connD->status->setText(tr("Please wait..."));
connD->statusDetail->setText(tr("Computing your transaction"));
d->show();
// And send the Tx
rpc->executeTransaction(tx, false,
[=] (QString txid) {
ui->statusBar->showMessage(Settings::txidStatusMessage + " " + txid);
connD->status->setText(tr("Done!"));
connD->statusDetail->setText(txid);
QTimer::singleShot(1000, [=]() {
d->accept();
d->close();
delete connD;
delete d;
// And switch to the balances tab
ui->tabWidget->setCurrentIndex(0);
});
bool isStickyServerEnabled = Settings::getInstance()->getUseStickyServer();
if(isStickyServerEnabled) {
Change lite server after sending a tx for improved privacy Assume Alice is using SDL for 1 hour and makes many transactions, perhaps using HushChat with Bob. The lite server she is connected to will know know that IP address A has created transaction id B, i.e. linkability of IP addresses to all the transaction ids that are created while she is connected. If Bob is connected to the same lite server for some or all of those transactions, the complete transaction graph is known : IP address A created txid B sending to IP address C which is the receiver of txid B. This is not good. One improvement could be that we change lite servers on an interval, such as every 5 minutes. That would be better than nothing, but what seems to be even better is to change the lite server after every tx. This means that every time Alice (or Bob) makes a new transaction, they are potentially talking to a different lite server. It is potentially because it is possible that our randomly chosen new lite server is the same as our previous lite server. We could try to ensure that the new random server is different than our previous, but in edge case of only one server being up, the code gets annoying. This commit implements changing to a likely different lite server after every transaction. In the worst case scenario, it reduces to the privacy of the old behavior, which is to leak all data to the current lite server. In the best case, we spread out metadata leakage to every lite server that is currently up. The average case is to spread out our metadata to more than just one lite server, which is a privacy win. If stickyServer=1, this code is disabled, since it's better for somebody to connect to their own lite server and not leak any metadata to 3rd parties. This algorithm should also be implemented in SDA. As an aside, Zcash has ignored this problem for 2.5 years and only supports talking to a single lite wallet at a time (no random selection on startup) which provides further evidence that ZEC mainnet is a honeypot.
1 year ago
qDebug() << "Not changing servers because stickyServer=1";
} else {
// After each transaction, change servers to spread out
// (ip,txid) metadata across different lite servers
// TODO: should we try to ensure that our new random server is actually different?
auto server = Settings::getRandomServer();
qDebug() << "Changed server to " << server << " for extreme privacy";
ui->statusBar->showMessage("Changed server to " % server);
ui->current_server->setText(server);
}
// Force a UI update so we get the unconfirmed Tx
rpc->refresh(true);
// If this was a recurring payment, update the payment with the info
if (!recurringPaymentHash.isEmpty()) {
// Since this is the send button payment, this is the first payment
Recurring::getInstance()->updatePaymentItem(recurringPaymentHash, 0,
txid, "", PaymentStatus::COMPLETED);
}
},
// Errored out
[=] (QString opid, QString errStr) {
ui->statusBar->showMessage(QObject::tr(" Tx ") % opid % QObject::tr(" failed"), 15 * 1000);
d->accept();
d->close();
delete connD;
delete d;
if (!opid.isEmpty())
errStr = QObject::tr("The transaction with id ") % opid % QObject::tr(" failed. The error was") + ":\n\n" + errStr;
// If this was a recurring payment, update the payment with the failure
if (!recurringPaymentHash.isEmpty()) {
// Since this is the send button payment, this is the first payment
Recurring::getInstance()->updatePaymentItem(recurringPaymentHash, 0,
"", errStr, PaymentStatus::ERROR);
}
QMessageBox::critical(this, QObject::tr("Transaction Error"), errStr, QMessageBox::Ok);
}
);
}
6 years ago
}
QString MainWindow::doSendTxValidations(Tx tx) {
// Check to see if we have enough verified funds to send the Tx.
CAmount total;
for (auto toAddr : tx.toAddrs) {
if (!Settings::isValidAddress(toAddr.addr)) {
QString addr = (toAddr.addr.length() > 100 ? toAddr.addr.left(100) + "..." : toAddr.addr);
return QString(tr("Recipient Address ")) % addr % tr(" is Invalid");
}
// This technically shouldn't be possible, but issue #62 seems to have discovered a bug
// somewhere, so just add a check to make sure.
5 years ago
if (toAddr.amount.toqint64() < 0) {
return QString(tr("Amount for address '%1' is invalid!").arg(toAddr.addr));
}
total = total + toAddr.amount;
}
total = total + tx.fee;
auto available = rpc->getModel()->getAvailableBalance();
if (available < total) {
return tr("Not enough available funds to send this transaction\n\nHave: %1\nNeed: %2\n\nNote: Funds need 1 confirmations before they can be spent")
.arg(available.toDecimalhushString(), total.toDecimalhushString());
}
if (total == 0) {
return tr("Value or fee must be > 0\n\nValue and fee cannot both be 0.");
}
5 years ago
return "";
}
6 years ago
void MainWindow::cancelButton() {
clearSendForm();
6 years ago
}
//Check for custom fee checkbox
void MainWindow::toggleMinerFeeEditable(int state) {
if (state == Qt::Checked) {
ui->minerFeeAmt->setReadOnly(false);
} else {
ui->minerFeeAmt->setReadOnly(true);
ui->minerFeeAmt->setText(Settings::getMinerFee().toDecimalString());
}
}