|
|
|
// Copyright 2019-2024 The Hush developers
|
|
|
|
// Released under the GPLv3
|
|
|
|
#include "mainwindow.h"
|
|
|
|
#include "settings.h"
|
|
|
|
#include "camount.h"
|
|
|
|
#include "../lib/silentdragonlitelib.h"
|
|
|
|
#include <QUrlQuery>
|
|
|
|
|
|
|
|
Settings* Settings::instance = nullptr;
|
|
|
|
|
|
|
|
Settings* Settings::init() {
|
|
|
|
if (instance == nullptr)
|
|
|
|
instance = new Settings();
|
|
|
|
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
Settings* Settings::getInstance() {
|
|
|
|
return instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
Config Settings::getSettings() {
|
|
|
|
qDebug() << __func__;
|
|
|
|
// Load from the QT Settings.
|
|
|
|
QSettings s;
|
|
|
|
|
|
|
|
auto server = s.value("connection/server").toString();
|
|
|
|
bool sticky = s.value("connection/stickyServer").toBool();
|
|
|
|
bool torOnly = s.value("connection/torOnly").toBool();
|
|
|
|
|
|
|
|
while (server.endsWith("/")) {
|
|
|
|
// trailing slashes make Rust sad
|
|
|
|
server.chop(1);
|
|
|
|
}
|
|
|
|
|
|
|
|
// default behavior : no server listed in conf, randomly choose from server list, unless sticky
|
|
|
|
if (server.trimmed().isEmpty()) {
|
|
|
|
server = Settings::getRandomServer();
|
|
|
|
|
|
|
|
bool isOnline = false;
|
|
|
|
// make sure existing server in conf is alive, otherwise choose random one
|
|
|
|
try {
|
|
|
|
isOnline = litelib_check_server_online(server.toStdString().c_str());
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isOnline) {
|
|
|
|
qDebug() << "Lite server in conf " << server << " is down, getting a random one";
|
|
|
|
server = Settings::getRandomServer();
|
|
|
|
s.setValue("connection/server", server);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (sticky) {
|
|
|
|
qDebug() << server << " is sticky";
|
|
|
|
}
|
|
|
|
// if it's down, oh well
|
|
|
|
}
|
|
|
|
|
|
|
|
s.sync();
|
|
|
|
// re-init to load correct settings
|
|
|
|
init();
|
|
|
|
|
|
|
|
return Config{server, torOnly, sticky};
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::saveSettings(const QString& server) {
|
|
|
|
QSettings s;
|
|
|
|
|
|
|
|
s.setValue("connection/server", server);
|
|
|
|
s.sync();
|
|
|
|
|
|
|
|
// re-init to load correct settings
|
|
|
|
init();
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isTestnet() {
|
|
|
|
return _isTestnet;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::setTestnet(bool isTestnet) {
|
|
|
|
this->_isTestnet = isTestnet;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isSaplingAddress(QString addr) {
|
|
|
|
if (!isValidAddress(addr))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return ( isTestnet() && addr.startsWith("ztestsapling")) ||
|
|
|
|
(!isTestnet() && addr.startsWith("zs1"));
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isZAddress(QString addr) {
|
|
|
|
if (!isValidAddress(addr))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return addr.startsWith("zs");
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isTAddress(QString addr) {
|
|
|
|
if (!isValidAddress(addr))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
return addr.startsWith("R");
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Settings::gethushdVersion() {
|
|
|
|
return _hushdVersion;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::sethushdVersion(QString version) {
|
|
|
|
_hushdVersion = version;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isSyncing() {
|
|
|
|
return _isSyncing;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::setSyncing(bool syncing) {
|
|
|
|
this->_isSyncing = syncing;
|
|
|
|
}
|
|
|
|
|
|
|
|
int Settings::getBlockNumber() {
|
|
|
|
return this->_blockNumber;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::setBlockNumber(int number) {
|
|
|
|
this->_blockNumber = number;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isSaplingActive() {
|
|
|
|
return (isTestnet() && getBlockNumber() > 0) || (!isTestnet() && getBlockNumber() > 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
double Settings::getHUSHPrice() {
|
|
|
|
return HUSHPrice;
|
|
|
|
}
|
|
|
|
double Settings::getEURPrice() {
|
|
|
|
return EURPrice;
|
|
|
|
}
|
|
|
|
double Settings::getBTCPrice() {
|
|
|
|
return BTCPrice;
|
|
|
|
}
|
|
|
|
double Settings::getCNYPrice() {
|
|
|
|
return CNYPrice;
|
|
|
|
}
|
|
|
|
double Settings::getRUBPrice() {
|
|
|
|
return RUBPrice;
|
|
|
|
}
|
|
|
|
double Settings::getCADPrice() {
|
|
|
|
return CADPrice;
|
|
|
|
}
|
|
|
|
double Settings::getSGDPrice() {
|
|
|
|
return SGDPrice;
|
|
|
|
}
|
|
|
|
double Settings::getCHFPrice() {
|
|
|
|
return CHFPrice;
|
|
|
|
}
|
|
|
|
double Settings::getINRPrice() {
|
|
|
|
return INRPrice;
|
|
|
|
}
|
|
|
|
double Settings::getGBPPrice() {
|
|
|
|
return GBPPrice;
|
|
|
|
}
|
|
|
|
double Settings::getAUDPrice() {
|
|
|
|
return AUDPrice;
|
|
|
|
}
|
|
|
|
double Settings::getUSDVolume() {
|
|
|
|
return USDVolume;
|
|
|
|
}
|
|
|
|
double Settings::getEURVolume() {
|
|
|
|
return EURVolume;
|
|
|
|
}
|
|
|
|
double Settings::getBTCVolume() {
|
|
|
|
return BTCVolume;
|
|
|
|
}
|
|
|
|
double Settings::getCNYVolume() {
|
|
|
|
return CNYVolume;
|
|
|
|
}
|
|
|
|
double Settings::getRUBVolume() {
|
|
|
|
return RUBVolume;
|
|
|
|
}
|
|
|
|
double Settings::getCADVolume() {
|
|
|
|
return CADVolume;
|
|
|
|
}
|
|
|
|
double Settings::getSGDVolume() {
|
|
|
|
return SGDVolume;
|
|
|
|
}
|
|
|
|
double Settings::getCHFVolume() {
|
|
|
|
return CHFVolume;
|
|
|
|
}
|
|
|
|
double Settings::getINRVolume() {
|
|
|
|
return INRVolume;
|
|
|
|
}
|
|
|
|
double Settings::getGBPVolume() {
|
|
|
|
return GBPVolume;
|
|
|
|
}
|
|
|
|
double Settings::getAUDVolume() {
|
|
|
|
return AUDVolume;
|
|
|
|
}
|
|
|
|
double Settings::getUSDCAP() {
|
|
|
|
return USDCAP;
|
|
|
|
}
|
|
|
|
double Settings::getEURCAP() {
|
|
|
|
return EURCAP;
|
|
|
|
}
|
|
|
|
double Settings::getBTCCAP() {
|
|
|
|
return BTCCAP;
|
|
|
|
}
|
|
|
|
double Settings::getCNYCAP() {
|
|
|
|
return CNYCAP;
|
|
|
|
}
|
|
|
|
double Settings::getRUBCAP() {
|
|
|
|
return RUBCAP;
|
|
|
|
}
|
|
|
|
double Settings::getCADCAP() {
|
|
|
|
return CADCAP;
|
|
|
|
}
|
|
|
|
double Settings::getSGDCAP() {
|
|
|
|
return SGDCAP;
|
|
|
|
}
|
|
|
|
double Settings::getCHFCAP() {
|
|
|
|
return CHFCAP;
|
|
|
|
}
|
|
|
|
double Settings::getINRCAP() {
|
|
|
|
return INRCAP;
|
|
|
|
}
|
|
|
|
double Settings::getGBPCAP() {
|
|
|
|
return GBPCAP;
|
|
|
|
}
|
|
|
|
double Settings::getAUDCAP() {
|
|
|
|
return AUDCAP;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
bool Settings::getCheckForUpdates() {
|
|
|
|
return QSettings().value("options/allowcheckupdates", true).toBool();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::setCheckForUpdates(bool allow) {
|
|
|
|
QSettings().setValue("options/allowcheckupdates", allow);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::getAllowFetchPrices() {
|
|
|
|
// now defaults to OFF, used to be ON
|
|
|
|
return QSettings().value("options/allowfetchprices", false).toBool();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::setAllowFetchPrices(bool allow) {
|
|
|
|
QSettings().setValue("options/allowfetchprices", allow);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::getUseStickyServer() {
|
|
|
|
return QSettings().value("connection/stickyServer", false).toBool();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::setUseStickyServer(bool allow) {
|
|
|
|
QSettings().setValue("connection/stickyServer", allow);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::getUseNoteAutomation() {
|
|
|
|
return QSettings().value("options/useNoteAutomation", true).toBool();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::setUseNoteAutomation(bool allow) {
|
|
|
|
QSettings().setValue("options/useNoteAutomation", allow);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Settings::get_currency_name() {
|
|
|
|
// Load from the QT Settings.
|
|
|
|
return QSettings().value("options/currency_name", false).toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::set_currency_name(QString currency_name) {
|
|
|
|
QSettings().setValue("options/currency_name", currency_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Settings::get_theme_name() {
|
|
|
|
// Load from the QT Settings.
|
|
|
|
return QSettings().value("options/theme_name", "Dark").toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::set_theme_name(QString theme_name) {
|
|
|
|
QSettings().setValue("options/theme_name", theme_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Static Stuff
|
|
|
|
void Settings::saveRestore(QDialog* d) {
|
|
|
|
d->restoreGeometry(QSettings().value(d->objectName() % "geometry").toByteArray());
|
|
|
|
|
|
|
|
QObject::connect(d, &QDialog::finished, [=](auto) {
|
|
|
|
QSettings().setValue(d->objectName() % "geometry", d->saveGeometry());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::saveRestoreTableHeader(QTableView* table, QDialog* d, QString tablename) {
|
|
|
|
table->horizontalHeader()->restoreState(QSettings().value(tablename).toByteArray());
|
|
|
|
table->horizontalHeader()->setStretchLastSection(true);
|
|
|
|
|
|
|
|
QObject::connect(d, &QDialog::finished, [=](auto) {
|
|
|
|
QSettings().setValue(tablename, table->horizontalHeader()->saveState());
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
QString Settings::getRandomServer() {
|
|
|
|
qDebug() << __func__;
|
|
|
|
// The more servers from different TLDs, the better
|
|
|
|
QList<QString> servers = {
|
|
|
|
"https://lite.hush.is",
|
|
|
|
"https://lite.myhush.org",
|
|
|
|
"https://wtfistheinternet.hush.is",
|
|
|
|
"https://poop.granitefone.me",
|
|
|
|
// These can be un-commented to test out how code deals with down servers
|
|
|
|
//"https://thisisdown1.example.com",
|
|
|
|
//"https://thisisdown2.example.com",
|
|
|
|
//"https://thisisdown3.example.com",
|
|
|
|
//"https://thisisdown4.example.com",
|
|
|
|
//"https://thisisdown5.example.com",
|
|
|
|
"https://lite.hush.land",
|
|
|
|
"https://lite.hushpool.is",
|
|
|
|
"https://lite2.hushpool.is"
|
|
|
|
};
|
|
|
|
|
|
|
|
// we don't need cryptographic random-ness, but we want
|
|
|
|
// clients to never get "stuck" with the same server, which
|
|
|
|
// prevents various attacks
|
|
|
|
int x = rand() % servers.size();
|
|
|
|
auto server = servers[x];
|
|
|
|
int tries = 0;
|
|
|
|
|
|
|
|
// We try every server,in order, starting from a random place in the list
|
|
|
|
while (tries < servers.size() ) {
|
|
|
|
qDebug() << "Checking if lite server " << server << " is alive, try=" << tries;
|
|
|
|
|
|
|
|
bool isOnline = "";
|
|
|
|
|
|
|
|
try {
|
|
|
|
isOnline = litelib_check_server_online(server.toStdString().c_str());
|
|
|
|
} catch (const std::exception& e) {
|
|
|
|
qDebug() << __func__ << ": caught an exception, ignoring: " << e.what();
|
|
|
|
}
|
|
|
|
|
|
|
|
// if we see a valid connection, return this server.
|
|
|
|
if (isOnline) {
|
|
|
|
qDebug() << "Choosing lite server " << server;
|
|
|
|
return server;
|
|
|
|
}
|
|
|
|
x++;
|
|
|
|
x = x % servers.size();
|
|
|
|
server = servers[x];
|
|
|
|
tries++;
|
|
|
|
}
|
|
|
|
return server;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::openAddressInExplorer(QString address) {
|
|
|
|
QString url = "https://explorer.hush.is/address/" + address;
|
|
|
|
QDesktopServices::openUrl(QUrl(url));
|
|
|
|
}
|
|
|
|
|
|
|
|
void Settings::openTxInExplorer(QString txid) {
|
|
|
|
QString url = "https://explorer.hush.is/tx/" + txid;
|
|
|
|
QDesktopServices::openUrl(QUrl(url));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const QString Settings::txidStatusMessage = QString(QObject::tr("Tx submitted (right click to copy) txid:"));
|
|
|
|
|
|
|
|
QString Settings::getTokenName() {
|
|
|
|
if (Settings::getInstance()->isTestnet()) {
|
|
|
|
return "TUSH";
|
|
|
|
} else {
|
|
|
|
return "HUSH";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
CAmount Settings::getMinerFee() {
|
|
|
|
return CAmount::fromqint64(10000);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isValidSaplingPrivateKey(QString pk) {
|
|
|
|
if (isTestnet()) {
|
|
|
|
QRegExp zspkey("^secret-extended-key-test[0-9a-z]{278}$", Qt::CaseInsensitive);
|
|
|
|
return zspkey.exactMatch(pk);
|
|
|
|
} else {
|
|
|
|
QRegExp zspkey("^secret-extended-key-main[0-9a-z]{278}$", Qt::CaseInsensitive);
|
|
|
|
return zspkey.exactMatch(pk);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool Settings::isValidAddress(QString addr) {
|
|
|
|
QRegExp zsexp("^zs1[a-z0-9]{75}$", Qt::CaseInsensitive);
|
|
|
|
QRegExp ztsexp("^ztestsapling[a-z0-9]{76}", Qt::CaseInsensitive);
|
|
|
|
QRegExp texp("^R[a-z0-9]{33}$", Qt::CaseInsensitive);
|
|
|
|
|
|
|
|
return texp.exactMatch(addr) || ztsexp.exactMatch(addr) || zsexp.exactMatch(addr);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get a pretty string representation of this Payment URI
|
|
|
|
QString Settings::paymentURIPretty(PaymentURI uri) {
|
|
|
|
CAmount amount = CAmount::fromDecimalString(uri.amt);
|
|
|
|
return QString() + "Payment Request\n" + "Pay: " + uri.addr + "\nAmount: " + amount.toDecimalhushString()
|
|
|
|
+ "\nMemo:" + QUrl::fromPercentEncoding(uri.memo.toUtf8());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Parse a payment URI string into its components
|
|
|
|
PaymentURI Settings::parseURI(QString uri) {
|
|
|
|
PaymentURI ans;
|
|
|
|
|
|
|
|
if (!uri.startsWith("hush:")) {
|
|
|
|
ans.error = "Not a HUSH payment URI";
|
|
|
|
return ans;
|
|
|
|
}
|
|
|
|
|
|
|
|
uri = uri.right(uri.length() - QString("hush:").length());
|
|
|
|
|
|
|
|
QRegExp re("([a-zA-Z0-9]+)");
|
|
|
|
int pos;
|
|
|
|
if ( (pos = re.indexIn(uri)) == -1 ) {
|
|
|
|
ans.error = "Couldn't find an address";
|
|
|
|
return ans;
|
|
|
|
}
|
|
|
|
|
|
|
|
ans.addr = re.cap(1);
|
|
|
|
if (!Settings::isValidAddress(ans.addr)) {
|
|
|
|
ans.error = "Could not understand address";
|
|
|
|
return ans;
|
|
|
|
}
|
|
|
|
uri = uri.right(uri.length() - ans.addr.length()-1); // swallow '?'
|
|
|
|
QUrlQuery query(uri);
|
|
|
|
|
|
|
|
// parse out amt / amount
|
|
|
|
if (query.hasQueryItem("amt")) {
|
|
|
|
ans.amt = query.queryItemValue("amt");
|
|
|
|
} else if (query.hasQueryItem("amount")) {
|
|
|
|
ans.amt = query.queryItemValue("amount");
|
|
|
|
}
|
|
|
|
|
|
|
|
// parse out memo / msg / message
|
|
|
|
if (query.hasQueryItem("memo")) {
|
|
|
|
ans.memo = query.queryItemValue("memo");
|
|
|
|
} else if (query.hasQueryItem("msg")) {
|
|
|
|
ans.memo = query.queryItemValue("msg");
|
|
|
|
} else if (query.hasQueryItem("message")) {
|
|
|
|
ans.memo = query.queryItemValue("message");
|
|
|
|
}
|
|
|
|
|
|
|
|
return ans;
|
|
|
|
}
|
|
|
|
|
|
|
|
const QString Settings::labelRegExp("[a-zA-Z0-9\\-_]{0,40}");
|