2020-05-24 13:20:03 +02:00
|
|
|
/*
|
|
|
|
|
* This file is part of the Flowee project
|
2022-04-05 21:18:39 +02:00
|
|
|
* Copyright (C) 2020-2022 Tom Zander <tom@flowee.org>
|
2020-05-24 13:20:03 +02:00
|
|
|
*
|
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
*
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
*
|
|
|
|
|
* You should have received a copy of the GNU General Public License
|
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
*/
|
|
|
|
|
#include "FloweePay.h"
|
|
|
|
|
#include "Wallet.h"
|
2021-10-25 19:42:13 +02:00
|
|
|
#include "NewWalletConfig.h"
|
2022-04-07 18:16:03 +02:00
|
|
|
#include "AddressInfo.h"
|
2022-04-30 19:44:06 +02:00
|
|
|
#include "PriceDataProvider.h"
|
2020-05-24 13:20:03 +02:00
|
|
|
|
|
|
|
|
#include <streaming/MessageParser.h>
|
|
|
|
|
#include <streaming/BufferPool.h>
|
|
|
|
|
#include <streaming/MessageBuilder.h>
|
2021-10-25 19:42:13 +02:00
|
|
|
#include <random.h>
|
2021-08-09 18:46:38 +02:00
|
|
|
#include <config/flowee-config.h>
|
2020-05-24 13:20:03 +02:00
|
|
|
|
|
|
|
|
#include <QStandardPaths>
|
2021-01-05 00:25:23 +01:00
|
|
|
#include <QGuiApplication>
|
|
|
|
|
#include <QClipboard>
|
2021-11-30 12:02:33 +01:00
|
|
|
#include <QDesktopServices>
|
2020-05-24 13:20:03 +02:00
|
|
|
#include <QLocale>
|
|
|
|
|
#include <QSettings>
|
|
|
|
|
#include <QFile>
|
|
|
|
|
#include <QFileInfo>
|
|
|
|
|
#include <base58.h>
|
|
|
|
|
#include <cashaddr.h>
|
2020-12-26 14:40:01 +01:00
|
|
|
#include <QTimer>
|
2021-05-28 11:42:38 +02:00
|
|
|
#include <QResource>
|
2021-10-18 22:14:12 +02:00
|
|
|
#include <QDir>
|
2021-10-21 15:11:36 +02:00
|
|
|
#include <SyncSPVAction.h>
|
2021-11-30 12:02:33 +01:00
|
|
|
#include <QUrl>
|
2020-05-24 13:20:03 +02:00
|
|
|
|
2021-04-21 15:17:08 +02:00
|
|
|
constexpr const char *UNIT_TYPE = "unit";
|
|
|
|
|
constexpr const char *CREATE_START_WALLET = "create-start-wallet";
|
2020-12-25 15:40:21 +01:00
|
|
|
constexpr const char *WINDOW_WIDTH = "window/width";
|
|
|
|
|
constexpr const char *WINDOW_HEIGHT = "window/height";
|
2020-10-14 15:12:33 +02:00
|
|
|
constexpr const char *DARKSKIN = "darkSkin";
|
2021-05-01 16:21:58 +02:00
|
|
|
constexpr const char *HIDEBALANCE = "hideBalance";
|
2020-12-25 15:40:21 +01:00
|
|
|
constexpr const char *USERAGENT = "net/useragent";
|
2021-01-16 17:01:18 +01:00
|
|
|
constexpr const char *DSPTIMEOUT = "payment/dsp-timeout";
|
2020-05-24 13:20:03 +02:00
|
|
|
|
2021-10-25 19:42:13 +02:00
|
|
|
constexpr const char *AppdataFilename = "/appdata";
|
|
|
|
|
constexpr const char *DefaultDerivationPath = "m/44'/145'/0'";
|
|
|
|
|
constexpr const char *DefaultDerivationPathTestnet = "m/44'/145'/0'";
|
2021-05-01 16:21:58 +02:00
|
|
|
|
2020-05-24 13:20:03 +02:00
|
|
|
enum FileTags {
|
2020-10-19 14:05:38 +02:00
|
|
|
WalletId,
|
2022-05-17 23:51:57 +02:00
|
|
|
WalletPriority, // int, maps to PrivacySegment::Priority
|
|
|
|
|
WalletName, // string. Duplicate of the wallet name
|
|
|
|
|
WalletEncryptionSeed // uint32 (see wallet.h)
|
2020-05-24 13:20:03 +02:00
|
|
|
};
|
|
|
|
|
|
2020-10-29 21:51:52 +01:00
|
|
|
static P2PNet::Chain s_chain = P2PNet::MainChain;
|
|
|
|
|
|
2020-05-24 13:20:03 +02:00
|
|
|
FloweePay::FloweePay()
|
2020-10-29 21:51:52 +01:00
|
|
|
: m_basedir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation)),
|
2022-04-30 19:44:06 +02:00
|
|
|
m_chain(s_chain),
|
|
|
|
|
m_prices(new PriceDataProvider())
|
2020-05-24 13:20:03 +02:00
|
|
|
{
|
2022-04-06 14:30:59 +02:00
|
|
|
// make sure the lifetime of the lockedPoolManager exceeds mine (LIFO of globals)
|
|
|
|
|
LockedPoolManager::instance();
|
|
|
|
|
|
2020-12-17 23:12:39 +01:00
|
|
|
if (m_chain == P2PNet::Testnet4Chain) {
|
2020-10-29 21:51:52 +01:00
|
|
|
m_basedir += "/testnet4";
|
2020-12-17 23:12:39 +01:00
|
|
|
m_chainPrefix = "bchtest";
|
2021-10-25 19:42:13 +02:00
|
|
|
m_defaultDerivationPath = QLatin1String(DefaultDerivationPathTestnet);
|
2020-12-17 23:12:39 +01:00
|
|
|
} else {
|
|
|
|
|
m_chainPrefix = "bitcoincash";
|
2021-10-25 19:42:13 +02:00
|
|
|
m_defaultDerivationPath = QLatin1String(DefaultDerivationPath);
|
2020-12-17 23:12:39 +01:00
|
|
|
}
|
2020-10-29 21:51:52 +01:00
|
|
|
boost::filesystem::create_directories(boost::filesystem::path(m_basedir.toStdString()));
|
|
|
|
|
|
2020-06-11 17:41:18 +02:00
|
|
|
// make it move to the proper thread.
|
2021-11-02 14:24:47 +01:00
|
|
|
connect(this, SIGNAL(loadComplete_priv()), this, SLOT(loadingCompleted()), Qt::QueuedConnection);
|
2021-04-21 15:17:08 +02:00
|
|
|
connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, this, [=]() {
|
2020-05-24 13:20:03 +02:00
|
|
|
p2pNet()->shutdown();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// start creation of downloadmanager and loading of data in a different thread
|
|
|
|
|
ioService().post(std::bind(&FloweePay::init, this));
|
|
|
|
|
|
2020-12-25 15:40:21 +01:00
|
|
|
QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat);
|
|
|
|
|
m_unit = static_cast<UnitOfBitcoin>(defaultConfig.value(UNIT_TYPE, BCH).toInt());
|
|
|
|
|
m_windowHeight = defaultConfig.value(WINDOW_HEIGHT, -1).toInt();
|
|
|
|
|
m_windowWidth = defaultConfig.value(WINDOW_WIDTH, -1).toInt();
|
|
|
|
|
m_darkSkin = defaultConfig.value(DARKSKIN, true).toBool();
|
2021-01-16 17:01:18 +01:00
|
|
|
m_dspTimeout = defaultConfig.value(DSPTIMEOUT, 3000).toInt();
|
2021-04-21 15:17:08 +02:00
|
|
|
m_createStartWallet = defaultConfig.value(CREATE_START_WALLET, false).toBool();
|
2020-12-25 15:40:21 +01:00
|
|
|
|
2020-05-24 13:20:03 +02:00
|
|
|
QSettings appConfig;
|
2020-12-25 15:40:21 +01:00
|
|
|
m_unit = static_cast<UnitOfBitcoin>(appConfig.value(UNIT_TYPE, m_unit).toInt());
|
|
|
|
|
m_windowHeight = appConfig.value(WINDOW_HEIGHT, m_windowHeight).toInt();
|
|
|
|
|
m_windowWidth = appConfig.value(WINDOW_WIDTH, m_windowWidth).toInt();
|
|
|
|
|
m_darkSkin = appConfig.value(DARKSKIN, m_darkSkin).toBool();
|
2021-01-16 17:01:18 +01:00
|
|
|
m_dspTimeout = appConfig.value(DSPTIMEOUT, m_dspTimeout).toInt();
|
2021-05-01 16:21:58 +02:00
|
|
|
m_hideBalance = appConfig.value(HIDEBALANCE, false).toBool();
|
2020-12-26 14:40:01 +01:00
|
|
|
|
|
|
|
|
// Update expected chain-height ever 5 minutes
|
|
|
|
|
QTimer *timer = new QTimer(this);
|
|
|
|
|
timer->setTimerType(Qt::VeryCoarseTimer);
|
|
|
|
|
timer->start(5 * 60 * 1000);
|
|
|
|
|
connect (timer, SIGNAL(timeout()), this, SIGNAL(expectedChainHeightChanged()));
|
2021-10-18 22:14:12 +02:00
|
|
|
|
2021-11-19 13:44:54 +01:00
|
|
|
QDir base(QCoreApplication::applicationDirPath() + "/../share/floweepay/");
|
2021-10-18 22:14:12 +02:00
|
|
|
if (base.exists()) {
|
|
|
|
|
// add Mnemonic (BIP39) dictionaries.
|
2021-11-19 13:44:54 +01:00
|
|
|
struct LangPair { const char *id, *filename; };
|
|
|
|
|
static const LangPair knownPairs[] = {
|
|
|
|
|
{"en", "bip39-english"},
|
|
|
|
|
{"zh-simple", "bip39-chinese_simplified"},
|
|
|
|
|
{"zh-traditional", "bip39-chinese_traditional"},
|
|
|
|
|
{"cs", "bip39-czech"},
|
|
|
|
|
{"fr", "bip39-french"},
|
|
|
|
|
{"it", "bip39-italian"},
|
|
|
|
|
{"ja", "bip39-japanese"},
|
|
|
|
|
{"ko", "bip39-korean"},
|
|
|
|
|
{"pt", "bip39-portuguese"},
|
|
|
|
|
{"es", "bip39-spanish"},
|
|
|
|
|
{0, 0}
|
|
|
|
|
};
|
|
|
|
|
for (int i = 0; knownPairs[i].id; ++i) {
|
|
|
|
|
const LangPair lang = knownPairs[i];
|
|
|
|
|
QString fullPath(base.absoluteFilePath(lang.filename));
|
|
|
|
|
if (QFile::exists(fullPath))
|
|
|
|
|
m_hdSeedValidator.registerWordList(lang.id, fullPath);
|
|
|
|
|
}
|
2021-10-18 22:14:12 +02:00
|
|
|
}
|
2021-11-19 13:20:08 +01:00
|
|
|
else {
|
|
|
|
|
logCritical() << "Warning: No bip39 wordlists found. Looking in:" << base.absolutePath();
|
2021-10-18 22:14:12 +02:00
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
FloweePay::~FloweePay()
|
|
|
|
|
{
|
2020-10-19 14:05:38 +02:00
|
|
|
saveData();
|
|
|
|
|
|
2022-07-14 14:54:27 +02:00
|
|
|
for (auto wallet : m_wallets) {
|
|
|
|
|
try {
|
|
|
|
|
wallet->saveWallet();
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
logFatal() << e;
|
|
|
|
|
}
|
|
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
qDeleteAll(m_wallets);
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 21:51:52 +01:00
|
|
|
// static
|
|
|
|
|
void FloweePay::selectChain(P2PNet::Chain chain)
|
|
|
|
|
{
|
|
|
|
|
s_chain = chain;
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-24 13:20:03 +02:00
|
|
|
FloweePay *FloweePay::instance()
|
|
|
|
|
{
|
|
|
|
|
static FloweePay s_app;
|
|
|
|
|
return &s_app;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::init()
|
|
|
|
|
{
|
|
|
|
|
auto dl = p2pNet(); // this wil load the p2p layer.
|
|
|
|
|
|
2021-10-25 19:42:13 +02:00
|
|
|
QFile in(m_basedir + AppdataFilename);
|
2020-10-19 14:05:38 +02:00
|
|
|
Wallet *lastOpened = nullptr;
|
2020-05-24 13:20:03 +02:00
|
|
|
if (in.open(QIODevice::ReadOnly)) {
|
|
|
|
|
const auto dataSize = in.size();
|
|
|
|
|
Streaming::BufferPool pool(dataSize);
|
|
|
|
|
in.read(pool.begin(), dataSize);
|
|
|
|
|
Streaming::MessageParser parser(pool.commit(dataSize));
|
2022-05-17 23:51:57 +02:00
|
|
|
uint32_t walletEncryptionSeed = 0;
|
2020-05-24 13:20:03 +02:00
|
|
|
while (parser.next() == Streaming::FoundTag) {
|
|
|
|
|
if (parser.tag() == WalletId) {
|
2020-10-15 19:18:54 +02:00
|
|
|
try {
|
2022-05-17 23:51:57 +02:00
|
|
|
Wallet *w = new Wallet(m_basedir.toStdString(), parser.intData(), walletEncryptionSeed);
|
2020-11-05 22:15:40 +01:00
|
|
|
w->moveToThread(thread());
|
2020-10-15 19:18:54 +02:00
|
|
|
dl->addDataListener(w);
|
|
|
|
|
dl->connectionManager().addPrivacySegment(w->segment());
|
|
|
|
|
m_wallets.append(w);
|
2022-07-13 13:54:17 +02:00
|
|
|
logDebug() << "Found wallet" << w->name() << "with segment ID:" << w->segment()->segmentId();
|
2022-06-24 00:34:08 +02:00
|
|
|
connect (w, &Wallet::encryptionChanged, w, [=]() {
|
|
|
|
|
// make sure that we get peers for the wallet directly after it gets decrypted
|
2022-06-24 15:13:18 +02:00
|
|
|
if (!m_offline && w->isDecrypted())
|
2022-06-24 00:34:08 +02:00
|
|
|
FloweePay::p2pNet()->addAction<SyncSPVAction>();
|
|
|
|
|
});
|
2020-10-19 14:05:38 +02:00
|
|
|
lastOpened = w;
|
2020-10-15 19:18:54 +02:00
|
|
|
} catch (const std::runtime_error &e) {
|
|
|
|
|
logWarning() << "Wallet load failed:" << e;
|
2020-10-19 14:05:38 +02:00
|
|
|
lastOpened = nullptr;
|
2020-10-15 19:18:54 +02:00
|
|
|
}
|
2022-05-17 23:51:57 +02:00
|
|
|
walletEncryptionSeed = 0;
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
2020-10-19 14:05:38 +02:00
|
|
|
else if (parser.tag() == WalletPriority) {
|
|
|
|
|
if (lastOpened) {
|
|
|
|
|
if (parser.isInt()
|
|
|
|
|
&& parser.intData() >= PrivacySegment::First
|
|
|
|
|
&& parser.intData() <= PrivacySegment::OnlyManual) {
|
|
|
|
|
lastOpened->segment()->setPriority(static_cast<PrivacySegment::Priority>(parser.intData()));
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
logWarning() << "Priority out of range";
|
|
|
|
|
}
|
|
|
|
|
else
|
|
|
|
|
logWarning() << "Priority found, but no wallet to apply it to";
|
|
|
|
|
}
|
2022-05-17 23:51:57 +02:00
|
|
|
else if (parser.tag() == WalletName) {
|
|
|
|
|
if (lastOpened && lastOpened->name().isEmpty())
|
|
|
|
|
lastOpened->setName(QString::fromUtf8(parser.stringData().c_str()));
|
|
|
|
|
}
|
|
|
|
|
else if (parser.tag() == WalletEncryptionSeed) {
|
|
|
|
|
walletEncryptionSeed = static_cast<uint32_t>(parser.longData());
|
|
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-21 15:17:08 +02:00
|
|
|
if (m_wallets.isEmpty() && m_createStartWallet) {
|
2022-06-22 15:58:05 +02:00
|
|
|
auto config = createNewWallet(m_defaultDerivationPath);
|
|
|
|
|
delete config; // the config was just for QML, so avoid a dangling object.
|
2021-04-21 15:17:08 +02:00
|
|
|
m_wallets.at(0)->setUserOwnedWallet(false);
|
2021-07-30 12:19:12 +02:00
|
|
|
m_wallets.at(0)->segment()->setPriority(PrivacySegment::Last);
|
2022-04-06 14:45:46 +02:00
|
|
|
m_wallets.at(0)->setName(tr("Initial Wallet"));
|
2021-10-21 17:04:20 +02:00
|
|
|
saveData();
|
2021-04-21 15:17:08 +02:00
|
|
|
}
|
2022-04-30 19:44:06 +02:00
|
|
|
|
2021-11-02 14:24:47 +01:00
|
|
|
emit loadComplete_priv(); // move execution to loadingCompleted, in a Qt thread
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::loadingCompleted()
|
|
|
|
|
{
|
|
|
|
|
for (auto wallet : m_wallets) {
|
|
|
|
|
wallet->performUpgrades();
|
|
|
|
|
}
|
2022-04-30 19:44:06 +02:00
|
|
|
if (!m_offline && m_chain == P2PNet::MainChain)
|
|
|
|
|
m_prices->start();
|
2021-11-02 14:24:47 +01:00
|
|
|
|
|
|
|
|
emit loadComplete();
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::saveData()
|
|
|
|
|
{
|
|
|
|
|
Streaming::BufferPool data;
|
|
|
|
|
Streaming::MessageBuilder builder(data);
|
2020-12-25 23:27:14 +01:00
|
|
|
for (auto &wallet : m_wallets) {
|
2022-05-17 23:51:57 +02:00
|
|
|
if (wallet->encryptionSeed() != 0)
|
2022-06-24 00:35:20 +02:00
|
|
|
builder.add(WalletEncryptionSeed, static_cast<uint64_t>(wallet->encryptionSeed()));
|
2020-05-24 13:20:03 +02:00
|
|
|
builder.add(WalletId, wallet->segment()->segmentId());
|
2020-10-19 14:05:38 +02:00
|
|
|
builder.add(WalletPriority, wallet->segment()->priority());
|
2022-05-17 23:51:57 +02:00
|
|
|
if (!wallet->name().isEmpty()) {
|
2022-06-22 15:58:05 +02:00
|
|
|
auto nameData = wallet->name().toUtf8();
|
|
|
|
|
builder.addByteArray(WalletName, nameData.constData(), nameData.size());
|
2022-05-17 23:51:57 +02:00
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
2021-10-25 19:42:13 +02:00
|
|
|
QString filebase = m_basedir + AppdataFilename;
|
2020-05-24 13:20:03 +02:00
|
|
|
QFile out(filebase + "~");
|
|
|
|
|
out.remove(); // avoid overwrite issues.
|
|
|
|
|
if (out.open(QIODevice::WriteOnly)) {
|
|
|
|
|
auto buf = builder.buffer();
|
|
|
|
|
auto rc = out.write(buf.begin(), buf.size());
|
|
|
|
|
if (rc == -1) {
|
|
|
|
|
logFatal() << "Failed to write. Disk full?";
|
|
|
|
|
// TODO have an app-wide error
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
out.close();
|
|
|
|
|
QFile::remove(filebase);
|
|
|
|
|
if (!out.rename(filebase)) {
|
|
|
|
|
logFatal() << "Failed to write to" << filebase;
|
|
|
|
|
// TODO have an app-wide error
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
logFatal() << "Failed to create data file. Disk full?";
|
|
|
|
|
// TODO have an app-wide error
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::saveAll()
|
|
|
|
|
{
|
2020-12-25 23:27:14 +01:00
|
|
|
for (auto &wallet : m_wallets) {
|
2020-05-24 13:20:03 +02:00
|
|
|
wallet->saveWallet();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FloweePay::basedir() const
|
|
|
|
|
{
|
|
|
|
|
return m_basedir;
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-18 17:40:41 +01:00
|
|
|
QString FloweePay::priceToStringPretty(double price) const
|
|
|
|
|
{
|
|
|
|
|
QString answer = FloweePay::priceToString(static_cast<qint64>(price), m_unit);
|
|
|
|
|
int c = answer.size();
|
|
|
|
|
while (c > 0) {
|
|
|
|
|
auto k = answer.at(c - 1).unicode();
|
|
|
|
|
if (k == '.' || k == ',') {
|
|
|
|
|
--c;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
if (k > '0' && k <= '9')
|
|
|
|
|
break;
|
|
|
|
|
--c;
|
|
|
|
|
}
|
|
|
|
|
return answer.left(c);
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-06 15:26:46 +01:00
|
|
|
// static
|
|
|
|
|
QString FloweePay::priceToString(qint64 price, UnitOfBitcoin unit)
|
2020-05-24 13:20:03 +02:00
|
|
|
{
|
2021-01-06 15:26:46 +01:00
|
|
|
if (unit == Satoshis)
|
2020-10-15 20:04:10 +02:00
|
|
|
return QString::number(price);
|
2020-05-24 13:20:03 +02:00
|
|
|
QByteArray string(QByteArray::number(std::abs(price)));
|
|
|
|
|
|
|
|
|
|
int decimals;
|
2021-01-06 15:26:46 +01:00
|
|
|
switch (unit) {
|
2020-05-24 13:20:03 +02:00
|
|
|
default:
|
|
|
|
|
decimals = 8;
|
|
|
|
|
break;
|
|
|
|
|
case FloweePay::MilliBCH:
|
|
|
|
|
decimals = 5;
|
|
|
|
|
break;
|
|
|
|
|
case FloweePay::MicroBCH:
|
|
|
|
|
case FloweePay::Bits:
|
|
|
|
|
decimals = 2;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
const char decimalPoint = QLocale::system().decimalPoint().unicode();
|
|
|
|
|
|
|
|
|
|
int stringLength = string.size();
|
|
|
|
|
int neededSize = std::max(stringLength, decimals) + 1; // 1 for the decimalPoint.
|
|
|
|
|
if (price < 0) // minus sign
|
|
|
|
|
neededSize++;
|
|
|
|
|
if (stringLength <= decimals) // add a zero in front of the decimalPoint too.
|
|
|
|
|
neededSize++;
|
|
|
|
|
string.resize(neededSize);
|
|
|
|
|
char *str = string.data();
|
|
|
|
|
memcpy(str + string.size() - stringLength, str, stringLength); // move to be right-aligned
|
|
|
|
|
for (int i = string.size() - stringLength; i > 0; --i) { str[i - 1] = '0'; } // pad zeros on left
|
|
|
|
|
|
|
|
|
|
// insert the actual decimal point. We need to move the part in front of it back to the left to make space.
|
|
|
|
|
for (int i = 0; i < string.size() - decimals; ++i) {
|
|
|
|
|
str[i - 1] = str[i];
|
|
|
|
|
}
|
|
|
|
|
str[string.size() - decimals - 1] = decimalPoint;
|
|
|
|
|
if (price < 0)
|
|
|
|
|
*str = '-';
|
|
|
|
|
return QString::fromLatin1(str);
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-31 15:19:35 +01:00
|
|
|
QString FloweePay::formatDate(QDateTime date) const
|
|
|
|
|
{
|
|
|
|
|
static QString format = QLocale::system().dateFormat(QLocale::ShortFormat);
|
|
|
|
|
if (!date.isValid() || date.isNull())
|
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
|
|
const QDateTime now = QDateTime::currentDateTime();
|
|
|
|
|
if (now > date) {
|
|
|
|
|
// use the 'yesterday' style if the date is reasonably close.
|
|
|
|
|
const auto days = date.daysTo(now);
|
|
|
|
|
if (days == 0)
|
|
|
|
|
return tr("Today");
|
|
|
|
|
if (days == 1)
|
|
|
|
|
return tr("Yesterday");
|
|
|
|
|
if (days < 9) // return day of the week
|
|
|
|
|
return date.toString("dddd");
|
|
|
|
|
}
|
|
|
|
|
return date.toString(format);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FloweePay::formatDateTime(QDateTime date) const
|
|
|
|
|
{
|
|
|
|
|
static QString format = QLocale::system().dateTimeFormat(QLocale::ShortFormat);
|
|
|
|
|
static QString timeFormat = QLocale::system().timeFormat(QLocale::ShortFormat);
|
|
|
|
|
if (!date.isValid() || date.isNull())
|
|
|
|
|
return QString();
|
|
|
|
|
|
|
|
|
|
const QDateTime now = QDateTime::currentDateTime();
|
2022-04-13 11:02:16 +02:00
|
|
|
// instead of simply checking if \a date is in the past, allow for a bit of
|
|
|
|
|
// imprecision based on the fact that blocks are not going to have the precise
|
|
|
|
|
// timestamps (due to how mining works) and they can be slightly in the future.
|
|
|
|
|
// So to avoid a timestamp that is up to a minute in the future being displayed
|
|
|
|
|
// with a full ISO date instead of 'now', we need to have the next line be a
|
|
|
|
|
// bit more lenient.
|
|
|
|
|
if (date.secsTo(now) > -60) { // in the past, or at most 1 min in the future.
|
2021-10-31 15:19:35 +01:00
|
|
|
// use the 'yesterday' style if the date is reasonably close.
|
|
|
|
|
const auto secs = date.secsTo(now);
|
2021-11-21 11:29:08 +01:00
|
|
|
if (secs < 24 * 60) {
|
|
|
|
|
const int mins = (secs + 24) / 60;
|
|
|
|
|
if (mins <= 0)
|
|
|
|
|
return tr("Now", "timestamp");
|
|
|
|
|
return tr("%1 minutes ago", "relative time stamp", mins).arg(mins);
|
|
|
|
|
}
|
|
|
|
|
if (secs < 18 * 60 * 60) {
|
|
|
|
|
if (secs < 46 * 60)
|
|
|
|
|
return tr("½ hour ago", "timestamp");
|
|
|
|
|
const int hours = (secs + 900) / 3600;
|
|
|
|
|
return tr("%1 hours ago", "timestamp", hours).arg(hours);
|
|
|
|
|
}
|
2021-10-31 15:19:35 +01:00
|
|
|
const auto days = date.daysTo(now);
|
|
|
|
|
if (days == 0)
|
|
|
|
|
return tr("Today") + " " + date.toString(timeFormat);
|
|
|
|
|
if (days == 1)
|
|
|
|
|
return tr("Yesterday") + " " + date.toString(timeFormat);
|
|
|
|
|
if (days < 9) // return day of the week
|
|
|
|
|
return date.toString("dddd " + timeFormat);
|
|
|
|
|
}
|
|
|
|
|
return date.toString(format);
|
|
|
|
|
}
|
|
|
|
|
|
2020-05-24 13:20:03 +02:00
|
|
|
Wallet *FloweePay::createWallet(const QString &name)
|
|
|
|
|
{
|
|
|
|
|
auto dl = p2pNet();
|
|
|
|
|
|
|
|
|
|
quint16 id;
|
|
|
|
|
while (true) {
|
|
|
|
|
id = rand();
|
|
|
|
|
QString dir = QString("/wallet-%1").arg(id);
|
2020-12-25 23:27:14 +01:00
|
|
|
if (!QFileInfo::exists(m_basedir + dir))
|
2020-05-24 13:20:03 +02:00
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 19:18:54 +02:00
|
|
|
Wallet *w = Wallet::createWallet(m_basedir.toStdString(), id, name);
|
2020-05-24 13:20:03 +02:00
|
|
|
dl->addDataListener(w);
|
|
|
|
|
dl->connectionManager().addPrivacySegment(w->segment());
|
|
|
|
|
m_wallets.append(w);
|
|
|
|
|
|
|
|
|
|
emit walletsChanged();
|
|
|
|
|
return w;
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-29 21:51:52 +01:00
|
|
|
P2PNet::Chain FloweePay::chain() const
|
|
|
|
|
{
|
|
|
|
|
return m_chain;
|
|
|
|
|
}
|
|
|
|
|
|
2021-01-05 00:25:23 +01:00
|
|
|
void FloweePay::copyToClipboard(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
QGuiApplication::clipboard()->setText(text);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-30 12:02:33 +01:00
|
|
|
void FloweePay::openInExplorer(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
if (text.size() == 64) { // assume this is a txid
|
|
|
|
|
QDesktopServices::openUrl(QUrl("https://blockchair.com/bitcoin-cash/transaction/" + text));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
// Add maybe other types?
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-15 20:04:10 +02:00
|
|
|
FloweePay::UnitOfBitcoin FloweePay::unit() const
|
|
|
|
|
{
|
|
|
|
|
return m_unit;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setUnit(const UnitOfBitcoin &unit)
|
|
|
|
|
{
|
|
|
|
|
if (m_unit == unit)
|
|
|
|
|
return;
|
|
|
|
|
m_unit = unit;
|
2021-06-07 23:35:33 +02:00
|
|
|
QSettings appConfig;
|
|
|
|
|
appConfig.setValue(UNIT_TYPE, m_unit);
|
2020-10-15 20:04:10 +02:00
|
|
|
emit unitChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-17 17:34:40 +02:00
|
|
|
int FloweePay::headerChainHeight() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_downloadManager.get())
|
|
|
|
|
return 0;
|
|
|
|
|
return m_downloadManager->blockHeight();
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-26 14:40:01 +01:00
|
|
|
int FloweePay::expectedChainHeight() const
|
|
|
|
|
{
|
|
|
|
|
if (!m_downloadManager.get())
|
|
|
|
|
return 0;
|
|
|
|
|
return m_downloadManager->blockchain().expectedBlockHeight();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FloweePay::chainHeight()
|
|
|
|
|
{
|
|
|
|
|
if (m_initialHeaderChainHeight <= 0)
|
|
|
|
|
m_initialHeaderChainHeight = headerChainHeight();
|
2021-02-06 16:30:51 +01:00
|
|
|
|
|
|
|
|
const int hch = headerChainHeight();
|
|
|
|
|
if (m_initialHeaderChainHeight == headerChainHeight()) {
|
|
|
|
|
const int expected = expectedChainHeight();
|
|
|
|
|
const int behind = expected - hch; // num blocks we are behind theoretical height
|
|
|
|
|
if (behind > 3) // don't report expected when variance could explain the diff
|
|
|
|
|
return expected;
|
|
|
|
|
}
|
2020-12-26 14:40:01 +01:00
|
|
|
return headerChainHeight();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-17 17:34:40 +02:00
|
|
|
void FloweePay::blockchainHeightChanged(int)
|
|
|
|
|
{
|
|
|
|
|
emit headerChainHeightChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2020-10-14 15:12:33 +02:00
|
|
|
bool FloweePay::darkSkin() const
|
|
|
|
|
{
|
|
|
|
|
return m_darkSkin;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setDarkSkin(bool darkSkin)
|
|
|
|
|
{
|
|
|
|
|
if (m_darkSkin == darkSkin)
|
|
|
|
|
return;
|
|
|
|
|
m_darkSkin = darkSkin;
|
|
|
|
|
emit darkSkinChanged();
|
2021-01-07 20:09:41 +01:00
|
|
|
QSettings appConfig;
|
|
|
|
|
appConfig.setValue(DARKSKIN, m_darkSkin);
|
2020-10-14 15:12:33 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-11 17:56:06 +02:00
|
|
|
int FloweePay::windowHeight() const
|
|
|
|
|
{
|
2020-10-14 15:12:33 +02:00
|
|
|
return m_windowHeight;
|
2020-06-11 17:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setWindowHeight(int windowHeight)
|
|
|
|
|
{
|
2020-10-14 15:12:33 +02:00
|
|
|
if (m_windowHeight == windowHeight)
|
|
|
|
|
return;
|
|
|
|
|
m_windowHeight = windowHeight;
|
2021-01-07 20:09:41 +01:00
|
|
|
QSettings appConfig;
|
|
|
|
|
appConfig.setValue(WINDOW_HEIGHT, m_windowHeight);
|
2020-10-14 15:12:33 +02:00
|
|
|
emit windowHeightChanged();
|
2020-06-11 17:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FloweePay::windowWidth() const
|
|
|
|
|
{
|
2020-10-14 15:12:33 +02:00
|
|
|
return m_windowWidth;
|
2020-06-11 17:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setWindowWidth(int windowWidth)
|
|
|
|
|
{
|
2020-10-14 15:12:33 +02:00
|
|
|
if (windowWidth == m_windowWidth)
|
|
|
|
|
return;
|
|
|
|
|
m_windowWidth = windowWidth;
|
|
|
|
|
emit windowWidthChanged();
|
2021-01-07 20:09:41 +01:00
|
|
|
QSettings appConfig;
|
|
|
|
|
appConfig.setValue(WINDOW_WIDTH, m_windowWidth);
|
2020-06-11 17:56:06 +02:00
|
|
|
}
|
|
|
|
|
|
2021-05-28 18:13:50 +02:00
|
|
|
uint32_t FloweePay::walletStartHeightHint() const
|
2020-10-30 18:06:58 +01:00
|
|
|
{
|
2021-05-28 18:13:50 +02:00
|
|
|
if (m_downloadManager->isChainUpToDate())
|
|
|
|
|
return m_downloadManager->blockHeight();
|
|
|
|
|
|
|
|
|
|
// return a massively too high number that the wallet will
|
|
|
|
|
// interpret as seconds and match it to the block time to resolve a
|
|
|
|
|
// hight when the headers are synched.
|
|
|
|
|
return time(nullptr);
|
2020-10-30 18:06:58 +01:00
|
|
|
}
|
|
|
|
|
|
2022-04-30 19:44:06 +02:00
|
|
|
PriceDataProvider *FloweePay::prices() const
|
|
|
|
|
{
|
|
|
|
|
return m_prices.get();
|
|
|
|
|
}
|
|
|
|
|
|
2022-04-05 21:18:39 +02:00
|
|
|
bool FloweePay::isOffline() const
|
|
|
|
|
{
|
|
|
|
|
return m_offline;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setOffline(bool offline)
|
|
|
|
|
{
|
|
|
|
|
m_offline = offline;
|
2022-04-30 19:44:06 +02:00
|
|
|
if (offline)
|
|
|
|
|
m_prices->mock(50000);
|
2022-04-05 21:18:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::startNet()
|
|
|
|
|
{
|
|
|
|
|
if (m_offline)
|
|
|
|
|
return;
|
|
|
|
|
p2pNet()->start(); // lets go!
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-04 17:41:49 +02:00
|
|
|
bool FloweePay::preferSchnorr() const
|
|
|
|
|
{
|
|
|
|
|
return m_preferSchnorr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setPreferSchnorr(bool preferSchnorr)
|
|
|
|
|
{
|
|
|
|
|
if (m_preferSchnorr == preferSchnorr)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
m_preferSchnorr = preferSchnorr;
|
|
|
|
|
emit preferSchnorrChanged();
|
|
|
|
|
}
|
|
|
|
|
|
2021-08-09 18:46:38 +02:00
|
|
|
QString FloweePay::version() const
|
|
|
|
|
{
|
|
|
|
|
return QCoreApplication::instance()->applicationVersion();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FloweePay::libsVersion() const
|
|
|
|
|
{
|
|
|
|
|
return QString("%1.%2.%3")
|
|
|
|
|
.arg(CLIENT_VERSION_MAJOR)
|
|
|
|
|
.arg(QString::number(CLIENT_VERSION_MINOR), 2, '0')
|
|
|
|
|
.arg(CLIENT_VERSION_REVISION);
|
|
|
|
|
}
|
|
|
|
|
|
2021-05-01 16:21:58 +02:00
|
|
|
bool FloweePay::hideBalance() const
|
|
|
|
|
{
|
|
|
|
|
return m_hideBalance;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setHideBalance(bool hideBalance)
|
|
|
|
|
{
|
|
|
|
|
if (m_hideBalance == hideBalance)
|
|
|
|
|
return;
|
|
|
|
|
m_hideBalance = hideBalance;
|
|
|
|
|
emit hideBalanceChanged();
|
|
|
|
|
QSettings appConfig;
|
|
|
|
|
appConfig.setValue(HIDEBALANCE, m_hideBalance);
|
|
|
|
|
}
|
|
|
|
|
|
2021-11-01 17:58:11 +01:00
|
|
|
NewWalletConfig* FloweePay::createImportedWallet(const QString &privateKey, const QString &walletName, int startHeight)
|
2020-05-24 13:20:03 +02:00
|
|
|
{
|
|
|
|
|
auto wallet = createWallet(walletName);
|
2020-12-26 19:35:54 +01:00
|
|
|
wallet->setSingleAddressWallet(true);
|
2021-11-01 17:58:11 +01:00
|
|
|
if (startHeight <= 1)
|
|
|
|
|
startHeight = s_chain == P2PNet::MainChain ? 550000 : 1000;
|
|
|
|
|
wallet->addPrivateKey(privateKey, startHeight);
|
2021-10-21 17:04:20 +02:00
|
|
|
saveData();
|
2022-06-24 15:13:18 +02:00
|
|
|
if (!m_offline)
|
|
|
|
|
p2pNet()->addAction<SyncSPVAction>(); // make sure that we get peers for the new wallet.
|
2021-10-25 19:42:13 +02:00
|
|
|
|
|
|
|
|
return new NewWalletConfig(wallet);
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
|
|
|
|
|
2022-04-07 18:16:03 +02:00
|
|
|
QObject *FloweePay::researchAddress(const QString &address, QObject *parent)
|
|
|
|
|
{
|
|
|
|
|
CashAddress::Content c = CashAddress::decodeCashAddrContent(address.toStdString(), m_chainPrefix);
|
|
|
|
|
if (c.hash.empty() || c.type != CashAddress::PUBKEY_TYPE) {
|
|
|
|
|
logWarning() << "researchAddress() only works with a propertly formatted cash-address!";
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
2022-07-06 22:06:58 +02:00
|
|
|
const KeyId key(reinterpret_cast<char *>(c.hash.data()));
|
2022-04-07 18:16:03 +02:00
|
|
|
|
|
|
|
|
// if we don't know the address, return a nullptr
|
|
|
|
|
AddressInfo *info = nullptr;
|
|
|
|
|
|
|
|
|
|
for (const auto *wallet : qAsConst(m_wallets)) {
|
|
|
|
|
int privKeyId = wallet->findPrivKeyId(key);
|
|
|
|
|
if (privKeyId != -1) {
|
|
|
|
|
info = new AddressInfo(address, parent);
|
|
|
|
|
auto details = wallet->fetchKeyDetails(privKeyId);
|
|
|
|
|
info->setCoins(details.coins);
|
|
|
|
|
info->setHistoricalCoins(details.historicalCoins);
|
|
|
|
|
info->setSaldo(details.saldo);
|
2022-04-12 21:38:08 +02:00
|
|
|
info->setAccountName(wallet->name());
|
2022-04-07 18:16:03 +02:00
|
|
|
info->setAccountId(wallet->segment()->segmentId());
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-25 19:42:13 +02:00
|
|
|
NewWalletConfig* FloweePay::createImportedHDWallet(const QString &mnemonic, const QString &password, const QString &derivationPathStr, const QString &walletName, int startHeight)
|
2021-10-18 22:14:12 +02:00
|
|
|
{
|
2021-10-18 23:19:57 +02:00
|
|
|
auto wallet = createWallet(walletName);
|
|
|
|
|
try {
|
|
|
|
|
std::vector<uint32_t> derivationPath = HDMasterKey::deriveFromString(derivationPathStr.toStdString());
|
2021-10-21 20:54:34 +02:00
|
|
|
if (startHeight <= 1)
|
|
|
|
|
startHeight = s_chain == P2PNet::MainChain ? 550000 : 1000;
|
|
|
|
|
wallet->createHDMasterKey(mnemonic, password, derivationPath, startHeight);
|
|
|
|
|
wallet->segment()->blockSynched(startHeight);
|
|
|
|
|
wallet->segment()->blockSynched(startHeight); // yes, twice
|
2021-10-21 17:04:20 +02:00
|
|
|
saveData();
|
2022-06-24 15:13:18 +02:00
|
|
|
if (!m_offline)
|
|
|
|
|
p2pNet()->addAction<SyncSPVAction>(); // make sure that we get peers for the new wallet.
|
2021-10-25 19:42:13 +02:00
|
|
|
|
|
|
|
|
return new NewWalletConfig(wallet);
|
2021-10-18 23:19:57 +02:00
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
logFatal() << "Failed to parse user provided data due to:" << e;
|
2021-10-25 19:42:13 +02:00
|
|
|
return nullptr;
|
2021-10-18 23:19:57 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool FloweePay::checkDerivation(const QString &path) const
|
|
|
|
|
{
|
|
|
|
|
try {
|
|
|
|
|
auto vector = HDMasterKey::deriveFromString(path.toStdString());
|
|
|
|
|
return true;
|
|
|
|
|
} catch (...) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
2021-10-18 22:14:12 +02:00
|
|
|
}
|
|
|
|
|
|
2020-06-08 14:24:27 +02:00
|
|
|
FloweePay::StringType FloweePay::identifyString(const QString &string) const
|
2020-05-24 13:20:03 +02:00
|
|
|
{
|
2021-10-18 22:14:12 +02:00
|
|
|
const QString string_ = string.trimmed();
|
|
|
|
|
const std::string s = string_.toStdString();
|
|
|
|
|
if (string_.isEmpty()) {
|
|
|
|
|
m_hdSeedValidator.clearSelectedLanguage();
|
|
|
|
|
return Unknown;
|
|
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
|
|
|
|
|
CBase58Data legacy;
|
|
|
|
|
if (legacy.SetString(s)) {
|
2020-10-30 18:06:58 +01:00
|
|
|
if ((m_chain == P2PNet::MainChain && legacy.isMainnetPkh())
|
|
|
|
|
|| (m_chain == P2PNet::Testnet4Chain && legacy.isTestnetPkh()))
|
2020-05-24 13:20:03 +02:00
|
|
|
return LegacyPKH;
|
2020-10-30 18:06:58 +01:00
|
|
|
if ((m_chain == P2PNet::MainChain && legacy.isMainnetSh())
|
|
|
|
|
|| (m_chain == P2PNet::Testnet4Chain && legacy.isTestnetSh()))
|
|
|
|
|
return LegacySH;
|
|
|
|
|
if ((m_chain == P2PNet::MainChain && legacy.isMainnetPrivKey())
|
|
|
|
|
|| (m_chain == P2PNet::Testnet4Chain && legacy.isTestnetPrivKey()))
|
2020-05-24 13:20:03 +02:00
|
|
|
return PrivateKey;
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-17 23:12:39 +01:00
|
|
|
CashAddress::Content c = CashAddress::decodeCashAddrContent(s, m_chainPrefix);
|
2020-05-24 13:20:03 +02:00
|
|
|
if (!c.hash.empty()) {
|
|
|
|
|
if (c.type == CashAddress::PUBKEY_TYPE)
|
|
|
|
|
return CashPKH;
|
|
|
|
|
if (c.type == CashAddress::SCRIPT_TYPE)
|
|
|
|
|
return CashSH;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-18 22:14:12 +02:00
|
|
|
try {
|
2021-10-31 13:46:47 +01:00
|
|
|
int firstWord = -2;
|
|
|
|
|
int space = -1;
|
|
|
|
|
do {
|
|
|
|
|
++space;
|
|
|
|
|
int space2 = string.indexOf(' ', space);
|
|
|
|
|
auto word = string.mid(space, space2 - space);
|
|
|
|
|
if (!word.isEmpty()) {
|
|
|
|
|
int index = m_hdSeedValidator.findWord(word);
|
|
|
|
|
if (firstWord == -2) {
|
|
|
|
|
firstWord = index;
|
|
|
|
|
if (index != -1) {
|
|
|
|
|
auto validity = m_hdSeedValidator.validateMnemonic(string_, index);
|
|
|
|
|
if (validity == Mnemonic::Valid)
|
|
|
|
|
return CorrectMnemonic;
|
|
|
|
|
}
|
|
|
|
|
else { // not a recognized word
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else if (index == -1) { // a not-first-word failed the lookup.
|
|
|
|
|
if (space2 != -1) // this is the last word, don't highlight while writing.
|
|
|
|
|
return PartialMnemonicWithTypo;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
// if we get to this point in the loop then we have a real word that we found in the dictionary.
|
|
|
|
|
// Lets continue checking words and check if the rest of the words are part of the lexicon too.
|
|
|
|
|
}
|
|
|
|
|
space = space2;
|
|
|
|
|
} while (space != -1);
|
|
|
|
|
if (firstWord >= 0)
|
2021-10-18 22:14:12 +02:00
|
|
|
return PartialMnemonic;
|
|
|
|
|
} catch (const std::exception &e) {
|
|
|
|
|
// probably deployment issues (faulty word list)
|
|
|
|
|
logFatal() << e;
|
2021-10-31 13:46:47 +01:00
|
|
|
return MissingLexicon;
|
2021-10-18 22:14:12 +02:00
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
return Unknown;
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-25 19:42:13 +02:00
|
|
|
NewWalletConfig* FloweePay::createNewBasicWallet(const QString &walletName)
|
2020-05-24 13:20:03 +02:00
|
|
|
{
|
|
|
|
|
auto wallet = createWallet(walletName);
|
2020-10-30 18:06:58 +01:00
|
|
|
wallet->createNewPrivateKey(walletStartHeightHint());
|
2022-06-24 15:13:18 +02:00
|
|
|
if (!m_offline)
|
|
|
|
|
p2pNet()->addAction<SyncSPVAction>();
|
2021-10-25 19:42:13 +02:00
|
|
|
return new NewWalletConfig(wallet);
|
|
|
|
|
}
|
2021-10-21 17:04:20 +02:00
|
|
|
|
2021-10-25 19:42:13 +02:00
|
|
|
NewWalletConfig* FloweePay::createNewWallet(const QString &derivationPath, const QString &password, const QString &walletName)
|
|
|
|
|
{
|
2021-10-27 18:31:49 +02:00
|
|
|
// special case the first user-created wallet.
|
|
|
|
|
// If the user creates a new wallet that is identical to the one we auto-created, reuse that one.
|
2021-11-01 16:28:25 +01:00
|
|
|
const bool haveOneHiddenWallet = m_wallets.size() == 1 && !m_wallets.first()->userOwnedWallet();
|
2021-10-27 18:31:49 +02:00
|
|
|
if (haveOneHiddenWallet) {
|
|
|
|
|
auto wallet = m_wallets.first();
|
|
|
|
|
if (wallet->isHDWallet() && derivationPath == wallet->derivationPath() && password == wallet->hdWalletMnemonicPwd()) {
|
|
|
|
|
wallet->setUserOwnedWallet(true);
|
|
|
|
|
if (!walletName.isEmpty())
|
|
|
|
|
wallet->setName(walletName);
|
|
|
|
|
// little hacky to make listeners realize we really changed the wallet.
|
|
|
|
|
m_wallets.clear();
|
|
|
|
|
emit walletsChanged();
|
|
|
|
|
m_wallets.append(wallet);
|
|
|
|
|
emit walletsChanged();
|
|
|
|
|
return new NewWalletConfig(wallet);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-10-25 19:42:13 +02:00
|
|
|
auto wallet = createWallet(walletName);
|
2021-11-01 17:57:11 +01:00
|
|
|
std::vector<uint8_t> seed(16); // 12 word seed
|
2021-10-25 19:42:13 +02:00
|
|
|
RandAddSeedPerfmon();
|
|
|
|
|
GetRandBytes(seed.data(), seed.size());
|
|
|
|
|
auto mnemonic = m_hdSeedValidator.generateMnemonic(seed, "en");
|
|
|
|
|
std::vector<uint32_t> dp = HDMasterKey::deriveFromString(derivationPath.toStdString());
|
|
|
|
|
wallet->createHDMasterKey(mnemonic, password, dp, walletStartHeightHint());
|
2022-06-24 15:13:18 +02:00
|
|
|
if (!m_offline)
|
|
|
|
|
p2pNet()->addAction<SyncSPVAction>(); // make sure that we get peers for the new wallet.
|
2021-10-25 19:42:13 +02:00
|
|
|
|
|
|
|
|
return new NewWalletConfig(wallet);
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FloweePay::unitName() const
|
|
|
|
|
{
|
2021-04-22 16:13:40 +02:00
|
|
|
return nameOfUnit(m_unit);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString FloweePay::nameOfUnit(FloweePay::UnitOfBitcoin unit) const
|
|
|
|
|
{
|
|
|
|
|
switch (unit) {
|
2020-06-12 20:53:01 +02:00
|
|
|
case FloweePay::BCH:
|
2020-12-26 15:03:02 +01:00
|
|
|
if (s_chain == P2PNet::MainChain)
|
|
|
|
|
return QLatin1String("BCH");
|
|
|
|
|
else
|
|
|
|
|
return QLatin1String("tBCH");
|
2020-06-12 20:53:01 +02:00
|
|
|
case FloweePay::MilliBCH:
|
2020-12-26 15:03:02 +01:00
|
|
|
if (s_chain == P2PNet::MainChain)
|
|
|
|
|
return QLatin1String("mBCH");
|
|
|
|
|
else
|
|
|
|
|
return QLatin1String("m-tBCH");
|
2020-06-12 20:53:01 +02:00
|
|
|
case FloweePay::MicroBCH:
|
2020-12-26 15:03:02 +01:00
|
|
|
if (s_chain == P2PNet::MainChain)
|
|
|
|
|
return QString("µBCH");
|
|
|
|
|
else
|
|
|
|
|
return QString("µ-tBCH");
|
2020-06-12 20:53:01 +02:00
|
|
|
case FloweePay::Bits:
|
2020-12-26 15:03:02 +01:00
|
|
|
if (s_chain == P2PNet::MainChain)
|
|
|
|
|
return QLatin1String("bits");
|
|
|
|
|
else
|
|
|
|
|
return QLatin1String("tbits");
|
2020-06-12 20:53:01 +02:00
|
|
|
case FloweePay::Satoshis:
|
2020-12-26 15:03:02 +01:00
|
|
|
if (s_chain == P2PNet::MainChain)
|
|
|
|
|
return QLatin1String("sats");
|
|
|
|
|
else
|
|
|
|
|
return QLatin1String("tsats");
|
2020-06-12 20:53:01 +02:00
|
|
|
default:
|
2021-04-22 16:13:40 +02:00
|
|
|
return QString();
|
2020-06-12 20:53:01 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int FloweePay::unitAllowedDecimals() const
|
|
|
|
|
{
|
|
|
|
|
switch (m_unit) {
|
|
|
|
|
case FloweePay::BCH:
|
|
|
|
|
return 8;
|
|
|
|
|
case FloweePay::MilliBCH:
|
|
|
|
|
return 5;
|
|
|
|
|
case FloweePay::MicroBCH:
|
|
|
|
|
case FloweePay::Bits:
|
|
|
|
|
return 2;
|
|
|
|
|
case FloweePay::Satoshis:
|
|
|
|
|
return 0;
|
|
|
|
|
default:
|
2020-10-15 20:04:10 +02:00
|
|
|
Q_ASSERT(false);
|
2020-06-12 20:53:01 +02:00
|
|
|
return 0;
|
|
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QList<Wallet *> FloweePay::wallets() const
|
|
|
|
|
{
|
|
|
|
|
return m_wallets;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
DownloadManager *FloweePay::p2pNet()
|
|
|
|
|
{
|
2020-10-17 17:34:40 +02:00
|
|
|
if (m_downloadManager == nullptr) {
|
2020-10-29 21:51:52 +01:00
|
|
|
m_downloadManager.reset(new DownloadManager(ioService(), m_basedir.toStdString(), m_chain));
|
2020-10-24 17:09:10 +02:00
|
|
|
m_downloadManager->addP2PNetListener(this);
|
2021-02-05 16:56:02 +01:00
|
|
|
m_downloadManager->notifications().addListener(&m_notifications);
|
2020-12-25 15:40:21 +01:00
|
|
|
|
|
|
|
|
QSettings defaultConfig(":/defaults.ini", QSettings::IniFormat);
|
|
|
|
|
QString useragent = defaultConfig.value(USERAGENT, "Flowee Pay Wallet").toString();
|
|
|
|
|
m_downloadManager->connectionManager().setUserAgent(useragent.toStdString());
|
2020-10-17 17:34:40 +02:00
|
|
|
emit headerChainHeightChanged();
|
2020-12-26 14:40:01 +01:00
|
|
|
emit expectedChainHeightChanged();
|
2020-10-17 17:34:40 +02:00
|
|
|
}
|
2020-05-24 13:20:03 +02:00
|
|
|
return m_downloadManager.get();
|
|
|
|
|
}
|
2020-12-17 23:12:39 +01:00
|
|
|
|
2021-01-16 17:01:18 +01:00
|
|
|
int FloweePay::dspTimeout() const
|
2021-01-16 14:28:09 +01:00
|
|
|
{
|
2021-01-16 17:01:18 +01:00
|
|
|
return m_dspTimeout;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void FloweePay::setDspTimeout(int milliseconds)
|
|
|
|
|
{
|
|
|
|
|
if (milliseconds == m_dspTimeout)
|
|
|
|
|
return;
|
|
|
|
|
m_dspTimeout = milliseconds;
|
|
|
|
|
emit dspTimeoutChanged();
|
|
|
|
|
QSettings appConfig;
|
|
|
|
|
appConfig.setValue(DSPTIMEOUT, m_dspTimeout);
|
2021-01-16 14:28:09 +01:00
|
|
|
}
|
|
|
|
|
|
2020-12-17 23:12:39 +01:00
|
|
|
const std::string &chainPrefix()
|
|
|
|
|
{
|
|
|
|
|
return FloweePay::instance()->chainPrefix();
|
|
|
|
|
}
|
2021-11-21 17:46:17 +01:00
|
|
|
|
2022-07-06 22:06:58 +02:00
|
|
|
QString renderAddress(const KeyId &pubkeyhash)
|
2021-11-21 17:46:17 +01:00
|
|
|
{
|
|
|
|
|
CashAddress::Content c;
|
|
|
|
|
c.type = CashAddress::PUBKEY_TYPE;
|
|
|
|
|
c.hash = std::vector<uint8_t>(pubkeyhash.begin(), pubkeyhash.end());
|
2022-06-22 15:58:05 +02:00
|
|
|
const std::string &chainPref = FloweePay::instance()->chainPrefix();
|
|
|
|
|
auto s = CashAddress::encodeCashAddr(chainPref, c);
|
|
|
|
|
const auto size = chainPref.size();
|
2021-11-23 11:26:12 +01:00
|
|
|
return QString::fromLatin1(s.c_str() + size + 1, s.size() - size -1); // the 1 is for the colon
|
2021-11-21 17:46:17 +01:00
|
|
|
}
|